如下介绍一个并发回射客户端/服务器的雏形,所谓回射:就是客户端输入一条数据,服务器端读取并显示,然后服务器端再把刚读取的信息发送回客户端进行显示。示意图如下:
所谓并发服务器:就是一个服务器可以同时为多个连入的客户端提供服务,示意图如下:
如下主要介绍两种实现并发回射服务器的方式,一种是通过子进程方式实现并发,一种是通过I/O多路转接实现并发。
并发服务器(1)[子进程方式]
1 [root@benxintuzi tcp]# cat echoserv_childprocess.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 #define ERR_EXIT(message) 13 do 14 { 15 perror(message); 16 exit(EXIT_FAILURE); 17 } while(0) 18 19 /* 回射服务 */ 20 void echoserv(int conn) 21 { 22 char recvbuf[1024]; 23 while (1) 24 { 25 memset(recvbuf, 0, sizeof(recvbuf)); 26 int ret; 27 if ((ret = read(conn, recvbuf, sizeof(recvbuf))) == -1) 28 ERR_EXIT("read"); 29 if (ret == 0) /* client closed */ 30 { 31 printf("client closed. "); 32 break; 33 } 34 35 fputs(recvbuf, stdout); 36 if (write(conn, recvbuf, ret) != ret) 37 ERR_EXIT("write"); 38 } 39 exit(EXIT_SUCCESS); 40 } 41 42 43 int main(void) 44 { 45 /* 创建一个监听套接字 */ 46 int listen_fd; 47 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 48 ERR_EXIT("socket"); 49 50 /* 初始化服务器地址 */ 51 struct sockaddr_in serv_addr; 52 memset(&serv_addr, 0, sizeof(serv_addr)); 53 serv_addr.sin_family = AF_INET; 54 serv_addr.sin_port = htons(5188); 55 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 56 /** 57 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 58 inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 59 60 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 61 int on = 1; 62 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 63 ERR_EXIT("setsockopt"); 64 65 /* 将服务器地址绑定到监听套接字上 */ 66 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 67 ERR_EXIT("bind"); 68 69 /* 监听进入的连接 */ 70 if (listen(listen_fd, SOMAXCONN) == -1) 71 ERR_EXIT("listen"); 72 73 /* 初始化一个客户端地址用于保存接入的客户端 */ 74 struct sockaddr_in clit_addr; 75 memset(&clit_addr, 0, sizeof(clit_addr)); 76 socklen_t clit_len = sizeof(clit_addr); 77 78 pid_t pid; 79 int conn; 80 while (1) 81 { 82 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */ 83 /* 将返回的客户端连接保存在clit_addr中 */ 84 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1) 85 ERR_EXIT("accept"); 86 printf("client(ip = %s, port = %d) connected. ", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 87 88 /* 创建子进程用于回射服务 */ 89 if (( pid = fork()) == -1) 90 ERR_EXIT("fork"); 91 if (pid == 0) /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */ 92 { 93 /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */ 94 close(listen_fd); 95 96 /* 进行回射服务 */ 97 echoserv(conn); 98 } 99 else /* 父进程 */ 100 close(conn); 101 } 102 103 return 0; 104 }
并发客户端(1)
1 [root@benxintuzi tcp]# cat echoclit.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 #define ERR_EXIT(message) 13 do 14 { 15 perror(message); 16 exit(EXIT_FAILURE); 17 } while (0) 18 19 void echoclit(int sock_fd) 20 { 21 /* 创建一个发送缓冲区和一个接收缓冲区 */ 22 char sendbuf[1024], recvbuf[1024]; 23 /* 从标准输入读取数据,存入发送缓冲区 */ 24 while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 25 { 26 /* 将发送缓冲区的数据写到套接字指定的服务器 */ 27 write(sock_fd, sendbuf, sizeof(sendbuf)); 28 /* 将服务器返回的数据存入接收缓冲区 */ 29 read(sock_fd, recvbuf, sizeof(recvbuf)); 30 /* 将接收缓冲区的数据打印到标准输出 */ 31 fputs(recvbuf, stdout); 32 /* 清空数据缓冲区 */ 33 memset(sendbuf, 0, sizeof(sendbuf)); 34 memset(recvbuf, 0, sizeof(recvbuf)); 35 } 36 } 37 38 39 int main(void) 40 { 41 /* 创建连接套接字 */ 42 int sock_fd; 43 if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 44 ERR_EXIT("socket"); 45 46 /* 初始化要连接的服务器地址 */ 47 struct sockaddr_in serv_addr; 48 memset(&serv_addr, 0, sizeof(serv_addr)); 49 serv_addr.sin_family = AF_INET; 50 serv_addr.sin_port = htons(5188); 51 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 52 53 /* 将套接字连接至指定服务器 */ 54 if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 55 ERR_EXIT("connect"); 56 57 echoclit(sock_fd); 58 close(sock_fd); 59 60 return 0; 61 }
(UDP)SOCK_SEQPACKET套接字提供了基于报文的服务,这意味着接收的数据量和发送的数据量完全一致,因此应用程序可以很容易区分出报文的边界。而(TCP)SOCK_STREAM套接字提供了字节流服务,应用程序不能分辨出报文的边界,这样就很容易导致粘包问题。具体解决方案主要是靠应用层维护消息与消息之间的边界。有如下几种:
- 发送/接收定长包
- 在包尾加上 (如ftp就是这样做的)
- 在包头封装数据的长度
- 依赖于更复杂的应用层协议
如下为封装好的发送/接收定长数据包的函数:
1 /* 接收定长数据包 */ 2 size_t readn(int fd, void* buf, size_t count) 3 { 4 /* nleft:剩下未接收的数据量 5 * nread:每次接收的数据量 */ 6 size_t nleft = count; 7 ssize_t nread; 8 9 while (nleft > 0) /* 如果还有未接收的数据 */ 10 { 11 if ((nread = read(fd, buf, nleft)) == -1) 12 { 13 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 14 return (-1); 15 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 16 break; 17 } 18 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 19 break; 20 else /* 接收数据后更新变量 */ 21 { 22 buf += nread; 23 nleft -= nread; 24 } 25 } 26 27 return (count - nleft); /* 返回成功接收的数据量 */ 28 } 29 30 /* 发送定长数据包 */ 31 ssize_t writen(int fd, const void* buf, size_t count) 32 { 33 size_t nleft = count; 34 ssize_t nwritten; 35 36 while (nleft > 0) 37 { 38 if ((nwritten = write(fd, buf, nleft)) == -1) 39 { 40 if (nleft == count) 41 return (-1); 42 else 43 break; 44 } 45 else if (nwritten == 0) 46 break; 47 else 48 { 49 buf += nwritten; 50 nleft -= nwritten; 51 } 52 } 53 54 return (count - nleft); /* 返回成功发送的数据量 */ 55 }
并发服务器(2)[子进程方式][利用发送定长包解决粘包问题]
1 [root@benxintuzi tcp]# cat echoserv_childprocess_n.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 #define ERR_EXIT(message) 13 do 14 { 15 perror(message); 16 exit(EXIT_FAILURE); 17 } while (0) 18 19 /* 接收定长数据包 */ 20 size_t readn(int fd, void* buf, size_t count) 21 { 22 /* nleft:剩下未接收的数据量 23 * nread:每次接收的数据量 */ 24 size_t nleft = count; 25 ssize_t nread; 26 27 while (nleft > 0) /* 如果还有未接收的数据 */ 28 { 29 if ((nread = read(fd, buf, nleft)) == -1) 30 { 31 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 32 return (-1); 33 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 34 break; 35 } 36 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 37 break; 38 else /* 接收数据后更新变量 */ 39 { 40 buf += nread; 41 nleft -= nread; 42 } 43 } 44 45 return (count - nleft); /* 返回成功接收的数据量 */ 46 } 47 48 /* 发送定长数据包 */ 49 ssize_t writen(int fd, const void* buf, size_t count) 50 { 51 size_t nleft = count; 52 ssize_t nwritten; 53 54 while (nleft > 0) 55 { 56 if ((nwritten = write(fd, buf, nleft)) == -1) 57 { 58 if (nleft == count) 59 return (-1); 60 else 61 break; 62 } 63 else if (nwritten == 0) 64 break; 65 else 66 { 67 buf += nwritten; 68 nleft -= nwritten; 69 } 70 } 71 72 return (count - nleft); /* 返回成功发送的数据量 */ 73 } 74 75 void echoserv(int conn) 76 { 77 char recvbuf[1024]; 78 while (1) 79 { 80 memset(recvbuf, 0, sizeof(recvbuf)); 81 int ret; 82 if ((ret = readn(conn, recvbuf, sizeof(recvbuf))) == -1) 83 ERR_EXIT("readn"); 84 85 fputs(recvbuf, stdout); 86 if (writen(conn, recvbuf, ret) == -1) 87 ERR_EXIT("writen"); 88 } 89 90 exit(EXIT_SUCCESS); 91 } 92 93 94 int main(void) 95 { 96 /* 创建一个监听套接字 */ 97 int listen_fd; 98 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 99 ERR_EXIT("socket"); 100 101 /* 初始化服务器地址 */ 102 struct sockaddr_in serv_addr; 103 memset(&serv_addr, 0, sizeof(serv_addr)); 104 serv_addr.sin_family = AF_INET; 105 serv_addr.sin_port = htons(5188); 106 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 107 /** 108 * serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 109 * inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 110 111 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 112 int on = 1; 113 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 114 ERR_EXIT("setsockopt"); 115 116 /* 将服务器地址绑定到监听套接字上 */ 117 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 118 ERR_EXIT("bind"); 119 120 /* 监听进入的连接 */ 121 if (listen(listen_fd, SOMAXCONN) == -1) 122 ERR_EXIT("listen"); 123 124 /* 初始化一个客户端地址用于保存接入的客户端 */ 125 struct sockaddr_in clit_addr; 126 memset(&clit_addr, 0, sizeof(clit_addr)); 127 socklen_t clit_len = sizeof(clit_addr); 128 129 pid_t pid; 130 int conn; 131 while (1) 132 { 133 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */ 134 /* 将返回的客户端连接保存在clit_addr中 */ 135 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1) 136 ERR_EXIT("accept"); 137 printf("client(ip = %s, port = %d) connected. ", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 138 139 /* 创建子进程用于回射服务 */ 140 if (( pid = fork()) == -1) 141 ERR_EXIT("fork"); 142 if (pid == 0) /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */ 143 { 144 /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */ 145 close(listen_fd); 146 147 /* 进行回射服务 */ 148 echoserv(conn); 149 } 150 else /* 父进程 */ 151 close(conn); 152 } 153 154 return 0; 155 }
并发客户端(2)[利用发送定长包解决粘包问题]
1 [root@benxintuzi tcp]# cat echoclit_n.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 #define ERR_EXIT(message) 13 do 14 { 15 perror(message); 16 exit(EXIT_FAILURE); 17 } while(0) 18 19 20 /* 接收定长数据包 */ 21 size_t readn(int fd, void* buf, size_t count) 22 { 23 /* nleft:剩下未接收的数据量 24 * * nread:每次接收的数据量 */ 25 size_t nleft = count; 26 ssize_t nread; 27 28 while (nleft > 0) /* 如果还有未接收的数据 */ 29 { 30 if ((nread = read(fd, buf, nleft)) == -1) 31 { 32 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 33 return (-1); 34 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 35 break; 36 } 37 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 38 break; 39 else /* 接收数据后更新变量 */ 40 { 41 buf += nread; 42 nleft -= nread; 43 } 44 } 45 46 return (count - nleft); /* 返回成功接收的数据量 */ 47 } 48 49 /* 发送定长数据包 */ 50 ssize_t writen(int fd, const void* buf, size_t count) 51 { 52 size_t nleft = count; 53 ssize_t nwritten; 54 55 while (nleft > 0) 56 { 57 if ((nwritten = write(fd, buf, nleft)) == -1) 58 { 59 if (nleft == count) 60 return (-1); 61 else 62 break; 63 } 64 else if (nwritten == 0) 65 break; 66 else 67 { 68 buf += nwritten; 69 nleft -= nwritten; 70 } 71 } 72 73 return (count - nleft); /* 返回成功发送的数据量 */ 74 } 75 76 void echoclit(int sock_fd) 77 { 78 /* 创建一个发送缓冲区和一个接收缓冲区 */ 79 char sendbuf[1024], recvbuf[1024]; 80 /* 从标准输入读取数据,存入发送缓冲区 */ 81 while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 82 { 83 /* 将发送缓冲区的数据写到套接字指定的服务器 */ 84 int ret; 85 if ((writen(sock_fd, sendbuf, sizeof(sendbuf))) == -1) 86 ERR_EXIT("writen"); 87 88 /* 将服务器返回的数据存入接收缓冲区 */ 89 if ((readn(sock_fd, recvbuf, sizeof(recvbuf))) == -1) 90 ERR_EXIT("recvbuf"); 91 92 /* 将接收缓冲区的数据打印到标准输出 */ 93 fputs(recvbuf, stdout); 94 /* 清空数据缓冲区 */ 95 memset(sendbuf, 0, sizeof(sendbuf)); 96 memset(recvbuf, 0, sizeof(recvbuf)); 97 } 98 } 99 100 101 int main(void) 102 { 103 /* 创建连接套接字 */ 104 int sock_fd; 105 if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 106 ERR_EXIT("socket"); 107 108 /* 初始化要连接的服务器地址 */ 109 struct sockaddr_in serv_addr; 110 memset(&serv_addr, 0, sizeof(serv_addr)); 111 serv_addr.sin_family = AF_INET; 112 serv_addr.sin_port = htons(5188); 113 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 114 115 /* 将套接字连接至指定服务器 */ 116 if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 117 ERR_EXIT("connect"); 118 119 echoclit(sock_fd); 120 close(sock_fd); 121 122 return 0; 123 }
如下解决粘包问题采用自定义数据包头,在包头中封装一个数据的长度与存放数据的缓存区:
struct packet /* 包头 */
{
int len; /* 表示数据的长度 */
char buf[1024]; /* 数据缓存区 */
};
并发服务器(3)[子进程方式][利用自定义包头解决粘包问题]
1 [root@benxintuzi tcp]# cat echoserv_childprocess_packet.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 13 #define ERR_EXIT(message) 14 do 15 { 16 perror(message); 17 exit(EXIT_FAILURE); 18 } while (0) 19 20 /* 自定义包头 */ 21 struct packet 22 { 23 int len; /* 数据长度 */ 24 char buf[1024]; /* 数据缓存区 */ 25 }; 26 27 28 /* 接收定长数据包 */ 29 size_t readn(int fd, void* buf, size_t count) 30 { 31 /* nleft:剩下未接收的数据量 32 * nread:每次接收的数据量 */ 33 size_t nleft = count; 34 ssize_t nread; 35 36 while (nleft > 0) /* 如果还有未接收的数据 */ 37 { 38 if ((nread = read(fd, buf, nleft)) == -1) 39 { 40 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 41 return (-1); 42 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 43 break; 44 } 45 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 46 break; 47 else /* 接收数据后更新变量 */ 48 { 49 buf += nread; 50 nleft -= nread; 51 } 52 } 53 54 return (count - nleft); /* 返回成功接收的数据量 */ 55 } 56 57 /* 发送定长数据包 */ 58 ssize_t writen(int fd, const void* buf, size_t count) 59 { 60 size_t nleft = count; 61 ssize_t nwritten; 62 63 while (nleft > 0) 64 { 65 if ((nwritten = write(fd, buf, nleft)) == -1) 66 { 67 if (nleft == count) 68 return (-1); 69 else 70 break; 71 } 72 else if (nwritten == 0) 73 break; 74 else 75 { 76 buf += nwritten; 77 nleft -= nwritten; 78 } 79 } 80 81 return (count - nleft); /* 返回成功发送的数据量 */ 82 } 83 84 void echoserv(int conn) 85 { 86 struct packet recvbuf; 87 int n; 88 while (1) 89 { 90 memset(&recvbuf, 0, sizeof(recvbuf)); 91 92 /* 先接收包头中的数据长度字段 */ 93 int ret; 94 if ((ret = readn(conn, &recvbuf.len, 4)) == -1) 95 ERR_EXIT("readn"); 96 else if (ret < 4) 97 { 98 printf("client closed. "); 99 break; 100 } 101 else 102 { 103 n = ntohl(recvbuf.len); /* 取出数据长度 */ 104 if ((ret = readn(conn, recvbuf.buf, n)) == -1) 105 ERR_EXIT("readn"); 106 else if (ret < n) 107 { 108 printf("client closed. "); 109 break; 110 } 111 112 fputs(recvbuf.buf, stdout); /* 服务器端输出 */ 113 if ((ret = writen(conn, &recvbuf, 4 + n)) == -1) /* 回射到客户端 */ 114 ERR_EXIT("writen"); 115 } 116 } 117 118 exit(EXIT_SUCCESS); 119 } 120 121 122 123 int main(void) 124 { 125 /* 创建一个监听套接字 */ 126 int listen_fd; 127 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 128 ERR_EXIT("socket"); 129 130 /* 初始化服务器地址 */ 131 struct sockaddr_in serv_addr; 132 memset(&serv_addr, 0, sizeof(serv_addr)); 133 serv_addr.sin_family = AF_INET; 134 serv_addr.sin_port = htons(5188); 135 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 136 /** 137 * * serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 138 * * inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 139 140 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 141 int on = 1; 142 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 143 ERR_EXIT("setsockopt"); 144 145 /* 将服务器地址绑定到监听套接字上 */ 146 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 147 ERR_EXIT("bind"); 148 149 /* 监听进入的连接 */ 150 if (listen(listen_fd, SOMAXCONN) == -1) 151 ERR_EXIT("listen"); 152 153 /* 初始化一个客户端地址用于保存接入的客户端 */ 154 struct sockaddr_in clit_addr; 155 memset(&clit_addr, 0, sizeof(clit_addr)); 156 socklen_t clit_len = sizeof(clit_addr); 157 158 pid_t pid; 159 int conn; 160 while (1) 161 { 162 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */ 163 /* 将返回的客户端连接保存在clit_addr中 */ 164 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1) 165 ERR_EXIT("accept"); 166 printf("client(ip = %s, port = %d) connected. ", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 167 168 /* 创建子进程用于回射服务 */ 169 if (( pid = fork()) == -1) 170 ERR_EXIT("fork"); 171 if (pid == 0) /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */ 172 { 173 /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */ 174 close(listen_fd); 175 176 /* 进行回射服务 */ 177 echoserv(conn); 178 } 179 else /* 父进程 */ 180 close(conn); 181 } 182 183 return 0; 184 }
并发客户端(3)[利用自定义包头解决粘包问题]
1 [root@benxintuzi tcp]# cat echoclit_packet.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 #define ERR_EXIT(message) 13 do 14 { 15 perror(message); 16 exit(EXIT_FAILURE); 17 } while (0) 18 19 /* 自定义包头 */ 20 struct packet 21 { 22 int len; /* 数据长度 */ 23 char buf[1024]; /* 数据缓存区 */ 24 }; 25 26 27 /* 接收定长数据包 */ 28 size_t readn(int fd, void* buf, size_t count) 29 { 30 /* nleft:剩下未接收的数据量 31 * * nread:每次接收的数据量 */ 32 size_t nleft = count; 33 ssize_t nread; 34 35 while (nleft > 0) /* 如果还有未接收的数据 */ 36 { 37 if ((nread = read(fd, buf, nleft)) == -1) 38 { 39 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 40 return (-1); 41 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 42 break; 43 } 44 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 45 break; 46 else /* 接收数据后更新变量 */ 47 { 48 buf += nread; 49 nleft -= nread; 50 } 51 } 52 53 return (count - nleft); /* 返回成功接收的数据量 */ 54 } 55 56 /* 发送定长数据包 */ 57 ssize_t writen(int fd, const void* buf, size_t count) 58 { 59 size_t nleft = count; 60 ssize_t nwritten; 61 62 while (nleft > 0) 63 { 64 if ((nwritten = write(fd, buf, nleft)) == -1) 65 { 66 if (nleft == count) 67 return (-1); 68 else 69 break; 70 } 71 else if (nwritten == 0) 72 break; 73 else 74 { 75 buf += nwritten; 76 nleft -= nwritten; 77 } 78 } 79 80 return (count - nleft); /* 返回成功发送的数据量 */ 81 } 82 83 84 void echoclit(int sock_fd) 85 { 86 struct packet sendbuf; 87 struct packet recvbuf; 88 memset(&sendbuf, 0, sizeof(sendbuf)); 89 memset(&recvbuf, 0, sizeof(recvbuf)); 90 91 int n; 92 while (fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL) 93 { 94 n = sizeof(sendbuf.buf); 95 sendbuf.len = htonl(n); 96 int ret; 97 if ((ret = writen(sock_fd, &sendbuf, 4 + n)) == -1) 98 ERR_EXIT("writen"); 99 100 if ((ret = readn(sock_fd, &recvbuf.len, 4)) == -1) 101 ERR_EXIT("readn"); 102 else if (ret < 4) 103 break; 104 else 105 { 106 n = ntohl(recvbuf.len); 107 if ((ret = readn(sock_fd, &recvbuf.buf, n)) == -1) 108 ERR_EXIT("readn"); 109 else if (ret < n) 110 break; 111 112 fputs(recvbuf.buf, stdout); 113 memset(&sendbuf, 0, sizeof(sendbuf)); 114 memset(&recvbuf, 0, sizeof(recvbuf)); 115 } 116 } 117 } 118 119 120 int main(void) 121 { 122 /* 创建连接套接字 */ 123 int sock_fd; 124 if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 125 ERR_EXIT("socket"); 126 127 /* 初始化要连接的服务器地址 */ 128 struct sockaddr_in serv_addr; 129 memset(&serv_addr, 0, sizeof(serv_addr)); 130 serv_addr.sin_family = AF_INET; 131 serv_addr.sin_port = htons(5188); 132 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 133 134 /* 将套接字连接至指定服务器 */ 135 if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 136 ERR_EXIT("connect"); 137 138 echoclit(sock_fd); 139 close(sock_fd); 140 141 return 0; 142 }
用recv代替read时,如果指定MSG_PEEK标志,那么recv函数在返回缓冲区数据的同时仍然保留该部分数据,并不从缓冲区队列中删除,这样下一次读取时,将依然返回同样的数据。如下封装一个recv_peek函数:
1 size_t recv_peek(int listen_fd, void* buf, size_t len) 2 { 3 while (1) 4 { 5 int ret; 6 ret = recv(listen_fd, buf, len, MSG_PEEK); 7 if (ret == -1 && errno == EINTR) 8 continue; 9 return (ret); 10 } 11 }
用recv_peek来实现readline函数的功能:
1 ssize_t readline(int listen_fd, void* buf, size_t maxline) 2 { 3 int ret; 4 int nread; 5 char* pbuf = buf; 6 int nleft = maxline; 7 8 while (1) 9 { 10 ret = recv_peek(listen_fd, pbuf, nleft); 11 if (ret < 0) 12 return (ret); 13 else if (ret == 0) 14 return (ret); 15 16 nread = ret; 17 18 int i; 19 for (i = 0; i < nread; i++) 20 { 21 if (pbuf[i] == ' ') 22 { 23 ret = readn(listen_fd, pbuf, i + 1); 24 if (ret != i + 1) 25 exit(EXIT_FAILURE); 26 return (ret); 27 } 28 } 29 30 if (nread > nleft) 31 exit(EXIT_FAILURE); 32 nleft -= nread; 33 34 ret = readn(listen_fd, pbuf, nread); 35 if (ret != nread) 36 exit(EXIT_FAILURE); 37 pbuf += nread; 38 } 39 40 return (-1); 41 }
并发服务器(4)[子进程方式][利用readline函数实现]
1 [root@benxintuzi tcp]# cat echoserv_childprocess_readline.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 13 #define ERR_EXIT(message) 14 do 15 { 16 perror(message); 17 exit(EXIT_FAILURE); 18 } while (0) 19 20 21 /* 接收定长数据包 */ 22 size_t readn(int fd, void* buf, size_t count) 23 { 24 /* nleft:剩下未接收的数据量 25 * * * nread:每次接收的数据量 */ 26 size_t nleft = count; 27 ssize_t nread; 28 29 while (nleft > 0) /* 如果还有未接收的数据 */ 30 { 31 if ((nread = read(fd, buf, nleft)) == -1) 32 { 33 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 34 return (-1); 35 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 36 break; 37 } 38 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 39 break; 40 else /* 接收数据后更新变量 */ 41 { 42 buf += nread; 43 nleft -= nread; 44 } 45 } 46 47 return (count - nleft); /* 返回成功接收的数据量 */ 48 } 49 50 /* 发送定长数据包 */ 51 ssize_t writen(int fd, const void* buf, size_t count) 52 { 53 size_t nleft = count; 54 ssize_t nwritten; 55 56 while (nleft > 0) 57 { 58 if ((nwritten = write(fd, buf, nleft)) == -1) 59 { 60 if (nleft == count) 61 return (-1); 62 else 63 break; 64 } 65 else if (nwritten == 0) 66 break; 67 else 68 { 69 buf += nwritten; 70 nleft -= nwritten; 71 } 72 } 73 74 return (count - nleft); /* 返回成功发送的数据量 */ 75 } 76 77 78 size_t recv_peek(int listen_fd, void* buf, size_t len) 79 { 80 while (1) 81 { 82 int ret; 83 ret = recv(listen_fd, buf, len, MSG_PEEK); 84 if (ret == -1 && errno == EINTR) 85 continue; 86 return (ret); 87 } 88 } 89 90 ssize_t readline(int listen_fd, void* buf, size_t maxline) 91 { 92 int ret; 93 int nread; 94 char* pbuf = buf; 95 int nleft = maxline; 96 97 while (1) 98 { 99 ret = recv_peek(listen_fd, pbuf, nleft); 100 if (ret < 0) 101 return (ret); 102 else if (ret == 0) 103 return (ret); 104 105 nread = ret; 106 107 int i; 108 for (i = 0; i < nread; i++) 109 { 110 if (pbuf[i] == ' ') 111 { 112 ret = readn(listen_fd, pbuf, i + 1); 113 if (ret != i + 1) 114 exit(EXIT_FAILURE); 115 return (ret); 116 } 117 } 118 119 if (nread > nleft) 120 exit(EXIT_FAILURE); 121 nleft -= nread; 122 123 ret = readn(listen_fd, pbuf, nread); 124 if (ret != nread) 125 exit(EXIT_FAILURE); 126 pbuf += nread; 127 } 128 return (-1); 129 } 130 131 void echoserv(int conn) 132 { 133 char recvbuf[1024]; 134 while (1) 135 { 136 memset(&recvbuf, 0, sizeof(recvbuf)); 137 138 int ret; 139 if ((ret = readline(conn, recvbuf, 1024)) == -1) 140 ERR_EXIT("readline"); 141 else if (ret == 0) 142 { 143 printf("client closed. "); 144 break; 145 } 146 else 147 { 148 fputs(recvbuf, stdout); /* 服务器端输出 */ 149 if ((ret = writen(conn, recvbuf, strlen(recvbuf))) == -1) /* 回射到客户端 */ 150 ERR_EXIT("writen"); 151 } 152 } 153 154 exit(EXIT_SUCCESS); 155 } 156 157 158 int main(void) 159 { 160 /* 创建一个监听套接字 */ 161 int listen_fd; 162 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 163 ERR_EXIT("socket"); 164 165 /* 初始化服务器地址 */ 166 struct sockaddr_in serv_addr; 167 memset(&serv_addr, 0, sizeof(serv_addr)); 168 serv_addr.sin_family = AF_INET; 169 serv_addr.sin_port = htons(5188); 170 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 171 /** 172 * * * serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 173 * * * inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 174 175 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 176 int on = 1; 177 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 178 ERR_EXIT("setsockopt"); 179 180 /* 将服务器地址绑定到监听套接字上 */ 181 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 182 ERR_EXIT("bind"); 183 184 /* 监听进入的连接 */ 185 if (listen(listen_fd, SOMAXCONN) == -1) 186 ERR_EXIT("listen"); 187 188 /* 初始化一个客户端地址用于保存接入的客户端 */ 189 struct sockaddr_in clit_addr; 190 memset(&clit_addr, 0, sizeof(clit_addr)); 191 socklen_t clit_len = sizeof(clit_addr); 192 193 pid_t pid; 194 int conn; 195 while (1) 196 { 197 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */ 198 /* 将返回的客户端连接保存在clit_addr中 */ 199 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1) 200 ERR_EXIT("accept"); 201 printf("client(ip = %s, port = %d) connected. ", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 202 203 /* 创建子进程用于回射服务 */ 204 if (( pid = fork()) == -1) 205 ERR_EXIT("fork"); 206 if (pid == 0) /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */ 207 { 208 /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */ 209 close(listen_fd); 210 211 /* 进行回射服务 */ 212 echoserv(conn); 213 } 214 else /* 父进程 */ 215 close(conn); 216 } 217 218 return (0); 219 }
并发客户端(4)[利用readline函数实现]
1 [root@benxintuzi tcp]# cat echoclit_readline.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 13 #define ERR_EXIT(message) 14 do 15 { 16 perror(message); 17 exit(EXIT_FAILURE); 18 } while (0) 19 20 21 /* 接收定长数据包 */ 22 size_t readn(int fd, void* buf, size_t count) 23 { 24 /* nleft:剩下未接收的数据量 25 * * * * nread:每次接收的数据量 */ 26 size_t nleft = count; 27 ssize_t nread; 28 29 while (nleft > 0) /* 如果还有未接收的数据 */ 30 { 31 if ((nread = read(fd, buf, nleft)) == -1) 32 { 33 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 34 return (-1); 35 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 36 break; 37 } 38 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 39 break; 40 else /* 接收数据后更新变量 */ 41 { 42 buf += nread; 43 nleft -= nread; 44 } 45 } 46 47 return (count - nleft); /* 返回成功接收的数据量 */ 48 } 49 50 /* 发送定长数据包 */ 51 ssize_t writen(int fd, const void* buf, size_t count) 52 { 53 size_t nleft = count; 54 ssize_t nwritten; 55 56 while (nleft > 0) 57 { 58 if ((nwritten = write(fd, buf, nleft)) == -1) 59 { 60 if (nleft == count) 61 return (-1); 62 else 63 break; 64 } 65 else if (nwritten == 0) 66 break; 67 else 68 { 69 buf += nwritten; 70 nleft -= nwritten; 71 } 72 } 73 74 return (count - nleft); /* 返回成功发送的数据量 */ 75 } 76 77 78 size_t recv_peek(int listen_fd, void* buf, size_t len) 79 { 80 while (1) 81 { 82 int ret; 83 ret = recv(listen_fd, buf, len, MSG_PEEK); 84 if (ret == -1 && errno == EINTR) 85 continue; 86 return (ret); 87 } 88 } 89 90 ssize_t readline(int sock_fd, void* buf, size_t maxline) 91 { 92 int ret; 93 int nread; 94 char* pbuf = buf; 95 int nleft = maxline; 96 97 while (1) 98 { 99 ret = recv_peek(sock_fd, pbuf, nleft); 100 101 if (ret < 0) 102 return (ret); 103 else if (ret == 0) 104 return (ret); 105 106 nread = ret; 107 108 int i; 109 for (i = 0; i < nread; i++) 110 { 111 if (pbuf[i] == ' ') 112 { 113 ret = readn(sock_fd, pbuf, i + 1); 114 if (ret != i + 1) 115 exit(EXIT_FAILURE); 116 return (ret); 117 } 118 } 119 120 if (nread > nleft) 121 exit(EXIT_FAILURE); 122 nleft -= nread; 123 124 ret = readn(sock_fd, pbuf, nread); 125 if (ret != nread) exit(EXIT_FAILURE); 126 127 pbuf += nread; 128 } 129 return (-1); 130 } 131 132 void echoclit(int sock_fd) 133 { 134 char sendbuf[1024]; 135 char recvbuf[1024]; 136 memset(&sendbuf, 0, sizeof(sendbuf)); 137 memset(&recvbuf, 0, sizeof(recvbuf)); 138 139 while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 140 { 141 writen(sock_fd, sendbuf, strlen(sendbuf)); 142 143 int ret; 144 ret = readline(sock_fd, recvbuf, 1024); 145 if (ret == -1) 146 ERR_EXIT("readline"); 147 else if (ret == 0) 148 break; 149 150 fputs(recvbuf, stdout); 151 memset(sendbuf, 0, sizeof(sendbuf)); 152 memset(recvbuf, 0, sizeof(recvbuf)); 153 } 154 exit(EXIT_SUCCESS); 155 } 156 157 158 int main(void) 159 { 160 /* 创建连接套接字 */ 161 int sock_fd; 162 if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 163 ERR_EXIT("socket"); 164 165 /* 初始化要连接的服务器地址 */ 166 struct sockaddr_in serv_addr; 167 memset(&serv_addr, 0, sizeof(serv_addr)); 168 serv_addr.sin_family = AF_INET; 169 serv_addr.sin_port = htons(5188); 170 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 171 172 /* 将套接字连接至指定服务器 */ 173 if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 174 ERR_EXIT("connect"); 175 176 echoclit(sock_fd); 177 close(sock_fd); 178 179 return (0); 180 }
当从一个文件描述符读,然后又写到另一个文件描述符时,一般在下列形式的循环中使用阻塞I/O:
while ((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
printf(“write error. ”);
这种形式的阻塞I/O随处可见,但是如果必须从两个文件描述符读,该如何处理呢?在这种情况下,我们不能在任一个描述符上进行阻塞read,因为可能会因为被阻塞在一个描述符的read操作上而导致另一个描述符即使有数据也无法处理。
一种比较好的技术是使用I/O多路转接(I/O multiplexing)。在这种技术中,先构造一张我们感兴趣的描述符列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。如下介绍多路转接函数select、pselect、poll。
传给select的参数告诉内核:
我们所关心的描述符、关于每个描述符我们所关心的条件、愿意等待多长时间;
内核返回给我们如下信息:
已准备好的描述符的总数量、对于读、写或异常这3个条件中的每一个,哪些描述符已做好准备。
使用这种返回信息,就可调用相应的I/O函数(一般是read或write),并且确知不会发生阻塞情况。
一般使得这3种事件发生的条件如下:
(1)可读
套接字接收缓冲区有数据可读;
连接的读端关闭,即接收到了FIN段,读操作将返回0;
如果是监听套接字,则已完成三次握手的连接队列不为空;
套接字上发生了一个错误待处理,错误可以通过setsockopt指定的SO_ERROR选项来获取;
(2)可写
套接字发送缓冲区中有空间容纳数据;
连接的写端关闭,即收到RST段,再次调用write操作;
套接字上发生了一个错误待处理,错误可以通过setsockopt指定的SO_ERROR选项来获取;
(3)异常
套接字存在带外数据;
与select不同,poll并非为每个条件都构造一个描述符集,而是构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对该描述符感兴趣的条件。
比较:
用select实现的并发服务器,存在两个方面的限制:
(1) 一个进程可以打开的最大文件描述符限制,当然可以通过调整内核参数参数解决(通过ulimit –n命令或者通过getrlimit/setrlimit函数实现);
(2) select中的fd_set集合容量存在限制(FD_SETSIZE),可以修改内核,但是需要重新编译内核才能生效。
如果用poll实现并发服务器,则不存在select的第二个限制。
与select和poll相比,epoll的最大优势在于其不会随着监听fd数目的增多而降低效率。原因如下:select与poll中,内核采用轮询来处理,轮询的fd数目越多越耗时。而epoll是基于回调实现的,如果某个fd有预期事件发生,立即通过回调函数将其加入epoll就绪队列中,因此其仅关心“活跃”的fd,与fd的总数关系不大。再者,在内核空间与用户空间通信方面,select与poll采用内存拷贝方式,而epoll采用共享内存方式,效率优于前者。最后,epoll不仅会告诉应用程序有I/O事件到来,而且还会告诉应用程序关于事件相关的信息。根据这些信息,应用程序就可以直接定位事件而不必遍历整个fd集合。
epoll执行一个与poll相似的任务:监控多个文件描述符,从而判断它们中的一个或多个是否可以进行I/O操作。
如下系统调用用于创建和管理epoll实例:
(1) epoll_create: 创建一个epoll实例,返回一个该实例的文件描述符;
(2) epoll_ctl: 注册感兴趣事件对于某个文件描述符。注册感兴趣事件后的文件描述符集合有时也被称为epoll集合;
(3) epoll_wait: 等待I/O事件。如果没有事件发生,则阻塞调用线程。
使用epoll的两种模式:
Level-triggered(LT) and edge-triggered(ET)
应用程序使用EPOLLET标志时,应该同时使用非阻塞的文件描述符来避免阻塞读或者阻塞写。
建议如下情况使用epoll时指定EPOLLET标志:
1. 使用非阻塞的文件描述符;
2. read或者write操作返回EAGIN后等待事件发生;
此模式下,系统仅仅通知应用程序哪些fds变成了就绪状态,一旦fd变成就绪状态,epoll将不再关注这个fd的任何状态信息了(将该fd从epoll队列中移除),直到应用程序通过读写操作触发EAGAIN状态,此时epoll认为这个fd又变为了空闲状态,那么epoll将重新关注该fd的状态变化(将其重新加入epoll队列中)。随着epoll_wait的返回,epoll队列中的fds在逐渐减少,因此在大并发处理中,ET模式更有优势。
相反地,当时用LT模式时,epoll就是一个比较快的poll。此模式下,应用程序只需处理从epoll_wait返回的fds,这些fds我们认为其处于就绪状态。
如下分别采用select、poll、epoll实现并发服务器:
并发服务器(5)[select方式]
1 [root@benxintuzi tcp]# cat echoserv_select.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 13 #define ERR_EXIT(message) 14 do 15 { 16 perror(message); 17 exit(EXIT_FAILURE); 18 } while (0) 19 20 21 /* 接收定长数据包 */ 22 size_t readn(int fd, void* buf, size_t count) 23 { 24 /* nleft:剩下未接收的数据量 25 * * * * nread:每次接收的数据量 */ 26 size_t nleft = count; 27 ssize_t nread; 28 29 while (nleft > 0) /* 如果还有未接收的数据 */ 30 { 31 if ((nread = read(fd, buf, nleft)) == -1) 32 { 33 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 34 return (-1); 35 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 36 break; 37 } 38 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 39 break; 40 else /* 接收数据后更新变量 */ 41 { 42 buf += nread; 43 nleft -= nread; 44 } 45 } 46 47 return (count - nleft); /* 返回成功接收的数据量 */ 48 } 49 50 /* 发送定长数据包 */ 51 ssize_t writen(int fd, const void* buf, size_t count) 52 { 53 size_t nleft = count; 54 ssize_t nwritten; 55 56 while (nleft > 0) 57 { 58 if ((nwritten = write(fd, buf, nleft)) == -1) 59 { 60 if (nleft == count) 61 return (-1); 62 else 63 break; 64 } 65 else if (nwritten == 0) 66 break; 67 else 68 { 69 buf += nwritten; 70 nleft -= nwritten; 71 } 72 } 73 74 return (count - nleft); /* 返回成功发送的数据量 */ 75 } 76 77 78 size_t recv_peek(int listen_fd, void* buf, size_t len) 79 { 80 while (1) 81 { 82 int ret; 83 ret = recv(listen_fd, buf, len, MSG_PEEK); 84 if (ret == -1 && errno == EINTR) 85 continue; 86 return (ret); 87 } 88 } 89 90 ssize_t readline(int listen_fd, void* buf, size_t maxline) 91 { 92 int ret; 93 int nread; 94 char* pbuf = buf; 95 int nleft = maxline; 96 97 while (1) 98 { 99 ret = recv_peek(listen_fd, pbuf, nleft); 100 if (ret < 0) 101 return (ret); 102 else if (ret == 0) 103 return (ret); 104 105 nread = ret; 106 107 int i; 108 for (i = 0; i < nread; i++) 109 { 110 if (pbuf[i] == ' ') 111 { 112 ret = readn(listen_fd, pbuf, i + 1); 113 if (ret != i + 1) 114 exit(EXIT_FAILURE); 115 return (ret); 116 } 117 } 118 119 if (nread > nleft) 120 exit(EXIT_FAILURE); 121 nleft -= nread; 122 123 ret = readn(listen_fd, pbuf, nread); 124 if (ret != nread) 125 exit(EXIT_FAILURE); 126 pbuf += nread; 127 } 128 return (-1); 129 } 130 131 void echoserv(int listen_fd) 132 { 133 /** using select to realize a concurrent server */ 134 135 struct sockaddr_in clit_addr; 136 memset(&clit_addr, 0, sizeof(clit_addr)); 137 socklen_t clit_len = sizeof(clit_addr); 138 int conn; 139 int client[FD_SETSIZE]; 140 int i; 141 for (i = 0; i < FD_SETSIZE; i++) 142 client[i] = -1; 143 144 int maxi = 0; 145 int nready; 146 int maxfd = listen_fd; 147 fd_set rset; 148 fd_set allset; 149 FD_ZERO(&rset); 150 FD_ZERO(&allset); 151 FD_SET(listen_fd, &allset); 152 153 while (1) 154 { 155 rset = allset; 156 nready = select(maxfd + 1, &rset, NULL, NULL, NULL); 157 if (nready == -1) 158 { 159 if (errno == EINTR) 160 continue; 161 else 162 ERR_EXIT("select"); 163 } 164 if (nready == 0) 165 continue; 166 167 if (FD_ISSET(listen_fd, &rset)) 168 { 169 conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len); 170 if (conn == -1) 171 ERR_EXIT("accept"); 172 for (i = 0; i < FD_SETSIZE; i++) 173 { 174 if (client[i] < 0) 175 { 176 client[i] = conn; 177 if (i > maxi) 178 maxi = i; 179 break; 180 } 181 } 182 if (i == FD_SETSIZE) 183 { 184 fprintf(stderr, "too many clients. "); 185 exit(EXIT_FAILURE); 186 } 187 188 printf("client(ip = %s, port = %d) connected. ",inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 189 190 FD_SET(conn, &allset); 191 if (conn > maxfd) 192 maxfd = conn; 193 if (--nready <= 0) 194 continue; 195 } 196 197 for (i = 0; i <= maxi; i++) 198 { 199 conn = client[i]; 200 if (conn == -1) 201 continue; 202 if (FD_ISSET(conn, &rset)) 203 { 204 char recvbuf[1024] = {0}; 205 int ret = readline(conn, recvbuf, 1024); 206 if (ret == -1) 207 ERR_EXIT("readline"); 208 if (ret == 0) 209 { 210 printf("client close. "); 211 FD_CLR(conn, &allset); 212 client[i] = -1; 213 close(conn); 214 } 215 fputs(recvbuf, stdout); 216 writen(conn, recvbuf, strlen(recvbuf)); 217 if (--nready <= 0) 218 break; 219 } 220 } 221 222 } 223 exit(EXIT_SUCCESS); 224 } 225 226 227 228 int main(void) 229 { 230 /* 创建一个监听套接字 */ 231 int listen_fd; 232 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 233 ERR_EXIT("socket"); 234 235 /* 初始化服务器地址 */ 236 struct sockaddr_in serv_addr; 237 memset(&serv_addr, 0, sizeof(serv_addr)); 238 serv_addr.sin_family = AF_INET; 239 serv_addr.sin_port = htons(5188); 240 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 241 /** 242 * * * * serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 243 * * * * inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 244 245 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 246 int on = 1; 247 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 248 ERR_EXIT("setsockopt"); 249 250 /* 将服务器地址绑定到监听套接字上 */ 251 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 252 ERR_EXIT("bind"); 253 254 /* 监听进入的连接 */ 255 if (listen(listen_fd, SOMAXCONN) == -1) 256 ERR_EXIT("listen"); 257 258 echoserv(listen_fd); 259 260 close(listen_fd); 261 262 return (0); 263 }
并发客户端(5)
1 [root@benxintuzi tcp]# cat echoclit_select.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 13 #define ERR_EXIT(message) 14 do 15 { 16 perror(message); 17 exit(EXIT_FAILURE); 18 } while (0) 19 20 21 /* 接收定长数据包 */ 22 size_t readn(int fd, void* buf, size_t count) 23 { 24 /* nleft:剩下未接收的数据量 25 * * * * * nread:每次接收的数据量 */ 26 size_t nleft = count; 27 ssize_t nread; 28 29 while (nleft > 0) /* 如果还有未接收的数据 */ 30 { 31 if ((nread = read(fd, buf, nleft)) == -1) 32 { 33 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 34 return (-1); 35 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 36 break; 37 } 38 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 39 break; 40 else /* 接收数据后更新变量 */ 41 { 42 buf += nread; 43 nleft -= nread; 44 } 45 } 46 47 return (count - nleft); /* 返回成功接收的数据量 */ 48 } 49 50 /* 发送定长数据包 */ 51 ssize_t writen(int fd, const void* buf, size_t count) 52 { 53 size_t nleft = count; 54 ssize_t nwritten; 55 56 while (nleft > 0) 57 { 58 if ((nwritten = write(fd, buf, nleft)) == -1) 59 { 60 if (nleft == count) 61 return (-1); 62 else 63 break; 64 } 65 else if (nwritten == 0) 66 break; 67 else 68 { 69 buf += nwritten; 70 nleft -= nwritten; 71 } 72 } 73 74 return (count - nleft); /* 返回成功发送的数据量 */ 75 } 76 77 78 size_t recv_peek(int listen_fd, void* buf, size_t len) 79 { 80 while (1) 81 { 82 int ret; 83 ret = recv(listen_fd, buf, len, MSG_PEEK); 84 if (ret == -1 && errno == EINTR) 85 continue; 86 return (ret); 87 } 88 } 89 90 ssize_t readline(int sock_fd, void* buf, size_t maxline) 91 { 92 int ret; 93 int nread; 94 char* pbuf = buf; 95 int nleft = maxline; 96 97 while (1) 98 { 99 ret = recv_peek(sock_fd, pbuf, nleft); 100 101 if (ret < 0) 102 return (ret); 103 else if (ret == 0) 104 return (ret); 105 106 nread = ret; 107 108 int i; 109 for (i = 0; i < nread; i++) 110 { 111 if (pbuf[i] == ' ') 112 { 113 ret = readn(sock_fd, pbuf, i + 1); 114 if (ret != i + 1) 115 exit(EXIT_FAILURE); 116 return (ret); 117 } 118 } 119 120 if (nread > nleft) 121 exit(EXIT_FAILURE); 122 nleft -= nread; 123 124 ret = readn(sock_fd, pbuf, nread); 125 if (ret != nread) exit(EXIT_FAILURE); 126 127 pbuf += nread; 128 } 129 return (-1); 130 } 131 132 void echoclit(int sock_fd) 133 { 134 fd_set rset; 135 FD_ZERO(&rset); 136 137 int nready; 138 int maxfd; 139 int fd_stdin = fileno(stdin); 140 if (fd_stdin > sock_fd) 141 maxfd = fd_stdin; 142 else 143 maxfd = sock_fd; 144 145 char sendbuf[1024]; 146 char recvbuf[1024]; 147 148 while (1) 149 { 150 FD_SET(fd_stdin, &rset); 151 FD_SET(sock_fd, &rset); 152 nready = select(maxfd + 1, &rset, NULL, NULL, NULL); 153 if (nready == -1) 154 ERR_EXIT("select"); 155 if (nready == 0) 156 continue; 157 158 if (FD_ISSET(sock_fd, &rset)) 159 { 160 int ret = readline(sock_fd, recvbuf, 1024); 161 if (ret == -1) 162 ERR_EXIT("readline"); 163 else if (ret == 0) 164 break; 165 166 fputs(recvbuf, stdout); 167 memset(recvbuf, 0, sizeof(recvbuf)); 168 } 169 if (FD_ISSET(fd_stdin, &rset)) 170 { 171 if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) 172 break; 173 writen(sock_fd, sendbuf, strlen(sendbuf)); 174 memset(sendbuf, 0, sizeof(sendbuf)); 175 } 176 } 177 exit(EXIT_SUCCESS); 178 } 179 180 int main(void) 181 { 182 /* 创建连接套接字 */ 183 int sock_fd; 184 if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 185 ERR_EXIT("socket"); 186 187 /* 初始化要连接的服务器地址 */ 188 struct sockaddr_in serv_addr; 189 memset(&serv_addr, 0, sizeof(serv_addr)); 190 serv_addr.sin_family = AF_INET; 191 serv_addr.sin_port = htons(5188); 192 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 193 194 /* 将套接字连接至指定服务器 */ 195 if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 196 ERR_EXIT("connect"); 197 198 echoclit(sock_fd); 199 close(sock_fd); 200 201 return (0); 202 }
并发服务器(6)[poll方式]
1 [root@benxintuzi tcp]# cat echoserv_poll.c 2 #include <poll.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <netinet/in.h> 7 #include <arpa/inet.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <errno.h> 11 #include <stdio.h> 12 13 14 #define ERR_EXIT(message) 15 do 16 { 17 perror(message); 18 exit(EXIT_FAILURE); 19 } while (0) 20 21 22 /* 接收定长数据包 */ 23 size_t readn(int fd, void* buf, size_t count) 24 { 25 /* nleft:剩下未接收的数据量 26 * * * * * nread:每次接收的数据量 */ 27 size_t nleft = count; 28 ssize_t nread; 29 30 while (nleft > 0) /* 如果还有未接收的数据 */ 31 { 32 if ((nread = read(fd, buf, nleft)) == -1) 33 { 34 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 35 return (-1); 36 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 37 break; 38 } 39 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 40 break; 41 else /* 接收数据后更新变量 */ 42 { 43 buf += nread; 44 nleft -= nread; 45 } 46 } 47 48 return (count - nleft); /* 返回成功接收的数据量 */ 49 } 50 51 /* 发送定长数据包 */ 52 ssize_t writen(int fd, const void* buf, size_t count) 53 { 54 size_t nleft = count; 55 ssize_t nwritten; 56 57 while (nleft > 0) 58 { 59 if ((nwritten = write(fd, buf, nleft)) == -1) 60 { 61 if (nleft == count) 62 return (-1); 63 else 64 break; 65 } 66 else if (nwritten == 0) 67 break; 68 else 69 { 70 buf += nwritten; 71 nleft -= nwritten; 72 } 73 } 74 75 return (count - nleft); /* 返回成功发送的数据量 */ 76 } 77 78 79 size_t recv_peek(int listen_fd, void* buf, size_t len) 80 { 81 while (1) 82 { 83 int ret; 84 ret = recv(listen_fd, buf, len, MSG_PEEK); 85 if (ret == -1 && errno == EINTR) 86 continue; 87 return (ret); 88 } 89 } 90 91 ssize_t readline(int listen_fd, void* buf, size_t maxline) 92 { 93 int ret; 94 int nread; 95 char* pbuf = buf; 96 int nleft = maxline; 97 98 while (1) 99 { 100 ret = recv_peek(listen_fd, pbuf, nleft); 101 if (ret < 0) 102 return (ret); 103 else if (ret == 0) 104 return (ret); 105 106 nread = ret; 107 108 int i; 109 for (i = 0; i < nread; i++) 110 { 111 if (pbuf[i] == ' ') 112 { 113 ret = readn(listen_fd, pbuf, i + 1); 114 if (ret != i + 1) 115 exit(EXIT_FAILURE); 116 return (ret); 117 } 118 } 119 120 if (nread > nleft) 121 exit(EXIT_FAILURE); 122 nleft -= nread; 123 124 ret = readn(listen_fd, pbuf, nread); 125 if (ret != nread) 126 exit(EXIT_FAILURE); 127 pbuf += nread; 128 } 129 130 return (-1); 131 } 132 133 void echoserv(int listen_fd) 134 { 135 /** using poll to realize a concurrent server */ 136 struct sockaddr_in clit_addr; 137 memset(&clit_addr, 0, sizeof(clit_addr)); 138 socklen_t clit_len = sizeof(clit_addr); 139 140 struct pollfd client[256]; 141 int maxi = 0; 142 143 int i; 144 for (i = 0; i < 256; i++) 145 client[i].fd = -1; 146 147 int nready; 148 client[0].fd = listen_fd; 149 client[0].events = POLLIN; /* 对监听套接字的可读事件感兴趣 */ 150 151 int conn; 152 while (1) 153 { 154 nready = poll(client, maxi + 1, -1); 155 if (nready == -1) 156 { 157 if (errno == EINTR) 158 continue; 159 else 160 ERR_EXIT("poll"); 161 } 162 if (nready == 0) 163 continue; 164 165 if (client[0].revents & POLLIN) 166 { 167 conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len); 168 if (conn == -1) 169 ERR_EXIT("accept"); 170 for (i = 0; i < 256; i++) 171 { 172 if (client[i].fd < 0) /* 寻找空闲位置保存连接 */ 173 { 174 client[i].fd = conn; 175 if (i > maxi) 176 maxi = i; 177 break; 178 } 179 } 180 if (i == 256) 181 { 182 fprintf(stderr, "too many clients. "); 183 exit(EXIT_FAILURE); 184 } 185 186 printf("client(ip = %s, port = %d) connected. ",inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 187 188 client[i].events = POLLIN; 189 190 if (--nready <= 0) 191 continue; 192 193 } 194 195 for (i = 1; i <= maxi; i++) 196 { 197 conn = client[i].fd; 198 if (conn == -1) 199 continue; 200 if (client[i].events & POLLIN) 201 { 202 char recvbuf[1024] = {0}; 203 int ret = readline(conn, recvbuf, 1024); 204 if (ret == -1) 205 ERR_EXIT("readline"); 206 if (ret == 0) 207 { 208 printf("client close. "); 209 client[i].fd = -1; 210 close(conn); 211 } 212 213 fputs(recvbuf, stdout); 214 writen(conn, recvbuf, strlen(recvbuf)); 215 if (--nready <= 0) 216 break; 217 } 218 } 219 220 } 221 222 exit(EXIT_SUCCESS); 223 } 224 225 int main(void) 226 { 227 /* 创建一个监听套接字 */ 228 int listen_fd; 229 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 230 ERR_EXIT("socket"); 231 232 /* 初始化服务器地址 */ 233 struct sockaddr_in serv_addr; 234 memset(&serv_addr, 0, sizeof(serv_addr)); 235 serv_addr.sin_family = AF_INET; 236 serv_addr.sin_port = htons(5188); 237 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 238 /** 239 * * * * * serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 240 * * * * * inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 241 242 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 243 int on = 1; 244 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 245 ERR_EXIT("setsockopt"); 246 247 /* 将服务器地址绑定到监听套接字上 */ 248 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 249 ERR_EXIT("bind"); 250 251 /* 监听进入的连接 */ 252 if (listen(listen_fd, SOMAXCONN) == -1) 253 ERR_EXIT("listen"); 254 255 echoserv(listen_fd); 256 close(listen_fd); 257 258 return (0); 259 }
并发客户端(6)---同(5)
并发服务器(7)[epoll方式]
1 [root@benxintuzi tcp]# cat echoserv_epoll.cpp 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <sys/wait.h> 6 #include <sys/epoll.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 #include <fcntl.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <errno.h> 13 #include <stdio.h> 14 15 #include <vector> 16 #include <algorithm> 17 18 typedef std::vector<struct epoll_event> EventList; 19 20 #define ERR_EXIT(message) 21 do 22 { 23 perror(message); 24 exit(EXIT_FAILURE); 25 } while (0) 26 27 28 /* 接收定长数据包 */ 29 size_t readn(int fd, void* buf, size_t count) 30 { 31 /* nleft:剩下未接收的数据量 32 * * * * nread:每次接收的数据量 */ 33 size_t nleft = count; 34 ssize_t nread; 35 36 while (nleft > 0) /* 如果还有未接收的数据 */ 37 { 38 if ((nread = read(fd, buf, nleft)) == -1) 39 { 40 if (nleft == count) /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */ 41 return (-1); 42 else /* 本次read操作失败,返回到目前为止成功接收的数据量 */ 43 break; 44 } 45 else if (nread == 0) /* EOF,说明没有数据可供接收了 */ 46 break; 47 else /* 接收数据后更新变量 */ 48 { 49 buf += nread; 50 nleft -= nread; 51 } 52 } 53 54 return (count - nleft); /* 返回成功接收的数据量 */ 55 } 56 57 /* 发送定长数据包 */ 58 ssize_t writen(int fd, const void* buf, size_t count) 59 { 60 size_t nleft = count; 61 ssize_t nwritten; 62 63 while (nleft > 0) 64 { 65 if ((nwritten = write(fd, buf, nleft)) == -1) 66 { 67 if (nleft == count) 68 return (-1); 69 else 70 break; 71 } 72 else if (nwritten == 0) 73 break; 74 else 75 { 76 buf += nwritten; 77 nleft -= nwritten; 78 } 79 } 80 81 return (count - nleft); /* 返回成功发送的数据量 */ 82 } 83 84 85 size_t recv_peek(int listen_fd, void* buf, size_t len) 86 { 87 while (1) 88 { 89 int ret; 90 ret = recv(listen_fd, buf, len, MSG_PEEK); 91 if (ret == -1 && errno == EINTR) 92 continue; 93 return (ret); 94 } 95 } 96 97 ssize_t readline(int listen_fd, void* buf, size_t maxline) 98 { 99 int ret; 100 int nread; 101 char* pbuf = (char*)buf; 102 int nleft = maxline; 103 104 while (1) 105 { 106 ret = recv_peek(listen_fd, pbuf, nleft); 107 if (ret < 0) 108 return (ret); 109 else if (ret == 0) 110 return (ret); 111 112 nread = ret; 113 114 int i; 115 for (i = 0; i < nread; i++) 116 { 117 if (pbuf[i] == ' ') 118 { 119 ret = readn(listen_fd, pbuf, i + 1); 120 if (ret != i + 1) 121 exit(EXIT_FAILURE); 122 return (ret); 123 } 124 } 125 126 if (nread > nleft) 127 exit(EXIT_FAILURE); 128 nleft -= nread; 129 130 ret = readn(listen_fd, pbuf, nread); 131 if (ret != nread) 132 exit(EXIT_FAILURE); 133 pbuf += nread; 134 } 135 return (-1); 136 } 137 138 void activate_nonblock(int fd) 139 { 140 int flags; 141 if ((flags = fcntl(fd, F_GETFL)) == -1) 142 ERR_EXIT("fcntl"); 143 flags |= O_NONBLOCK; 144 145 int ret; 146 if ((ret = fcntl(fd, F_SETFL, flags)) == -1) 147 ERR_EXIT("fcntl"); 148 } 149 150 void handle_sigchld(int sig) 151 { 152 /* wait(NULL); */ 153 while (waitpid(-1, NULL, WNOHANG) > 0) 154 ; 155 } 156 157 void handle_sigpipe(int sig) 158 { 159 printf("recv a sig = %d. ", sig); 160 } 161 162 void echoserv(int listen_fd, int conn) 163 { 164 std::vector<int> clients; 165 int epoll_fd; 166 epoll_fd = epoll_create1(EPOLL_CLOEXEC); 167 if (epoll_fd == -1) 168 ERR_EXIT("epoll_create1"); 169 170 struct epoll_event event; 171 event.data.fd = listen_fd; 172 event.events = EPOLLIN | EPOLLET; 173 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event); 174 175 EventList events(16); 176 struct sockaddr_in clit_addr; 177 socklen_t clit_len = sizeof(clit_addr); 178 int nready; 179 while (1) 180 { 181 nready = epoll_wait(epoll_fd, &*events.begin(), static_cast<int>(events.size()), -1); 182 if (nready == -1) 183 { 184 if (errno == EINTR) 185 continue; 186 ERR_EXIT("epoll_wait"); 187 } 188 if (nready == 0) 189 continue; 190 if ((size_t)nready == events.size()) /* 如果存储空间已满,则扩充容量 */ 191 events.resize(events.size() * 2); 192 193 int i; 194 for (i = 0; i < nready; i++) 195 { 196 if (events[i].data.fd == listen_fd) 197 { 198 conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len); 199 if (conn == -1) 200 ERR_EXIT("accept"); 201 printf("client(ip = %s, port = %d) connected. ", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 202 clients.push_back(conn); 203 activate_nonblock(conn); /* 设置当前连接为非阻塞模式 */ 204 event.data.fd = conn; 205 event.events = EPOLLIN | EPOLLET; 206 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn, &event); 207 } 208 else if (events[i].events & EPOLLIN) 209 { 210 conn = events[i].data.fd; 211 if (conn < 0) 212 continue; 213 214 char recvbuf[1024] = {0}; 215 int ret = readline(conn, recvbuf, 1024); 216 if (ret == -1) 217 ERR_EXIT("readline"); 218 if (ret == 0) 219 { 220 printf("client closed. "); 221 close(conn); 222 223 event = events[i]; 224 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn, &event); 225 clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end()); 226 } 227 228 fputs(recvbuf, stdout); 229 writen(conn, recvbuf, strlen(recvbuf)); 230 } 231 } 232 } 233 } 234 235 236 237 int main(void) 238 { 239 /* 产生如下信号时,我们可以捕捉并处理,但是通常忽略即可 */ 240 // signal(SIGPIPE, handle_sigpipe); /* 在收到RST段后,再次调用write操作就会产生SIGPIPE信号 */ 241 // signal(SIGCHLD, handle_sigchld); /* 避免僵尸进程 */ 242 signal(SIGPIPE, SIG_IGN); 243 signal(SIGCHLD, SIG_IGN); 244 245 /* 创建一个监听套接字 */ 246 int listen_fd; 247 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 248 ERR_EXIT("socket"); 249 250 /* 初始化服务器地址 */ 251 struct sockaddr_in serv_addr; 252 memset(&serv_addr, 0, sizeof(serv_addr)); 253 serv_addr.sin_family = AF_INET; 254 serv_addr.sin_port = htons(5188); 255 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 256 /** 257 * * * * serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 258 * * * * inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 259 260 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 261 int on = 1; 262 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 263 ERR_EXIT("setsockopt"); 264 265 /* 将服务器地址绑定到监听套接字上 */ 266 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 267 ERR_EXIT("bind"); 268 269 /* 监听进入的连接 */ 270 if (listen(listen_fd, SOMAXCONN) == -1) 271 ERR_EXIT("listen"); 272 273 int conn; 274 echoserv(listen_fd, conn); 275 276 close(listen_fd); 277 278 return (0); 279 }
并发客户端(7)---同(5)
利用线程也可以实现并发功能,如下使用POSIX线程库实现,编译时要加入-pthread选项。传统函数的返回值在失败时一般会返回-1,并设置errno变量。pthreads函数出错时不会设置全局变量errno,而是返回错误码,然后用strerror函数打印与该错误码相关的信息。
并发服务器(8)[线程方式]
1 [root@benxintuzi thread]# cat echoserv_thread.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <pthread.h> 10 #include <errno.h> 11 #include <stdio.h> 12 13 #define ERR_EXIT(message) 14 do 15 { 16 perror(message); 17 exit(EXIT_FAILURE); 18 } while(0) 19 20 21 void echoserv(int conn) 22 { 23 char recvbuf[1024]; 24 while (1) 25 { 26 memset(recvbuf, 0, sizeof(recvbuf)); 27 int ret; 28 if ((ret = read(conn, recvbuf, sizeof(recvbuf))) < 0) 29 ERR_EXIT("read"); 30 if (ret == 0) /* client closed */ 31 { 32 printf("client closed. "); 33 break; 34 } 35 fputs(recvbuf, stdout); 36 if (write(conn, recvbuf, ret) != ret) 37 ERR_EXIT("write"); 38 } 39 40 } 41 42 void* thread_routine(void* arg) 43 { 44 int conn = (int)arg; 45 echoserv(conn); 46 printf("exit thread. "); 47 48 return NULL; 49 } 50 51 int main(void) 52 { 53 int sock_fd; 54 if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) 55 ERR_EXIT("socket"); 56 57 struct sockaddr_in serv_addr; 58 memset(&serv_addr, 0, sizeof(serv_addr)); 59 serv_addr.sin_family = AF_INET; 60 serv_addr.sin_port = htons(5188); 61 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 62 63 int on = 1; 64 if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 65 ERR_EXIT("setsockopt"); 66 67 if (bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) 68 ERR_EXIT("bind"); 69 70 if (listen(sock_fd, SOMAXCONN) < 0) 71 ERR_EXIT("listen"); 72 struct sockaddr_in peer_addr; 73 socklen_t peer_len = sizeof(peer_addr); 74 int conn; 75 while (1) 76 { 77 if ((conn = accept(sock_fd, (struct sockaddr*)&peer_addr, &peer_len)) < 0) 78 ERR_EXIT("accept"); 79 printf("client(ip = %s, port = %d) connected. ", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port)); 80 81 pthread_t tid; 82 int ret; 83 ret = pthread_create(&tid, NULL, thread_routine, (void*)conn); 84 if (ret != 0) 85 { 86 fprintf(stderr, "pthread_create: %s. ", strerror(ret)); 87 exit(EXIT_FAILURE); 88 } 89 } 90 91 return 0; 92 }
并发客户端(8)---同(1)
与tcp相比,udp有时显得更为高效。但是udp报文可能会丢失、重复、乱序,其缺乏流量控制,upd编程中,recvfrom返回0并不代表连接关闭,因为udp是无连接的。
并发服务器(9)[udp]
1 [root@benxintuzi udp]# cat echoserv.c 2 #include <netinet/in.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <unistd.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <stdio.h> 9 #include <errno.h> 10 11 #define ERR_EXIT(msg) 12 do 13 { 14 perror(msg); 15 exit(EXIT_FAILURE); 16 } while (0) 17 18 void echoserv(int sock_fd) 19 { 20 char recv_buf[1024] = {0}; 21 struct sockaddr_in peer_addr; 22 socklen_t peer_len; 23 int n; 24 while (1) 25 { 26 peer_len = sizeof(peer_addr); 27 memset(recv_buf, 0, sizeof(recv_buf)); 28 29 if ((n = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&peer_addr, &peer_len)) == -1) 30 { 31 if (errno == EINTR) 32 continue; 33 else 34 ERR_EXIT("recvfrom"); 35 } 36 else if (n == 0) 37 { 38 /* recvfrom返回0不代表连接关闭,因为udp是无连接的 */ 39 } 40 else 41 { 42 fputs(recv_buf, stdout); 43 sendto(sock_fd, recv_buf, n, 0, (struct sockaddr*)&peer_addr, peer_len); 44 } 45 } 46 47 close(sock_fd); 48 } 49 50 int main(void) 51 { 52 int sock_fd; 53 if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 54 ERR_EXIT("socket"); 55 56 struct sockaddr_in serv_addr; 57 memset(&serv_addr, 0, sizeof(serv_addr)); 58 serv_addr.sin_family = AF_INET; 59 serv_addr.sin_port = htons(5188); 60 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 61 62 if (bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) 63 ERR_EXIT("bind"); 64 65 echoserv(sock_fd); 66 67 return 0; 68 }
并发客户端(9)[udp]
1 [root@benxintuzi udp]# cat echoclit.c 2 #include <netinet/in.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <unistd.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <stdio.h> 9 #include <errno.h> 10 11 #define ERR_EXIT(msg) 12 do 13 { 14 perror(msg); 15 exit(EXIT_FAILURE); 16 } while (0) 17 18 19 void echoclit(int sock_fd) 20 { 21 22 struct sockaddr_in serv_addr; 23 memset(&serv_addr, 0, sizeof(serv_addr)); 24 serv_addr.sin_family = AF_INET; 25 serv_addr.sin_port = htons(5188); 26 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 27 if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 28 ERR_EXIT("connect"); 29 30 char send_buf[1024] = {0}; 31 char recv_buf[1024] = {0}; 32 while (fgets(send_buf, sizeof(send_buf), stdin) != NULL) 33 { 34 /** sendto(sock_fd, send_buf, sizeof(send_buf), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); */ 35 /** 使用connect后,可以使用send代替sendto发送地址,因为connect已经指定了地址,就不再需要sendto中的地址了 */ 36 send(sock_fd, send_buf, sizeof(send_buf), 0); 37 38 /** recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, NULL, NULL); */ 39 recv(sock_fd, recv_buf, sizeof(recv_buf), 0); 40 fputs(recv_buf, stdout); 41 memset(send_buf, 0, sizeof(send_buf)); 42 memset(recv_buf, 0, sizeof(recv_buf)); 43 } 44 45 exit(EXIT_SUCCESS); 46 } 47 48 int main(void) 49 { 50 int sock_fd; 51 if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 52 ERR_EXIT("socket"); 53 54 echoclit(sock_fd); 55 close(sock_fd); 56 57 return 0; 58 }