当前位置: 首页 > news >正文

德保县建设局的网站免费域名空间申请网址

德保县建设局的网站,免费域名空间申请网址,网站建设的要点是什么,廊坊网站关键词排名udp编程接口 一个UDP程序的编写可以分为3步#xff1a; 创建一个网络套接字#xff1a; 它相当于文件操作时的文件描述符#xff0c;是一个程序进行网络通讯的门户#xff0c; 所有的网络操作都要基于它 绑定IP和端口#xff1a; 需要为网络套接字填充IP和端口信息 但是…udp编程接口 一个UDP程序的编写可以分为3步 创建一个网络套接字 它相当于文件操作时的文件描述符是一个程序进行网络通讯的门户 所有的网络操作都要基于它 绑定IP和端口 需要为网络套接字填充IP和端口信息 但是有些时候无需手动填充让系统自动自动分配即可 发送和接收消息 发送消息需要指明对方的IP和端口号接收消息不需要直接从套接字拿就行 socket 申请一个套接字 套接字相当于一个文件描述符其中存放着IP、端口、网络协议等信息所有的网络操作都要基于这个网络套接字就像所有文件操作都要基于文件描述符一样 函数原型及参数解析 #include sys/socket.h #include sys/types.h int socket(int domain, int type, int protocol);domainsocket的域选择本地通讯或网络通信 AF_UNIX(AF_LOCAL)本地通讯 AF_INETIPv4协议网络通讯 AF_INET6IPv6协议网络通讯 type套接字的类型决定通信时对应的报文udp–用户数据报tcp–流式 SOCK_STREAM流式–tcp SOCK_DGRAM数据报格式无链接不可靠–udp protocol协议类型网络应用中一般用 0 返回值返回一个文件描述符 Example #include sys/socket.h #include sys/types.h int main() {int sockfd_ socket(AF_INET, SOCK_DGRAM, 0);if (sockfd_ 0){exit(1);} }bind 绑定网络信息 将网络信息写入网络套接字对应的内核区域 函数原型及参数解析 #include sys/socket.h #include sys/types.h #include netinet/in.h//struct sockaddr结构体定义 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfd网络套接字, 表示将网络信息绑定到这个套接字 addr要进行绑定的网络信息IP、端口号 我们要用一个结构体存储存储网络信息然后把结构体传入bind函数用于绑定 由于socket创建的套接字需要兼容本地、网络等多个域多个协议而这些协议需要绑定的信息也不尽相同对应描述信息的的结构体就不同如 对于网络信息的描述就要有IP、端口号、网络通讯协议下面的struct sockaddr_in结构体本地信息的描述就要有路径名和本地的各种通讯协议下面的struct sockaddr_un结构体 、 我们可以用一种多态的理念直接给bind函数传入两种类型结构体变量的首地址当函数内要获取网络信息的时候先读前16位知道当前要绑定信息的域和协议 进而再对后面的位进行特定化读取 这个addr参数完全可以用一个void*来接收两种不同的结构体指针但是由于一些历史原因当时还没有void*的语法 所以函数编写者新定义了一个结构体 struct sockaddr 用法也很简单只需要把struct sockaddr_in*或struct sockaddr_un*强转为 struct sockaddr*传入即可 bind函数内部会自动通过通过前16位判断要选择哪种数据类型的绑定 sockaddr 结构: /* Structure describing a generic socket address. */ struct sockaddr{__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */char sa_data[14]; /* Address data. */};sockaddr_in 结构: struct sockaddr_in {__SOCKADDR_COMMON (sin_); //16位地址类型此句相当于unsigned short sin_family;in_port_t sin_port; //端口号struct in_addr sin_addr; //IP地址/* Pad to size of struct sockaddr. */unsigned char sin_zero[8]; }; struct in_addr {unsigned short s_addr;//16位IP地址 };addrlen addr结构体变量的大小 Example: #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/types.h int main() {// 创建套接字int sockfd socket(AF_INET, SOCK_DGRAM, 0);if (sockfd_ 0){exit(1);}// 填充网络信息结构体string ip 127.0.0.1;uint16_t port 8080; struct sockaddr_in local;bzero(local, sizeof(local)); // 初始化为全零local.sin_family AF_INET; // 填充协议家族域与创建套接字时的domain相同local.sin_port htons(port); // 填充端口号信息local.sin_addr.s_addr ip.empty() ? htons(INADDR_ANY) : inet_addr(ip.c_str());// 填充IP信息// 绑定if (bind(sockfd, (sockaddr *)local, sizeof(local)) 0){exit(1);} }INADDR_ANY: 程序员一般不用关心bind到哪个ip INADDR_ANY的值为0传入的四字节IP如果是INADDR_ANY则表示让编译器自动选择IP进行绑定 一般指定填充一个确定的ip在有特殊用途或者测试时使用 云服务器上禁止bind的任何确定IP只能使用 INADDR_ANY recvfrom 从网络套接字中接收消息 函数原型及参数解析 #include sys/types.h #include sys/socket.hssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);sockfd网络套接字 buf读取到的目标缓冲区读取长度len flags等待消息的方式0–阻塞式等待 src_addr发送方的网络信息会被填入其中输出型参数 对方网络信息结构体的大小输入、输出型参数带入结构体大小用于说明要为src_addr结构体开辟空间的大小带出收到结构体大小 注意接收消息时无需告知发送方的地址此结构体无需填充消息会被发送方主动发送过来通过套接字直接拿取即可 返回值返回-1表示读取出错 Example #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/types.h int main() {int sockfd;//生成套接字并完成绑定//...//开始接收消息char inbuff[1024];struct sockaddr_in peer;//用于存放消息发送方的网络信息socklen_t len sizeof(peer);size_t s recvfrom(sockfd,inbuff,sizeof(inbuff)-1,0,(sockaddr *)peer, len);if (s 0){inbuff[s] 0;}else if (s -1){exit(1);}else;//读取成功,读到了对方的数据和网络地址【IP:port】string ip inet_ntoa(peer.sin_addr); //拿到对方的IPuint16_t port ntohs(peer.sin_port); //拿到对方的port//打印客户端发过来的消息和网络地址printf([%s:%d]# %s, ip.c_str(), port, inbuff);return 0; }这个程序的功能就是从套接字读取一串字符并打印到屏幕 sendto 发送一条消息 函数原型及参数解析 #include sys/types.h #include sys/socket.hssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);sockfd:网络套接字 buf:从目标缓冲区进行读取,读取长度len flags:等待方式,阻塞等待是0(向网络发送消息也要申请一定的资源,是资源就有可能申请不到,就需要提供等待的方式) dest_addr:发送目标的网络信息, 注意发送消息一定要通过此结构体为sendto()提供发送目标的网络信息 addrlen:dest_addr结构体的大小 Example int main(int argc, char const *argv[]) {//获取服务器IP端口string server_ip argv[1];uint16_t server_port atoi(argv[2]);//创建客户端int socketfd socket(AF_INET, SOCK_DGRAM, 0);struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(server_port);server.sin_addr.s_addr inet_addr(server_ip.c_str());string buffer;cout 请输入;getline(cin, buffer);//发送消息sendto(socketfd, buffer.c_str(), buffer.size(), 0,(struct sockaddr *)server, sizeof(server));return 0; } IP和port的格式转换 网络字节序–本地字节序 ​ 由于不同操作系统不同编译器有不同的字节序为了网络通信的方便网络字节序统一规定为大端 ​ 所有要进行网络传输的数据都要先转为网路字节序 ​ 从网络种接收到的数据也要先转为本地字节序 #include arpa/inet.huint32_t htonl(uint32_t hostlong); //32位数转为网络字节序uint16_t htons(uint16_t hostshort); //16位数转为网络字节序uint32_t ntohl(uint32_t netlong); //32位数转为本地字节序uint16_t ntohs(uint16_t netshort); //16位数转为本地字节序函数名解析 ​ h: host -- 本地 ​ n: network -- 网络 ​ l long -- 32位数 ​ sshort -- 16位数 点分十进制IP–四字节二进制IP 服务器的IP地址我们一般写为 xx.xxx.xx.xxx的点分十进制格式 但是这样的字符串实际不利于存储和计算机运算所有结构体中存储的IP地址要以位段的方式用一个4字节数表示 如下这些函数用于将点分十进制的IP地址和4字节IP互相转换 #include sys/socket.h #include netinet/in.h #include arpa/inet.h //点分十进制字符串--4字节IP int inet_aton(const char *cp, struct in_addr *inp); // 转换成功返回1失败返回0网络字节序ip自动写入in_addr结构体认为255.255.255.255是有效IP推荐使用 in_addr_t inet_addr(const char *cp); // 返回的4字节数是网络字节序认为255.255.255.255是无效IP返回-1 in_addr_t inet_network(const char *cp); // 返回的4字节数是本地字节序认为255.255.255.255是无效IP返回-1//4字节IP--点分十进制字符串 char *inet_ntoa(struct in_addr in);UDP聊天室编写 一个聊天室需要有服务器端和客户端 服务器端负责接收消息并将收得的消息发送给所有的已登陆用户 客户端负责发送消息同时接收服务器同步过来的消息 用户登陆方式为在客户端向服务器发送一条消息 如果用户长时间没有在聊天室发言将会被提出群聊 日志打印 log.hpp 用于日志信息的打印 #pragma once #include stdlib.h #include cassert #include cstdio #include ctime //日志等级 #define DEBUG 0 #define NOTICE 1 #define WARINING 2 #define FATAL 3const char* log_leval[] {DEBUG, NOTICE, WARINING, FATAL}; void logMssage(int level, const char* format, ...) {assert(level DEBUG);assert(level FATAL);const char* name getenv(USER);char logInfo[1024];va_list ap;va_start(ap, format); //让dp对应到可变部分...vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);va_end(ap); // ap NULLFILE* out (level FATAL) ? stderr : stdout;fprintf(out, %s | %u | %s | %s\n, log_leval[level],(unsigned)time(nullptr), name nullptr ? nukonw : name,logInfo); }服务器端 udpServer.cc #include arpa/inet.h #include cctype #include cerrno #include cstdlib #include cstring #include iostream #include netinet/in.h #include string #include sys/socket.h #include sys/types.h #include unistd.h #include unordered_map#include log.hpp using namespace std;class UdpServer {struct Client{struct sockaddr_in peer;time_t time; // 到time之后如果没有更新过就清除此用户};private:// 服务器的socket fd信息int sockfd_;// 服务器的端口号信息uint16_t port_;// 服务器IP地址点分十进制std::string ip_;// 在线用户std::unordered_mapstd::string, struct Client users_;// 超过此时间未响应将被踢出群聊(秒)const int tickOutTime_; public:UdpServer(int port, const string ip , int tickOutTime 1000): sockfd_(-1), // 初始化为-1如果init创建失败用-1表示失败port_(port),ip_(ip),tickOutTime_(tickOutTime){}~UdpServer() {}public:void init()//创建套接字并绑定{// 1.创建socked套接字sockfd_ socket(AF_INET, SOCK_DGRAM, 0); //相当于打开了一个文件if (sockfd_ 0){logMssage(FATAL, %s:%d, strerror(errno), sockfd_);exit(1);}logMssage(DEBUG, socket create success:%d, sockfd_);// 2. 绑定网络信息ipport// 2.1 先填充基本信息到 stckaddr_instruct sockaddr_in local;bzero(local, sizeof(local));//填充协议家族域local.sin_family AF_INET;//填充端口号信息(htons():转为网络字节序)local.sin_port htons(port_);//服务器都必须有IP地址xx.xxx.xx.xxx,// inet_addr():字符串风格点分十进制--4字节IP--uint32_t ip(位段方式),// 该函数会自动转网络字节序// INADDR_ANY(0):程序员不关心bind到哪个ip让编译器自动绑定// inet_addr指定填充一个确定的ip特殊用途或者测试时使用//禁止bind云服务器上的任何确定IP只能使用 INADDR_ANYlocal.sin_addr.s_addr ip_.empty() ? htons(INADDR_ANY) : inet_addr(ip_.c_str());// 2.2绑定if (bind(sockfd_, (sockaddr *)local, sizeof(local)) 0){logMssage(FATAL, %s:%d, strerror(errno), sockfd_);exit(2);}}void start(){while (true){// demo2char inbuff[1024];char outbuff[1024];struct sockaddr_in peer;socklen_t len sizeof(peer);size_t s recvfrom(sockfd_, inbuff, sizeof(inbuff) - 1, 0,(sockaddr *)peer, len);if (s 0){//当作字符串看待inbuff[s] \0; outbuff[s] \0;}else if (s -1){logMssage(WARINING, recvfrom:%s:%d, strerror(errno), sockfd_);continue;}//读取成功,读到了对方的数据和网络地址【IP:port】string peerIP inet_ntoa(peer.sin_addr);uint16_t peerPort ntohs(peer.sin_port); // 拿到对方的portcheckOnlineUser(peerIP, peerPort, {peer, (time_t)time(NULL) tickOutTime_}); // 如果用户不存在则添加用户存在则更新时间// 打印客户端发过来的消息和网络地址logMssage(NOTICE, [%s:%d]# %s, peerIP.c_str(), peerPort, inbuff);messageRoute(peerIP, peerPort, inbuff); // 消息路由将消息转发给除自己外的所有人}}private:// 如果用户不存在则添加用户存在则更新时间void checkOnlineUser(string IP, uint16_t port, const Client usr){std::string key IP;key :;key std::to_string(port);if (users_.count(key)){users_[key].time usr.time; // 更新时间}else{users_.insert({key, usr}); // 添加用户}}// 消息路由将消息转发给除自己外的所有人void messageRoute(string IP, uint16_t port, string message){std::string from IP;from :;from std::to_string(port);string out [ from ]: message;// 记录超时未相应退出的用户auto it users_.begin();while (it ! users_.end()){auto next it; // 防止当前节点删除导致迭代器失效next;if (it-first ! from) // 发给自己外的所有人{if (time(NULL) it-second.time){sendto(sockfd_, out.c_str(), out.size(), 0, (sockaddr *)it-second.peer, sizeof(it-second.peer));}else // 用户长时间没有动态将被踢出群聊{// 发送退出消息char exitMessage[] \1;sendto(sockfd_, exitMessage, strlen(exitMessage), 0, (sockaddr *)it-second.peer, sizeof(it-second.peer));auto next it;users_.erase(it);// exits.push_back(it);}}it next;}} }; static void Usage(const std::string porc) {std::cout Usage:\n\t porc port [IP] std::endl; } // 程序运行方式 // ./udpServer port IP int main(int argc, char const *argv[]) {//确保命令行参数使用正确if (argc ! 2 argc ! 3){Usage(argv[0]);exit(3);}//端口号一定要有uint16_t port atoi(argv[1]);//IP可以没有string ip;if (argc 3){ip argv[2];}//网络服务器UdpServer svr(port, ip);//配置服务器网络信息svr.init();//开始运行服务器svr.start();return 0; }客户端 udpClient.cc #include arpa/inet.h #include cstring #include iostream #include netinet/in.h #include pthread.h #include string #include sys/socket.h #include sys/types.h #include unistd.husing namespace std;// 用于接收消息和打印的线程 void *recvAndPrint(void *args) {char bufferIn[1024]; // 消息接收的缓冲区while (true){int sockfd *(int *)args;// 从服务器接收消息struct sockaddr_in temp;// temp无需填充作为输出型参数带出服务器网络信息socklen_t len sizeof(temp);// 接收消息不需要提供目的地址网络信息,消息会被目标主动发送到本地套接字size_t s recvfrom(sockfd, bufferIn, sizeof(bufferIn) - 1, 0, (struct sockaddr *)temp, len);if (s 0){bufferIn[s] 0;if (bufferIn[0] \1){cout \r长时间未响应你已退出群聊\n;exit(0);}cout \rserver echo# bufferIn endl;cout 请输入;}} }static void Usage(const std::string porc) {std::cout Usage:\n\t porc IP port std::endl; }int main(int argc, char const *argv[]) {// 必须有传入IP 和 端口号if (argc ! 3){Usage(argv[0]);exit(1);}// 1.创建客户端int sockfd socket(AF_INET, SOCK_DGRAM, 0);// 不需要手动绑定让系统自动为客户端分配IP和端口// 2.通讯过程// 2.1创建线程循环从网络套接字接收消息pthread_t t;pthread_create(t, NULL, recvAndPrint, sockfd);// 2.2发送消息// 配置服务器的网络信息——发送消息的目的地// 从命令行参数获取服务器IP端口string server_ip argv[1];uint16_t server_port atoi(argv[2]);// 填写服务器的网络信息struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(server_port);server.sin_addr.s_addr inet_addr(server_ip.c_str());// 发送内容的缓冲区string bufferOut;// 循环读取内容并发送给服务器while (true){cout 请输入;getline(cin, bufferOut);//发送消息给serversendto(sockfd, bufferOut.c_str(), bufferOut.size(), 0,(struct sockaddr *)server, sizeof(server));}return 0; }makefile .PHONY:all all:udpClient udpServerudpClient:udpClient.ccg -o $ $^ -stdc11 -lpthread udpServer:udpServer.ccg -o $ $^ -stdc11.PHONY:clean clean:rm -f udpClient udpServertcp编程接口 tcp编程的前两步也依然是 创建socket套接字绑定网络信息 与udp的区别在于 创建socket的时候type选择SOCK_STREAM的流式套接字 如下为tcp独有的部分 设置为监听状态listen 此步骤不一定要进行只有被连接一方服务器需要设为监听 获取连接(accept)/发起连接(connect) 一般由客户端发起连接(客户端知道服务器的IP和port)服务器获取连接 进行发消息(write)和读消息(read) 一般程序分为服务器端和客户端编写步骤如下 服务器端 #mermaid-svg-oqT7YKcfbdMqLSmu {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oqT7YKcfbdMqLSmu .error-icon{fill:#552222;}#mermaid-svg-oqT7YKcfbdMqLSmu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oqT7YKcfbdMqLSmu .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oqT7YKcfbdMqLSmu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oqT7YKcfbdMqLSmu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oqT7YKcfbdMqLSmu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oqT7YKcfbdMqLSmu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oqT7YKcfbdMqLSmu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oqT7YKcfbdMqLSmu .marker.cross{stroke:#333333;}#mermaid-svg-oqT7YKcfbdMqLSmu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oqT7YKcfbdMqLSmu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oqT7YKcfbdMqLSmu .cluster-label text{fill:#333;}#mermaid-svg-oqT7YKcfbdMqLSmu .cluster-label span{color:#333;}#mermaid-svg-oqT7YKcfbdMqLSmu .label text,#mermaid-svg-oqT7YKcfbdMqLSmu span{fill:#333;color:#333;}#mermaid-svg-oqT7YKcfbdMqLSmu .node rect,#mermaid-svg-oqT7YKcfbdMqLSmu .node circle,#mermaid-svg-oqT7YKcfbdMqLSmu .node ellipse,#mermaid-svg-oqT7YKcfbdMqLSmu .node polygon,#mermaid-svg-oqT7YKcfbdMqLSmu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oqT7YKcfbdMqLSmu .node .label{text-align:center;}#mermaid-svg-oqT7YKcfbdMqLSmu .node.clickable{cursor:pointer;}#mermaid-svg-oqT7YKcfbdMqLSmu .arrowheadPath{fill:#333333;}#mermaid-svg-oqT7YKcfbdMqLSmu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oqT7YKcfbdMqLSmu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oqT7YKcfbdMqLSmu .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-oqT7YKcfbdMqLSmu .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-oqT7YKcfbdMqLSmu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oqT7YKcfbdMqLSmu .cluster text{fill:#333;}#mermaid-svg-oqT7YKcfbdMqLSmu .cluster span{color:#333;}#mermaid-svg-oqT7YKcfbdMqLSmu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oqT7YKcfbdMqLSmu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}创建套接字(socket)绑定服务器信息(bind)设置监听状态(listen)获取连接(accept)发送/接收消息(write/read) 客户端 #mermaid-svg-oYaeh2TBfm85CMPV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oYaeh2TBfm85CMPV .error-icon{fill:#552222;}#mermaid-svg-oYaeh2TBfm85CMPV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oYaeh2TBfm85CMPV .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oYaeh2TBfm85CMPV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oYaeh2TBfm85CMPV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oYaeh2TBfm85CMPV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oYaeh2TBfm85CMPV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oYaeh2TBfm85CMPV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oYaeh2TBfm85CMPV .marker.cross{stroke:#333333;}#mermaid-svg-oYaeh2TBfm85CMPV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oYaeh2TBfm85CMPV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oYaeh2TBfm85CMPV .cluster-label text{fill:#333;}#mermaid-svg-oYaeh2TBfm85CMPV .cluster-label span{color:#333;}#mermaid-svg-oYaeh2TBfm85CMPV .label text,#mermaid-svg-oYaeh2TBfm85CMPV span{fill:#333;color:#333;}#mermaid-svg-oYaeh2TBfm85CMPV .node rect,#mermaid-svg-oYaeh2TBfm85CMPV .node circle,#mermaid-svg-oYaeh2TBfm85CMPV .node ellipse,#mermaid-svg-oYaeh2TBfm85CMPV .node polygon,#mermaid-svg-oYaeh2TBfm85CMPV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oYaeh2TBfm85CMPV .node .label{text-align:center;}#mermaid-svg-oYaeh2TBfm85CMPV .node.clickable{cursor:pointer;}#mermaid-svg-oYaeh2TBfm85CMPV .arrowheadPath{fill:#333333;}#mermaid-svg-oYaeh2TBfm85CMPV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oYaeh2TBfm85CMPV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oYaeh2TBfm85CMPV .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-oYaeh2TBfm85CMPV .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-oYaeh2TBfm85CMPV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oYaeh2TBfm85CMPV .cluster text{fill:#333;}#mermaid-svg-oYaeh2TBfm85CMPV .cluster span{color:#333;}#mermaid-svg-oYaeh2TBfm85CMPV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oYaeh2TBfm85CMPV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}创建套接字(socket)发起连接请求(accept)发送/接收消息(write/read) listen 将socket套接字设为监听状态 因为tcp是面向面向连接的所以要把当前套接字设为可连接的状态 函数原型及参数解析 #include sys/types.h #include sys/socket.hint listen(int sockfd, int backlog);sockfd网络套接字backlog完成lisen后当对端向本地发起connect操作系统会自动完成连接此连接被放入一个队列当本地调用accept就会从此队列中拿取连接状态backlog即表示对列的最大容量当超过此容量操作系统即不会与对端进行连接后续TCP原理时会详细讲解返回值成功返回0失败返回-1同时设置errno example 如下是从创建套接字到设置监听状态的三个步骤 int main() {// 创建套接字int listenSock_ socket(AF_INET, SOCK_STREAM, 0);if (listenSock_ 0){exit(1);}// bind// 2.1填充服务器信息uint16_t port_ 8080;string ip_;struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family PF_INET;local.sin_port htons(port_);ip_.empty() ? (local.sin_addr.s_addr INADDR_ANY) : (inet_aton(ip_.c_str(), local.sin_addr));// 2.2绑定if (bind(listenSock_, (const struct sockaddr *)local, sizeof(local)) -1){exit(2);}// 3.监听socket因为tcp是面向连接的所以要把自己设为可连接的状态if (listen(listenSock_, 5) 0){exit(3);} }accept 让处于监听状态的套接字获取连接此时如果对端发起connect即可完成连接。 函数原型及参数解析 #include sys/types.h #include sys/socket.h int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd监听套接字这个socket绑定了IP和port且设置了监听状态通过它即可获取远端发起的连接addr发起连接一方的网络信息会被填入其中输出型参数addrlen传入addr对应结构体的大小对端返回的结构体大小会被这个参数带出输入输出型参数返回值返回一个新的文件描述符套接字后续的读写操作都要通过这个文件描述符进行 两个套接字的区别 传入的套接字是监听套接字一般只有一个这个套接字设置了监听状态专门用于accept各个客户端发来的connect请求让本地和对端建立连接返回的套接字可以认为是建立连接后和远端对话的接口如果有N个客户端和本地建立连接则有N的服务套接字生成 example 如下代码续接listen的example struct sockaddr_in peer; socklen_t size sizeof(peer); int serviceSock accept(listenSock_, (struct sockaddr *)peer, len); if (serviceSock 0) {// 获取连接失败cerr accept error; }connect 向服务器发起连接 函数原型及参数解析 #include sys/types.h #include sys/socket.hint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);一般由客户端发起连接创建套接字之后无需手动绑定无需设置监听状态直接向远端发起connect即可和远端服务器建立连接在此过程系统会自动为这个套接字绑定IP和端口同时自己的网络信息也会被发送到远端。 sockfd网络套接字addr对端的网络信息结构体需要提前创建此结构体并填充IPport协议等信息addr对应结构体的大小返回值成功返回0失败返回-1并设置errno example int main() {// 1.创建客户端套接字int sockfd socket(AF_INET, SOCK_STREAM, 0);// ?2.不需要手动绑定让系统自动为客户端分配IP和端口// ?3.不需要listen// 2.connect向服务器发起连接请求std::string server_ip 127.0.0.1;uint16_t server_port atoi(8080);// 2.1 先填充远端的主机信息struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET; // 协议server.sin_port htons(server_port); // portinet_aton(server_ip.c_str(), server.sin_addr); // ip// 2.2发起请求,connect会自动选择合适的IP和port进行bindif (connect(sockfd, (const struct sockaddr *)server, sizeof(server)) ! 0){std::cerr connect: strerror(errno);exit(CONN_ERR);} }read 接收网络消息与文件读取用的是同一个函数 函数原型及参数解析 #include unistd.hssize_t read(int fd, void *buf, size_t count);fd如果是读取本地的文件fd是open()打开文件后生成的文件描述符如果是接收网络消息fd则是网络套接字注意这个套接字不能是监听套接字可以是客户端没有设置过listen的套接字也可以是服务器端accept返回的套接字buf缓冲区读到的信息存于此处count表示要读取的字节数return读到的字节数对端退出返回0读取失败返回-1 example 如下是server端代码续接accept的example int main() {// 1.创建套接字// 2.绑定// 3.设置监听状态// 4.获取连接// 5.读取对端发来的消息char inbuffer[BUFFER_SIZE];ssize_t s read(serviceSock, inbuffer, sizeof(inbuffer) - 1);if (s 0) // 读到的字节数{// read成功inbuffer[s] \0;std::cout inbuffer std::endl;} }write 向网络发送消息和写文件使用同一个API 函数原型及参数解析 #include unistd.hssize_t write(int fd, const void *buf, size_t count);fd网络套接字用法对标readbuf缓冲区将缓冲区的内容发出count需要发送的字节数返回值读到的字节数对端退出返回0发送失败返回-1 example 续接read的example int main() {// 1.创建套接字// 2.绑定// 3.设置监听状态// 4.获取连接// 5.读取对端发来的消息char inbuffer[BUFFER_SIZE];ssize_t s read(serviceSock, inbuffer, sizeof(inbuffer) - 1);if (s 0) // 读到的字节数{// read成功inbuffer[s] \0;for (auto e : inbuffer){e toupper(e);}write(listenSock_, inbuffer, s);} }将接收到的所有字符转为大写并发回 Tcp程序编写—字符转大写 如下是分别是客户端和服务器程序的源代码服务器会将客户端发送过来的所有消息转为大写后发回,服务意义不大旨在理解Tcp套接字的使用 工具包 util.h #pragma once #include log.hpp #include arpa/inet.h #include cctype #include cstdlib #include cstring #include iostream #include netinet/in.h #include signal.h #include string #include sys/socket.h #include sys/types.h #include sys/wait.h #include unistd.h #define SOCKET_ERR 1 #define BIND_ERR 2 #define LISTEN_ERR 3 #define USAGE_ERR 4 #define CONN_ERR 5 #define BUFFER_SIZE 1024服务器端 tcpServer.cc #include util.hpp class TcpServer {struct ThreadData{int sock_;uint16_t clientPort_;std::string clientIp_;TcpServer *this_;};private:// sockint listenSock_;// portuint16_t port_;// ipstd::string ip_;public:TcpServer(uint16_t port, std::string ip ): listenSock_(-1),port_(port),ip_(ip){}void init(){// 创建套接字listenSock_ socket(AF_INET, SOCK_STREAM, 0);if (listenSock_ 0){logMssage(FATAL, socket:%s, strerror(errno));exit(SOCKET_ERR);}logMssage(DEBUG, socket:%s,%d, strerror(errno), listenSock_);// bind// 2.1填充服务器信息struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family PF_INET;local.sin_port htons(port_);ip_.empty() ? (local.sin_addr.s_addr INADDR_ANY) : (inet_aton(ip_.c_str(), local.sin_addr));// 2.2绑定if (bind(listenSock_, (const struct sockaddr *)local, sizeof(local)) -1){logMssage(FATAL, bind:%s, strerror(errno));exit(BIND_ERR);}logMssage(DEBUG, bind:%s,%d, strerror(errno), listenSock_);// 3.监听socket因为tcp是面向连接的所以要把自己设为可连接的状态if (listen(listenSock_, 5) 0){logMssage(FATAL, listen:%s, strerror(errno));exit(LISTEN_ERR);}logMssage(DEBUG, listen:%s,%d, strerror(errno), listenSock_);}void loop(){while (true){// 4.获取连接struct sockaddr_in peer;socklen_t len sizeof(peer);int serviceSock accept(listenSock_, (struct sockaddr *)peer, len);if (serviceSock 0){// 获取连接失败logMssage(WARINING, accept:%s[%d], strerror(errno), serviceSock);continue;}// 获取客户端的基本信息uint16_t peerPort ntohs(peer.sin_port);std::string peerIp inet_ntoa(peer.sin_addr);logMssage(DEBUG, addept:%s | %s[%d], strerror(errno), peerIp.c_str(), peerPort);// 5.提供服务,小写--大写 #if 0// 5.1 v1版本单进程版本一旦进入无法向后执行,同一时间只能为一个用户提供服务transService(serviceSock, peerIp, peerPort); #elif 0// 5.2 v2.1版本多进程每个用户占据一个子进程signal(SIGCHLD, SIG_IGN);pid_t id fork();assert(id ! -1);if (id 0){// 子进车close(listenSock_);transService(serviceSock, peerIp, peerPort);exit(0); // 进入僵尸}// 父进程close(serviceSock); // 子进程关不了父进程的// 可以非阻塞式等待但比较复杂// 可以signal忽略SIGCHILD信号 #elif 0// 5.2 v2.2版本多进程创造孤儿进程无需忽略SIGCHILDpid_t id fork();assert(id ! -1);if (id 0){close(listenSock_);// 子进车if (fork() 0)exit(0); // 退出子进程// 孙子进程成为孤儿进程由系统领养--回收问题由系统解决// 让孙子进程完成任务transService(serviceSock, peerIp, peerPort);exit(0); // 孙子进程退出}// 父进程close(serviceSock); // 孙子进程关不了父进程的pid_t ret waitpid(id, nullptr, 0); // 回收子进程assert(ret 0); #else// 5.3 v3 多线程版本// 为线程提供的网络信息ThreadData *threadData new ThreadData();threadData-clientIp_ peerIp;threadData-clientPort_ peerPort;threadData-sock_ serviceSock;threadData-this_ this;pthread_t tid;if (pthread_create(tid, NULL, threadRoutine, threadData) 0){logMssage(WARINING, pthread_create:%s, strerror(errno));} #endif// debug// logMssage(DEBUG, server 开始提供服务...);// sleep(1);}}static void *threadRoutine(void *args){pthread_detach(pthread_self()); // 设置线程分离无需主线程joinThreadData *td static_castThreadData *(args);td-this_-transService(td-sock_, td-clientIp_, td-clientPort_);delete td;}// 将所有的的字母转为大写void transService(int sock, const std::string clientIP, uint16_t clientPort){assert(sock 0);assert(!clientIP.empty());assert(clientPort 1024);char inbuffer[BUFFER_SIZE];while (true){ssize_t s read(sock, inbuffer, sizeof(inbuffer) - 1);if (s 0) // 读到的字节数{// read成功inbuffer[s] \0;if (strcasecmp(inbuffer, quit) 0){logMssage(DEBUG, client quit -- %s[%d], clientIP.c_str(), clientPort);break;}// 进行大小写转化logMssage(DEBUG, trans befor: %s, inbuffer);for (int i 0; i s; i){if (isalpha(inbuffer[i]) islower(inbuffer[i])){inbuffer[i] toupper(inbuffer[i]);}}logMssage(DEBUG, trans after: %s, inbuffer);write(sock, inbuffer, strlen(inbuffer));}else if (s 0){// 代表对方关闭client退出logMssage(DEBUG, client quit -- %s[%d], clientIP.c_str(), clientPort);break;}else{// 读取出错logMssage(WARINING, %s[%d] -- read:%s, clientIP.c_str(), clientPort, strerror(errno));break;}}// client退出服务到此结束close(sock); // 如果一个进程对应的文件fd打开了没有归还会造成文件描述符溢出logMssage(DEBUG, server close %d done, sock);} };static void Usage(const std::string porc) {std::cout Usage:\n\t porc port [IP] std::endl;std::cout example:\n\t porc 8080 127.0.0.1 std::endl; } int main(int argc, char *argv[]) {if (argc ! 2 argc ! 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);std::string ip;if (argc 3){ip argv[2];}TcpServer srv(port, ip);srv.init();srv.loop();return 0; }客户端 tcpClient.cc #include util.hpp static void Usage(const std::string porc) {std::cout Usage:\n\t porc IP port std::endl; } volatile static bool quit false; int main(int argc, char const *argv[]) {// 必须有传入IP 和 端口号if (argc ! 3){Usage(argv[0]);exit(1);}// 1.创建客户端int sockfd socket(AF_INET, SOCK_STREAM, 0);// ?2.不需要手动绑定让系统自动为客户端分配IP和端口// ?3.不需要listen// ?4.不需要accept// 2.connect向服务器发起连接请求// 从命令行参数获取服务器IP端口std::string server_ip argv[1];uint16_t server_port atoi(argv[2]);// 2.1 先填充远端的主机信息struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET; // 协议server.sin_port htons(server_port); // portinet_aton(server_ip.c_str(), server.sin_addr); // ip// 2.2发起请求,connect会自动选择合适的IP和port进行bindif (connect(sockfd, (const struct sockaddr *)server, sizeof(server)) ! 0){std::cerr connect: strerror(errno);exit(CONN_ERR);}std::cout info: connect success sockfd std::endl;while (!quit){std::string message;std::cout 请输入;std::getline(std::cin, message);if (strcasecmp(message.c_str(), quit) 0){quit true;}ssize_t s write(sockfd, message.c_str(), message.size());if (s 0){message.resize(1024);ssize_t s read(sockfd, (char *)(message.c_str()), 1024);std::cout Server Echo message std::endl;}else if (s 0){break;}}close(sockfd);return 0; }TCP服务器线程池版本 上面的字符转换服务器我们分别尝试了单执行流、多进程、多线程的版本 单执行流同一时间只能对一个客户端进行服务只有该客户端退出才能对下一个客户端进行服务 多线程和多进程的版本使用n个线程或进程同时对n个客户进行服务 多线程因为粒度更低调用成本相对较低 但是它们都是在完成网络连接之后再为客户端现场新建一个线程/进程 我们不妨使用一个线程池让服务器刚启动的时候就创建一些线程一旦连接成功直接可以交给线程池执行服务 为了提高趣味性我们再改一下服务器提供的服务使用popen()这个系统调用让客户端可以向服务器发送一些命令让服务器执行同时返回执行结果如客户端发送ls指令服务器端便会发回它当前目录的文件 tcpServer.cc//tcp服务器源代码 #include util.hpp class TcpServer { private:// sockint listenSock_;// portuint16_t port_;// ipstd::string ip_;ThreadPoolTask *tp_;public:TcpServer(uint16_t port, std::string ip ): listenSock_(-1),port_(port),ip_(ip),tp_(nullptr){}void init(){// 创建套接字listenSock_ socket(AF_INET, SOCK_STREAM, 0);if (listenSock_ 0){logMssage(FATAL, socket:%s, strerror(errno));exit(SOCKET_ERR);}logMssage(DEBUG, socket:%s,%d, strerror(errno), listenSock_);// bind// 2.1填充服务器信息struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family PF_INET;local.sin_port htons(port_);ip_.empty() ? (local.sin_addr.s_addr INADDR_ANY) : (inet_aton(ip_.c_str(), local.sin_addr));// 2.2绑定if (bind(listenSock_, (const struct sockaddr *)local, sizeof(local)) -1){logMssage(FATAL, bind:%s, strerror(errno));exit(BIND_ERR);}logMssage(DEBUG, bind:%s,%d, strerror(errno), listenSock_);// 3.监听socket因为tcp是面向连接的所以要把自己设为可连接的状态if (listen(listenSock_, 5) 0){logMssage(FATAL, listen:%s, strerror(errno));exit(LISTEN_ERR);}logMssage(DEBUG, listen:%s,%d, strerror(errno), listenSock_);// 加载线程池tp_ ThreadPoolTask::getInstance();}void loop(){tp_-start(); // 启动线程池logMssage(DEBUG, thread pool start success, thread num:%d, tp_-threadNum());while (true){// 4.获取连接struct sockaddr_in peer;socklen_t len sizeof(peer);int serviceSock accept(listenSock_, (struct sockaddr *)peer, len);if (serviceSock 0){// 获取连接失败logMssage(WARINING, accept:%s[%d], strerror(errno), serviceSock);continue;}// 获取客户端的基本信息uint16_t peerPort ntohs(peer.sin_port);std::string peerIp inet_ntoa(peer.sin_addr);logMssage(DEBUG, addept:%s | %s[%d], strerror(errno), peerIp.c_str(), peerPort);// 5.提供服务,线程池版本 Task t(serviceSock, peerIp, peerPort, std::bind(TcpServer::execCommand, this, placeholders::_1, placeholders::_2, placeholders::_3));// bind: (this,sock,ip,port)--(sock,ip,port)// C11语法详见包装器一文tp_-push(t); // 传入任务}}void execCommand(int sock, const std::string clientIP, uint16_t clientPort)//调用popen完成对端发来的指令循环接收直到客户退出断开连接{assert(sock 0);assert(!clientIP.empty());assert(clientPort 1024);char command[BUFFER_SIZE];while (true){ssize_t s read(sock, command, sizeof(command) - 1);if (s 0) // 读到的字节数{command[s] \0;logMssage(DEBUG, [%s:%d] exec [%s], clientIP.c_str(), clientPort, command);FILE *fp popen(command, r);if (fp nullptr){logMssage(WARINING, exec %s failed, beacuse:%s, command, strerror((errno)));break;}// dup2(sock, fp-_fileno);//错误注意区分文件读和写缓冲区// fflush(fp);char line[1024];while (fgets(line, sizeof(line), fp) ! nullptr){write(sock, line, strlen(line));}pclose(fp);logMssage(DEBUG, [%s:%d] exec [%s] ... done, clientIP.c_str(), clientPort, command);}else if (s 0){// 代表对方关闭client退出logMssage(DEBUG, client quit -- %s[%d], clientIP.c_str(), clientPort);break;}else{// 读取出错logMssage(WARINING, %s[%d] -- read:%s, clientIP.c_str(), clientPort, strerror(errno));break;}}// client退出服务到此结束close(sock); // 如果一个进程对应的文件fd打开了没有归还会造成文件描述符溢出logMssage(DEBUG, server close %d done, sock);} };static void Usage(const std::string porc) {std::cout Usage:\n\t porc port [IP] std::endl;std::cout example:\n\t porc 8080 127.0.0.1 std::endl; } int main(int argc, char *argv[]) {if (argc ! 2 argc ! 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);std::string ip;if (argc 3){ip argv[2];}TcpServer srv(port, ip);srv.init();srv.loop();return 0; }threadPool.hpp//具体线程池的编写可以看线程控制一文 #pragma once #include Lock.hpp #include assert.h #include iostream #include pthread.h #include queue #include stdlib.h #include sys/prctl.h //更改线程名便于调试查看 #include unistd.h using namespace std;const int gThreadNum 5; template class T class ThreadPool { private:bool isStart; // 判断防止当前线程池多次被启动int threadNum_; // 线程的数量queueT taskQueue_; // 任务队列pthread_mutex_t mutex_; // 保证访问任务队列是原子的pthread_cond_t cond_; // 如果当前任务队列为空让线程等待被唤醒bool quit_;static ThreadPoolT *instance_; // 设计成单例模式public:static ThreadPoolT *getInstance(){static Mutex mutex;if (nullptr instance_) // 仅仅过滤重复的判断{Lock_Guard lockGuard(mutex); // 保护后面的内容if (nullptr instance_){instance_ new ThreadPoolT();}}return instance_;}~ThreadPool(){pthread_mutex_destroy(mutex_);pthread_cond_destroy(cond_);}public:void start() // 创建多个线程让它们等待被唤醒执行push的任务{assert(isStart false);isStart true;for (int i 0; i threadNum_; i){pthread_t tmp;pthread_create(tmp, nullptr, threadRoutine, this);}}void quit() // 关闭线程池时确保所有任务都完成了{while (haveTask()){pthread_cond_broadcast(cond_);// usleep(1000);// cout taskQueue_.size() endl;}quit_ true;}void push(const T in) // 在线程池中添加任务{lockQueue();taskQueue_.push(in);choiceThreadForHandl();unlockQueue();}int threadNum(){return threadNum_;}private:ThreadPool(int threadNum gThreadNum){threadNum_ threadNum;assert(threadNum 0);isStart false;quit_ false;pthread_mutex_init(mutex_, nullptr);pthread_cond_init(cond_, nullptr);}ThreadPool(const ThreadPoolT ) delete; // 单例防拷贝ThreadPool operator(const ThreadPoolT ) delete; // 同上static void *threadRoutine(void *args){prctl(PR_SET_NAME, follower);pthread_detach(pthread_self());ThreadPoolT *tp static_castThreadPoolT *(args);while (true) // 循环从任务队列中拿出任务并执行队列为空则等待任务出现{tp-lockQueue();while (!tp-haveTask()) // 如果任务队列为空则等待{if (tp-quit_) // 当调用quit且队列已经为空的时候quit_才会被置为true{cout quit endl;return nullptr;}tp-waitForTask();}// 将任务从队列中拿到出来执行T t tp-pop();tp-unlockQueue();t();// 规定所有任务内都有一个自己的run方法}return nullptr;}void lockQueue() // 加锁{pthread_mutex_lock(mutex_);}void unlockQueue() // 解锁{pthread_mutex_unlock(mutex_);}void waitForTask() // 让线程等待被唤醒{pthread_cond_wait(cond_, mutex_);}bool haveTask() // 队列不为空{return !taskQueue_.empty();}void choiceThreadForHandl() // 随便唤醒一个等待的线程{pthread_cond_signal(cond_);}T pop() // 从队列中拿取一个任务{T tmp taskQueue_.front();taskQueue_.pop();return tmp;} }; template class T ThreadPoolT *ThreadPoolT::instance_ nullptr; // 单例Task.hpp//提供任务类可使用回调的方法给线程池传入任务 #pragma once #include log.hpp #include functional #include iostream #include string class Task {using callback_t std::functionvoid(int, std::string, uint16_t);// 等价于std::functionvoid(int, std::string, uint16_t) callback_t; private:int sock_;std::string ip_;uint16_t port_;callback_t func_; public:Task() : sock_(-1), port_(-1) {}Task(int sock, std::string ip, uint16_t port, callback_t func): sock_(sock), ip_(ip), port_(port), func_(func){}void operator()(){logMssage(DEBUG, 线程ID[%p]处理%s:%d的请求开始了..., pthread_self(), ip_.c_str(), port_);func_(sock_, ip_, port_);logMssage(DEBUG, 线程ID[%p]处理%s:%d的请求完成了..., pthread_self(), ip_.c_str(), port_);} };Lock.hpp//封装了互斥锁、设计了RAII的LockGard如果熟悉C线程库可以直接使用C线程库 #pragma once #include iostream #include pthread.hclass Mutex { private:pthread_mutex_t lock_;public:Mutex(){pthread_mutex_init(lock_, nullptr);}~Mutex(){pthread_mutex_destroy(lock_);}void lock(){pthread_mutex_lock(lock_);}void unlock(){pthread_mutex_unlock(lock_);} };class Lock_Guard { private:Mutex *mutex_;public:Lock_Guard(Mutex *mutex): mutex_(mutex){mutex_-lock();}~Lock_Guard(){mutex_-unlock();} };log.hpp//提供日志函数方便打印详细的日志信息 #pragma once#include stdlib.h#include cassert #include cstdarg #include cstdio #include ctime //日志等级 #define DEBUG 0 #define NOTICE 1 #define WARINING 2 #define FATAL 3const char* log_leval[] {DEBUG, NOTICE, WARINING, FATAL}; void logMssage(int level, const char* format, ...) {assert(level DEBUG);assert(level FATAL);const char* name getenv(USER);char logInfo[1024];va_list ap;va_start(ap, format); //让dp对应到可变部分...vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);va_end(ap); // ap NULLFILE* out (level FATAL) ? stderr : stdout;fprintf(out, %s | %u | %s | %s\n, log_leval[level],(unsigned)time(NULL), name NULL ? nukonw : name,logInfo); }util.hpp//工具包包含了所有要包含的头文件和一些宏定义 #pragma once #include Lock.hpp #include Task.hpp #include ThreadPool.hpp #include log.hpp #include arpa/inet.h #include cctype #include cstdlib #include cstring #include iostream #include netinet/in.h #include signal.h #include string #include sys/socket.h #include sys/types.h #include sys/wait.h #include unistd.h #define SOCKET_ERR 1 #define BIND_ERR 2 #define LISTEN_ERR 3 #define USAGE_ERR 4 #define CONN_ERR 5 #define BUFFER_SIZE 1024将服务器端“守护进程”化 一般服务器进程都是以守护进程的形式出现的具体守护进程的概念见《[进程概念](#(572条消息) 进程概念Linux_Massachusetts_11的博客-CSDN博客)》-守护进程一旦启动之后除非用户主动关闭否则一直会在运行 setid()可以更改当前进程的会话ID 但是调用此函数的进程不能是一个进程的组长 所以一般我们需要fork()一个子进程让子进程setsid父进程可以直接exit(); if(fork() 0) exit(0); setsid(1);除了守护进程化一般服务器程序还要进行一些选做内容 忽略SIGPIPE信号 如果server端在write时Client已经退出则server端也会被SIGPIPE信号终止所以我们要忽略此信号 更改进程的工作目录 chdir();//《进程控制》一文可以看到 删除/修改012号文件描述符 因为一般服务器端不会在标准输入输出流进行输入输出 所以我们可以将012号文件描述符关掉但是很少有人这么做 在Linux下有一个“垃圾桶”或者说“文件黑洞” 凡是写入/dev/null中的数据一概会被丢弃从中读取也是空 所以我们可以打开 /dev/null,并且对012进行重定向 或者也可以创建一个日志文件将产生的日志信息存储到文件中去 daemaonize.hpp #pragma once#include cstdio #include fcntl.h #include iostream #include signal.h #include sys/stat.h #include sys/types.h #include unistd.h void daemonize() // 将程序守护进程化 {// 1. 忽略SIGPIPEsignal(SIGPIPE, SIG_IGN);// 2. 更改进程的工作目录// chdir();// 3. 让自己不要成为进程组组长if (fork() 0)exit(0);// 4.设置自己时一个独立的会话setsid();// 5. 重定向012int fd 0;if ((fd open(/dev/null, O_RDWR) ! -1)){dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);// 关闭掉不需要的fdif (fd STDERR_FILENO)close(fd);} }log.hpp #pragma once#include cassert #include cstdarg #include cstdio #include ctime #include fcntl.h #include stdlib.h #include sys/stat.h #include sys/types.h // 日志等级 #define DEBUG 0 #define NOTICE 1 #define WARINING 2 #define FATAL 3 #define LOGFILE tcpServer.logconst char* log_leval[] {DEBUG, NOTICE, WARINING, FATAL}; void logMssage(int level, const char* format, ...) {assert(level DEBUG);assert(level FATAL);const char* name getenv(USER);char logInfo[1024];va_list ap;va_start(ap, format); //让dp对应到可变部分...vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);va_end(ap); // ap NULLint fd open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);assert(fd 0);FILE *out (level FATAL) ? stderr : stdout;dup2(fd, 1);dup2(fd, 2);fprintf(out, %s | %u | %s | %s\n, log_leval[level],(unsigned)time(NULL), name NULL ? nukonw : name,logInfo);fflush(out); // 将C缓冲区的数据刷新到OSfsync(fd); // 将OS中的数据尽快刷盘 }只需要在服务器端的main函数调用daemonize()即可完成守护进程化 tcpServer.cc int main(int argc, char *argv[]) {if (argc ! 2 argc ! 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);std::string ip;if (argc 3){ip argv[2];}daemonize(); // 我们的进程将成为守护进程TcpServer srv(port, ip);srv.init();srv.loop();return 0; }一般守护进程化的程序结尾带一个d makefile .PHONY:all all:tcpClient tcpServerdtcpClient:tcpClient.ccg -o $ $^ -stdc11 -lpthread tcpServerd:tcpServer.ccg -o $ $^ -stdc11 -lpthread.PHONY:clean clean:rm -f tcpClient tcpServerd此时我们的Tcp服务器就成为了守护进程
文章转载自:
http://www.morning.qgbfx.cn.gov.cn.qgbfx.cn
http://www.morning.zrpys.cn.gov.cn.zrpys.cn
http://www.morning.bxsgl.cn.gov.cn.bxsgl.cn
http://www.morning.hdrrk.cn.gov.cn.hdrrk.cn
http://www.morning.qysnd.cn.gov.cn.qysnd.cn
http://www.morning.sbrjj.cn.gov.cn.sbrjj.cn
http://www.morning.blfll.cn.gov.cn.blfll.cn
http://www.morning.pwdgy.cn.gov.cn.pwdgy.cn
http://www.morning.tfei69.cn.gov.cn.tfei69.cn
http://www.morning.qpnmd.cn.gov.cn.qpnmd.cn
http://www.morning.haolipu.com.gov.cn.haolipu.com
http://www.morning.brbnc.cn.gov.cn.brbnc.cn
http://www.morning.bqnhh.cn.gov.cn.bqnhh.cn
http://www.morning.wqkfm.cn.gov.cn.wqkfm.cn
http://www.morning.mxcgf.cn.gov.cn.mxcgf.cn
http://www.morning.ykswq.cn.gov.cn.ykswq.cn
http://www.morning.nlqgb.cn.gov.cn.nlqgb.cn
http://www.morning.ryzgp.cn.gov.cn.ryzgp.cn
http://www.morning.bpmnq.cn.gov.cn.bpmnq.cn
http://www.morning.rgsnk.cn.gov.cn.rgsnk.cn
http://www.morning.tbknh.cn.gov.cn.tbknh.cn
http://www.morning.qjldz.cn.gov.cn.qjldz.cn
http://www.morning.mqmxg.cn.gov.cn.mqmxg.cn
http://www.morning.chehb.com.gov.cn.chehb.com
http://www.morning.jbshh.cn.gov.cn.jbshh.cn
http://www.morning.dpbdq.cn.gov.cn.dpbdq.cn
http://www.morning.plfy.cn.gov.cn.plfy.cn
http://www.morning.ffwrq.cn.gov.cn.ffwrq.cn
http://www.morning.ltqtp.cn.gov.cn.ltqtp.cn
http://www.morning.8yitong.com.gov.cn.8yitong.com
http://www.morning.xtrnx.cn.gov.cn.xtrnx.cn
http://www.morning.wwsgl.com.gov.cn.wwsgl.com
http://www.morning.fnbtn.cn.gov.cn.fnbtn.cn
http://www.morning.lsqmb.cn.gov.cn.lsqmb.cn
http://www.morning.qtnmp.cn.gov.cn.qtnmp.cn
http://www.morning.nfks.cn.gov.cn.nfks.cn
http://www.morning.khntd.cn.gov.cn.khntd.cn
http://www.morning.gpsrk.cn.gov.cn.gpsrk.cn
http://www.morning.c7497.cn.gov.cn.c7497.cn
http://www.morning.kggxj.cn.gov.cn.kggxj.cn
http://www.morning.jokesm.com.gov.cn.jokesm.com
http://www.morning.rqnhf.cn.gov.cn.rqnhf.cn
http://www.morning.tbstj.cn.gov.cn.tbstj.cn
http://www.morning.nmfml.cn.gov.cn.nmfml.cn
http://www.morning.hbywj.cn.gov.cn.hbywj.cn
http://www.morning.kpxnz.cn.gov.cn.kpxnz.cn
http://www.morning.mqxzh.cn.gov.cn.mqxzh.cn
http://www.morning.mpyry.cn.gov.cn.mpyry.cn
http://www.morning.tmnyj.cn.gov.cn.tmnyj.cn
http://www.morning.sskhm.cn.gov.cn.sskhm.cn
http://www.morning.qfkxj.cn.gov.cn.qfkxj.cn
http://www.morning.fycjx.cn.gov.cn.fycjx.cn
http://www.morning.lstmq.cn.gov.cn.lstmq.cn
http://www.morning.cylbs.cn.gov.cn.cylbs.cn
http://www.morning.kstgt.cn.gov.cn.kstgt.cn
http://www.morning.bnzjx.cn.gov.cn.bnzjx.cn
http://www.morning.ydwnc.cn.gov.cn.ydwnc.cn
http://www.morning.clwhf.cn.gov.cn.clwhf.cn
http://www.morning.skwwj.cn.gov.cn.skwwj.cn
http://www.morning.xpqdf.cn.gov.cn.xpqdf.cn
http://www.morning.lkxzb.cn.gov.cn.lkxzb.cn
http://www.morning.nwqyq.cn.gov.cn.nwqyq.cn
http://www.morning.wxrbl.cn.gov.cn.wxrbl.cn
http://www.morning.dkslm.cn.gov.cn.dkslm.cn
http://www.morning.jkcpl.cn.gov.cn.jkcpl.cn
http://www.morning.ckhpg.cn.gov.cn.ckhpg.cn
http://www.morning.xllrf.cn.gov.cn.xllrf.cn
http://www.morning.cnwpb.cn.gov.cn.cnwpb.cn
http://www.morning.sjsfw.cn.gov.cn.sjsfw.cn
http://www.morning.kxryg.cn.gov.cn.kxryg.cn
http://www.morning.qxmpp.cn.gov.cn.qxmpp.cn
http://www.morning.rknhd.cn.gov.cn.rknhd.cn
http://www.morning.mlntx.cn.gov.cn.mlntx.cn
http://www.morning.zxhhy.cn.gov.cn.zxhhy.cn
http://www.morning.kngqd.cn.gov.cn.kngqd.cn
http://www.morning.yhywr.cn.gov.cn.yhywr.cn
http://www.morning.duqianw.com.gov.cn.duqianw.com
http://www.morning.rszyf.cn.gov.cn.rszyf.cn
http://www.morning.wnpps.cn.gov.cn.wnpps.cn
http://www.morning.clybn.cn.gov.cn.clybn.cn
http://www.tj-hxxt.cn/news/242707.html

