计算机网络(三) —— 简单Udp网络程序

2024-09-05 13:20

本文主要是介绍计算机网络(三) —— 简单Udp网络程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一,初始化服务器

1.0 辅助文件

1.1 socket函数

1.2 填充sockaddr结构体

1.3 bind绑定函数

1.4 字符串IP和整数IP的转换

二,运行服务器

2.1 接收

2.2 处理

2.3 返回

三,客户端实现

3.1 UdpClient.cc 实现

 3.2 Main.cc 实现

3.3 效果展示

3.4 代码分层

四,两种场景

4.1 发送部分命令给服务器并返回结果

4.2 实现Linux多终端窗口群聊

4.3 实现Windows做客户端,Linux做服务器群聊


一,初始化服务器

1.0 辅助文件

Log.hpp 日志打印文件:

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#ifndef _LOG_H_
#define _LOG_H_#include <ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[] = {"Debug","Notice","Warning","Error"};std::ostream &Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}#endif

makefile文件:

.PHONY:all
all:udpserver udpclient
udpserver:Main.ccg++ -o $@ $^ -std=c++11
udpclient:UdpClient.ccg++ -o $@ $^ -lpthread -std=c++11.PHONY:clean
clean:rm -f udpserver udpclient 

UdpServer.cc 部分代码:

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <functional>
#include <unordered_map>#include "Log.hpp"enum
{SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0"; // 设置为0,表示任意地址绑定
const int size = 1024;class Udpserver
{
public:Udpserver(const uint16_t &port = defaultport, const std::string &ip = defaultip): _sockfd(0), _port(port), _ip(ip), _isrunning(false){}~Udpserver(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;                                                      // 网络文件描述符uint16_t _port;                                                   // 表明服务器进程的端口号std::string _ip;                                                  // 地址绑定bool _isrunning;                                                  // 表明服务器是否在运行状态std::unordered_map<std::string, struct sockaddr_in> _online_user; // 第一个键值是ip,第二个键值是ip对应的套接字结构体信息
};

1.1 socket函数

我们首先会把服务器封装成一个类,然后定义一个服务器对象之后做的第一件事就是初始化服务器,而初始化服务器的第一件事,就是创建套接字,下面介绍以下socket接口:

参数说明:

  • domain:表示创建套接字的类型,该参数相当于struct sockaddr结构体的前16个比特位。在man手册中往下滑有很多很多AF开头的选项,但是我们目前只要关心几个:如果是本地通信,该参数就设置为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或AF_INET6(IPv6)。
  • type:表示创建套接字时所需的服务类型,最常见的就是:①SOCK_DGRAM,基于UDP的用户数据报服务    ②SOCK_STREAM,基于TCP的流式套接字服务
  • protocol:表示创建套接字的协议类别,可以指名为TCP或UDP,但该参数一般直接设置为0即可,表示默认,此时就会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议
  • 当套接字创建成功后,会直接返回一个文件描述符,创建失败返回-1,同时错误码被设置

问题:socket创建套接字时干了什么?

解答:上面说socket创建成功后会返回一个文件描述符,所以最简单的说法就是“socket创建套接字本质就是打开了一个文件”,以前的打开文件对应的一般是磁盘,把磁盘的文件加载到内存中,并且在进程内部构建files_struct,并且包括文件描述符表;而这里的打开“网络文件”,对应的就是网卡了,通过网卡的驱动层,在操作系统中构建“网卡文件”,通过操作“网卡文件”实现对网卡的宏观控制

下面是服务器初始化函数创建套接字代码: 

void Init(){// 1,创建Udp套接字,Udp的socket是全双工的,允许被同时读写的_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 表示使用IPv4协议,类型为Udp用户数据报if (_sockfd < 0)                          // 创建失败{Log("socket create error", Error) << "\n";exit(SOCKET_ERR);}Log("socket create success", Debug) << "\n"; // 创建成功,输出日志// ...}

创建成功后,_sockfd会被赋值成3 

1.2 填充sockaddr结构体

在上一篇文章的 4.2 sockaddr结构的时候,讲到过,在代码部分我们会对该结构体进行填充,原因请参照上篇博客:计算机网络(二) —— 网络编程套接字-CSDN博客

创建完套接字后,服务器初始化第一步完成,接着第二步就是构建填充sockaddr结构体了,如下代码:

 // 2,创建和填充sockaddr结构体struct sockaddr_in local;// 一bzero(&local, sizeof(local)); // 把指定类型的指定大小初始化为0,功能类似于memset// 二// 然后就是将我们自己的服务器的一些信息填充进这个结构体里,方便socket API使用local.sin_family = AF_INET; // 表明自身的结构体类型为IPv4   这个family是在宏定义用##来实现的// 三// local.sin_port = _port; //表明服务器将来要绑定的端口号 -- 需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的// 除了发正常消息外,我也要把我的端口号发给客户端,这样客户端发给我的时候才能找到我,所以端口号需要发送到网络里的,所以一开始我们把这个东东填充到结构体里时,必须是网络字节序local.sin_port = htons(_port); // 把主机序列转化成网络序列,大端不变,小端会转大端// 四// local.sin_addr = _ip;  //s表示socket,然后in表示inet,addr表示IP地址(ifconfig命令)// 我们需要先把string的ip --> uint32_t的ip,并且必须是网络序列的,而这样的各种转化都是有相应的接口的,不需要我们自己写了local.sin_addr.s_addr = inet_addr(_ip.c_str()); // inet_addr表示把字符串转为网络字节序列也就是uint32_t// 查看sockaddr_in的定义后可以发现,sin_addr其实是一个struct类型,这个类型里的s_addr才是要转化的类型//  local.sin_addr.s_addr = htonl(INADDR_ANY); // 这个宏表示任意ip地址,数值为0

1.3 bind绑定函数

上面两步操作完成之后,就是绑定了,下面介绍以下bind函数:

参数解释:

  • sockfd:就是之前socket函数返回的套接字
  • addr:这个就是我们前面填充的sockaddr结构体的指针,里面有绑定的所有必须信息
  • addrlen:传入的sockaddr结构体的长度

下面是初始化服务器的第三步绑定端口的代码:

// 3,绑定套接字
//  local是在地址空间的用户栈上定义的,上面三个参数的所有操作都是在栈上填好,并没有将local与网络套接字socket相关联,所以需要绑定bind函数
int n = bind(_sockfd, (const struct sockaddr *)&local, sizeof(local));
if (n < 0)
{Log("port bind error", Error) << "\n";exit(BIND_ERR);
}
Log("port bind success", Debug) << "\n";
std::cout << "Waiting user to connect ... " << std::endl;

1.4 字符串IP和整数IP的转换

网络传输的数据是寸土寸金的,如果我们在传输IP时以字符串的点分十进制进行IP传输,那么一个IP就需要15字节,但实际上不需要消耗这么多

IP地址可以划分位四个区域,每个区域取值都是0~255,每个区域是8个比特位,我们就可以只用32比特位表示四个区域来表示IP:

所以完成上面的操作就需要将IP在整数二号字符串之间做转换

首先是数字IP转字符串IP:

然后是字符串IP转数字IP: 

 inet_addr函数与inet_ntoa函数

上面的步骤了解一下即可,而且实际实现起来比较麻烦,所以系统为我们提供了相应的转换函数,我们直接调用即可:

二,运行服务器

2.1 接收

服务器初始化完成后,紧接着就是启动辣,服务器的任务就是周而复始为我们提供服务,所以运行起来一般不会退出,因此服务器的运行代码应该是一个死循环。

服务器运行起来后有三个基础动作

  1. 接收来自客户端的信息
  2. 处理信息
  3. 将结果返回给客户端

第一步就是接收,用到的函数名称为:recvfrom函数,下面来介绍一下这个函数 :

参数有点多,但不复杂,解释一下:

  • sockfd:老朋友了,socket的返回值
  • buf:表示要将读取到的数据放到哪里
  • len:表示要读取数据的字节数
  • flags:表示读取的方式,一般设为0,表示阻塞读取,没数据来的时候就给我等着
  • src_addr输出型参数,会保存发送方的协议结组,IP地址,端口号等,简单来说就是,这个字段会告诉程序“谁发数据过来的”,是为了后面发回去的之后,知道对方是谁在哪(传入结构体地址时需要强转,代码会有)
  • addrlen:表示读取到的src_addr的长度,需要和src_addr的大小一致
  • 返回值:表示读取成功返回实际读取到的字节数,读取失败返回-1,错误码被设置
void Run()
{_isrunning = true;char inbuffer[size]; //while (_isrunning)   // 服务器应该能周而复始一直在运行的,所以是死循环{struct sockaddr_in client;socklen_t len = sizeof(client);// 读取数据,从指定套接字里读取消息,然后把读取到的数据放到缓冲区中并指名长度,最后两个参数作为输出型参数,保存对方的IP和port等信息,方便后面我发消息回去ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){Log("recvfrom error", Warning) << "\n";continue;}// ...
}

2.2 处理

上面拿到数据之后,就是要对数据进行处理辣

但是这个处理,就是根据具体的业务需要,由公司具体实施了,我们这里只用很简单的几行代码模拟处理过程,后面会有几种场景专门针对处理方法做调整,如下代码:

inbuffer[n] = 0;
std::string info = inbuffer;
std::string echo_string = "server echo# " + info;
//就是简单的字符串拼接,最后的echo_string就是处理完后最终形成的数据std::string echo_string = "server echo# " + info;

2.3 返回

当处理完数据后,紧接着就是最后一步辣,就是把结果返回给客户端,返回用到的socket API是sendto函数,下面来介绍下这个函数:

它的参数和recvfrom是一样的,这里就不过多介绍了

// 处理完后要再发送回对方
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&client, len); // 把数据发回给对方

三,客户端实现

3.1 UdpClient.cc 实现

在我们这个简单的Udp网络程序中,客户端的工作其实非常简单,就是“发送数据”,“接收数据”,“打印数据”,所以实现方面比服务器简单许多。

客户端一般是主动发数据给服务器的一方,所以客户端也要有相应的两个过程:

  • 创建套接字
  • 填写sockaddr结构体

问题:为什么客户端不需要我们手动绑定端口?

解答: 

  • 客户端都是最先发出请求的一方,所以服务器的IP地址和端口必须让客户端知道,因为服务器一旦启动,基本情况下不会关闭,所以端口号也不会更改了,所以服务器需要进行端口号绑定
  • 客户端是经常开启和关闭的,因此客户端的端口号是经常变化的,所以每次都绑定会加大成本,所以客户端的端口号只要能标识“唯一性”就可以了,只要客户端首次发送数据的时候,操作系统会自动帮我们绑定一个端口
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 构建服务器信息,因为客户端发给服务端需要知道服务端的ip和portstruct ThreadData td;bzero(&td.server, sizeof(td.server));td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport);                  // 转成网络序列td.server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 点分十进制的字符串转化为数字td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0){cout << "socket error" << endl;return 1;}string message;char buffer[1024];socklen_t len = sizeof(td.server);while (true){cout << "Please Enter@ ";getline(cin, message);// std::cout << message << std::endl;// 1. 数据 2. 给谁发sendto(td.sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&td.server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td.sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(td.sockfd); // 不用了就和关闭文件描述符一样关闭套接字return 0;
}

 3.2 Main.cc 实现

