如下介紹一個並發回射客戶端/服務器的雛形,所謂回射:就是客戶端輸入一條數據,服務器端讀取並顯示,然后服務器端再把剛讀取的信息發送回客戶端進行顯示。示意圖如下:
所謂並發服務器:就是一個服務器可以同時為多個連入的客戶端提供服務,示意圖如下:
如下主要介紹兩種實現並發回射服務器的方式,一種是通過子進程方式實現並發,一種是通過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.\n"); 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.\n", 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套接字提供了字節流服務,應用程序不能分辨出報文的邊界,這樣就很容易導致粘包問題。具體解決方案主要是靠應用層維護消息與消息之間的邊界。有如下幾種:
- 發送/接收定長包
- 在包尾加上\r\n(如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.\n", 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.\n"); 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.\n"); 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.\n", 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] == '\n') 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] == '\n') 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.\n"); 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.\n", 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] == '\n') 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.\n”);
這種形式的阻塞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] == '\n') 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.\n"); 185 exit(EXIT_FAILURE); 186 } 187 188 printf("client(ip = %s, port = %d) connected.\n",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.\n"); 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] == '\n') 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] == '\n') 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.\n"); 183 exit(EXIT_FAILURE); 184 } 185 186 printf("client(ip = %s, port = %d) connected.\n",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.\n"); 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] == '\n') 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.\n", 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.\n", 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.\n"); 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.\n"); 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.\n"); 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.\n", 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.\n", 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 }