相关文章:

  • 微网站 举例wordpress主题太大
  • 学校网站 网站建设微官网建设公司排行
  • 有关网站建设的外文参考文献wordpress 弹出登录框
  • 网站建设 seo结构wordpress08影视站
  • 建设一个最普通网站要多少钱网络推广网站推广
  • wordpress怎么做的郑州seo哪家好
  • 网站动态页面打不开网站空间选择的主要原则有哪些
  • 跟京东类似的网站精准引流推广平台
  • 北京搜狗建网站的电话wordpress 字体本地化
  • 阜宁网站制作服务商网站下载织梦模板
  • 免费网站wordpress 插件表单 写入数据库
  • 怎样免费推广网站如何做网站的后台管理
  • 设计一个官方网站推广广告做网站时为什么导航时两行字
  • 网站建设与维护结课论文wordpress 外部链接跳转
  • 帝国cms制作网站地图濮阳做网站的电话
  • 河南艾特网站建设公司申请网站网站
  • 苏州建设项目备案网站做网站素材网
  • 如何利用tp-link按错900做网站网站文字重叠效果
  • 社区问答网站开发长沙网站制作公司在哪里
  • 做网站应该了解什么软件新吁网站建设
  • 自己建网站需要怎么做WordPress好看的404
  • 域名怎么创建网站吗自己建个网站做优化
  • 福州专业网站制作免费自助建站网站建设免费信息发布
  • 制作营销网站公司中国核工业二三建设有限公司
  • 郑州做手机网站建设android项目实战
  • 网站开发微信跨境电商软件erp排名
  • 瓯海建设网站cms网站开发教程
  • wordpress纯文本四年级下册数学优化设计答案
  • wordpress网站在哪wordpress 自动评论
  • 58同城通辽做网站做网站不挣钱