服务器的main函数所在的Main.cc如下:

#include "Udpserver.hpp"
#include "Log.hpp"
#include <memory>
#include <cstdio>
#include <vector>
#include <string>void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << "port[1024+]\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 2) // argc表示命令行中命令个数{Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]); // 字符串转整数std::unique_ptr<Udpserver> svr(new Udpserver(port));svr->Init(); // 初始化svr->Run(); // 服务器启动return 0;
}

3.3 效果展示

3.4 代码分层

 在实际开发场景中,其实很少会和 2.2 一样,直接把处理方法内置进服务器头文件中,所以我们可以在服务器启动前构建好处理函数,然后在服务器 Run 的时候,直接把方法传进去,实现代码分用,解耦,要更改的位置如下:

首先在Main.cc 的main函数前面加上处理方法:

// 代码分用
std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;std::string res = "Server get a message: ";res += info;std::cout << res << std::endl;return res;
}

然后就是利用C++的包装器,更改服务器的 Run 函数:

 加上包装器,也可以用typedef代替:

// using func_t = std::function<std::string(const std::string&, const std::string &, uint16_t)>; //c++11包装器
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;

 最后就是更改Run函数,代码如下:

void Run(func_t func)
{_isrunning = true;char inbuffer[size]; //while (_isrunning)   // 服务器应该能周而复始一直在运行的,所以是死循环{struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){Log("recvfrom error", Warning) << "\n";continue;}// 模拟群聊// ①拿到各客户端的ip和端口号uint16_t clientport = ntohs(client.sin_port);      // 拿到客户端的端口号,网络序列转主机序列std::string clientip = inet_ntoa(client.sin_addr); // 那搭配客户端ip地址,把inet_ntoa四字节ip转化为char*inbuffer[n] = 0;std::string info = inbuffer;// 充当了一次数据的处理,下面两条语句被第三条语句代替// std::string echo_string = "server echo# " + info;// std::cout << echo_string.c_str() << std::endl;std::string echo_string = func(info, clientip, clientport);// 处理完后要再发送回对方sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&client, len); // 把数据发回给对方}
}

 完成好后就可以直接实践了:

(可能各位有些混乱,如果遇到了不会了随时评论或私信哦~~,源代码在文章最后会附上 gitee 仓库链接) 

四,两种场景

4.1 发送部分命令给服务器并返回结果

上面的是最简单的客户端服务器的通讯结果,下面我们来搞一点好玩的

出了发送字符串,我们也可以发送部分命令给服务器,服务器处理好命令后再把结果返回给客户端,最后客户端打印

下面我们来更改一下代码,客户端代码不变,要变的就是服务器处理客户端信息的那部分代码,还是和上面代码分用一样,直接在Main.cc里实现处理函数,然后再传给Run函数

下面是Mani.cc 的处理命令的方法,用到的新函数是 popen,作用是直接执行命令并返回结果,有兴趣可以自行了解下:

Main.cc:

// 场景一,实现命令
bool SafeCheck(const std::string &cmd)
{std::vector<std::string> key_word = {"rm","mv","cp","kill","sudo","unlink","uninstall","yum","tcp","while"};for (auto &word : key_word){auto pos = cmd.find(word);if (pos != std::string::npos)return false; // 在你的命令中找到上面任意一个的话,就是不合法的,直接返回falsse}return true;
}
std::string ExcuteCommand(const std::string &cmd)
{std::cout << "get a request cmd: " << cmd << std::endl;if (!SafeCheck(cmd))return "Bad man";FILE *fp = popen(cmd.c_str(), "r");if (fp == NULL){perror("popen");return "error";}std::string result;char buffer[4096];while (true){char *ok = fgets(buffer, sizeof(buffer), fp);if (ok == nullptr)break; // 为空了,说明读完了result += buffer;}pclose(fp);return result;
}

 然后是UdpServer.hpp 的Run函数的更改:

先更改一下包装器函数的参数数量:

下面是效果展示:

 

4.2 实现Linux多终端窗口群聊

实现群聊我们要做下面几点工作:

  • 能够保存连接服务器的IP和端口,一个人发消息后遍历保存的IP和端口,把消息往所有连接服务器的IP都发送一次
  • 能够让群聊所有人知道是谁发的,也就是消息前要带上IP和端口
  • 利用dup2函数,实现两个窗口,一个窗口只负责发消息,一个窗口只负责收消息

先看效果演示: 

 有点复杂,但是不用担心,我们一步一步来

①首先是我们能够保存发起连接的用户的IP和端口

现在就要用到我们最开始就定义好的一个unordered_map了:

 然后我们在UdpServer.hpp里直接实现一个添加用户的函数:

void CheckUser(const struct sockaddr_in &client, const std::string clientip, uint16_t clientport) // 检查是否为新用户
{auto iter = _online_user.find(clientip); // 去哈希表去找对应IP的信息if (iter == _online_user.end())          // 如果上面这个查找的迭代器走到了结尾,说明哈希表里还没有这个ip,添加{_online_user.insert({clientip, client}); // 把ip和对应的套接字结构体插入std::cout << "[" << clientip << ": " << clientport << "]add to online user" << std::endl;}else // 如果存在了,则什么都不做{}
}

更改Run函数,先将Run函数参数去掉,因为群聊场景不需要传处理方法:

 ②遍历哈希表,对每一个IP都发送

上面的Run函数已经出现了,下面是实现Broadcast发送函数的代码:

void Broadcast(const std::string &info, const std::string clientip, uint16_t clientport)
{for (const auto &user : _online_user) // 遍历在线用户,遍历发送{// 编辑发送形式std::string message = "[";message += clientip;message += ":";message += std::to_string(clientport);message += "]#";message += info;socklen_t len = sizeof(user.second);sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)(&user.second), len); // sendto发送回给对方}
}

 ③最后就是客户端的调整了,我们可以使用dup2重定向函数,实现两个终端,一个终端窗口只发消息,一个窗口接收消息,就和上面的演示一样

 

#include <iostream>
#include <unistd.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>std::string terminal = "/dev/pts/2";int OpenTerminal()
{int fd = open(terminal.c_str(), O_WRONLY);if (fd < 0) // 打开失败{std::cerr << "open termial error";return 1;}dup2(fd, 2);// 测试//  printf("hello world\n"); // 把即将打印在当前终端的内容往特定路径的终端打// close(fd);return 0;
}

(可能有点复杂,如果看到这里的小伙伴有不懂或者有问题的,欢迎随时评论和私聊)

4.3 实现Windows做客户端,Linux做服务器群聊

如标题一样,Linux做服务器,Windows做客户端是非常常见的事情

我们可以在Windows本地实现一个客户端,然后和Linux服务器做通信,因为网络基础入门说过,虽然操作系统不一样,但是网络协议栈是一样的,因为这时规定,生产厂家必须遵守规则

下面是VS2022在Windows环境下的客户端代码:

#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#include <iostream>
#include<WinSock2.h> //这两个W开头的头文件顺序必须是这样,反过来编译时就会报错
#include<Windows.h>
#include <cstdlib>
#include <string>
#include<cstdio>
#pragma comment(lib, "ws2_32.lib")#include<thread>
uint16_t serverport = 8080;
std::string serverip = "58.87.91.241";struct ThreadData
{struct sockaddr_in server;SOCKET sockfd;std::string serverip;
};void* send_message(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);std::string message;std::cout << td->serverip << " coming... " << std::endl;while (true){std::cout << "Please Enter: ";std::getline(std::cin, message);sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&(td->server), sizeof(td->server));}
}
void* recv_message(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer)); //每次接收消息前清空缓冲区int len = sizeof(td->server);int s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr*)&(td->server), &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}
}int main()
{WSADATA wsd;WSAStartup(MAKEWORD(2, 3), &wsd);struct ThreadData td;// 构建服务器信息,因为客户端发给服务端需要知道服务端的ip和portmemset(&td.server, 0, sizeof(td.server));td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport);                  // 转成网络序列td.server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 点分十进制的字符串转化为数字td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0){std::cout << "socket error" << std::endl;return 1;}td.serverip = serverip;std::thread sender(send_message, &td);std::thread recver(recv_message, &td);sender.join();recver.join();WSACleanup();return 0;
}

代码和Linux差不多,也是多线程,只是Windows对于库的处理有点不一样,下面是效果演示:

两边打印中文时会乱码,其实是因为两边的编码不一致,我们暂时不考虑,反正能达到Windows和Linux实现网络通信的目的就行了莫

代码gitee链接:计算机网络/网络编程套接字/Udp · 小堃学编程/Linux学习 - 码云 - 开源中国 (gitee.com) 

这篇关于计算机网络(三) —— 简单Udp网络程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1139072

相关文章

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

Python 基于http.server模块实现简单http服务的代码举例

《Python基于http.server模块实现简单http服务的代码举例》Pythonhttp.server模块通过继承BaseHTTPRequestHandler处理HTTP请求,使用Threa... 目录测试环境代码实现相关介绍模块简介类及相关函数简介参考链接测试环境win11专业版python

基于Python编写自动化邮件发送程序(进阶版)

《基于Python编写自动化邮件发送程序(进阶版)》在数字化时代,自动化邮件发送功能已成为企业和个人提升工作效率的重要工具,本文将使用Python编写一个简单的自动化邮件发送程序,希望对大家有所帮助... 目录理解SMTP协议基础配置开发环境构建邮件发送函数核心逻辑实现完整发送流程添加附件支持功能实现htm

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

python连接sqlite3简单用法完整例子

《python连接sqlite3简单用法完整例子》SQLite3是一个内置的Python模块,可以通过Python的标准库轻松地使用,无需进行额外安装和配置,:本文主要介绍python连接sqli... 目录1. 连接到数据库2. 创建游标对象3. 创建表4. 插入数据5. 查询数据6. 更新数据7. 删除

Jenkins的安装与简单配置过程

《Jenkins的安装与简单配置过程》本文简述Jenkins在CentOS7.3上安装流程,包括Java环境配置、RPM包安装、修改JENKINS_HOME路径及权限、启动服务、插件安装与系统管理设置... 目录www.chinasem.cnJenkins安装访问并配置JenkinsJenkins配置邮件通知

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.

Linux之UDP和TCP报头管理方式

《Linux之UDP和TCP报头管理方式》文章系统讲解了传输层协议UDP与TCP的核心区别:UDP无连接、不可靠,适合实时传输(如视频),通过端口号标识应用;TCP有连接、可靠,通过确认应答、序号、窗... 目录一、关于端口号1.1 端口号的理解1.2 端口号范围的划分1.3 认识知名端口号1.4 一个进程