Linux 並發服務器雛形總結


如下介紹一個並發回射客戶端/服務器的雛形,所謂回射:就是客戶端輸入一條數據,服務器端讀取並顯示,然后服務器端再把剛讀取的信息發送回客戶端進行顯示。示意圖如下:

 

所謂並發服務器:就是一個服務器可以同時為多個連入的客戶端提供服務,示意圖如下:

 

如下主要介紹兩種實現並發回射服務器的方式,一種是通過子進程方式實現並發,一種是通過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 }
View Code

並發客戶端(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 }
View Code

 

(UDPSOCK_SEQPACKET套接字提供了基於報文的服務,這意味着接收的數據量和發送的數據量完全一致,因此應用程序可以很容易區分出報文的邊界。而TCPSOCK_STREAM套接字提供了字節流服務,應用程序不能分辨出報文的邊界,這樣就很容易導致粘包問題。具體解決方案主要是靠應用層維護消息與消息之間的邊界。有如下幾種:

  1. 發送/接收定長包
  2. 在包尾加上\r\n(如ftp就是這樣做的)
  3. 在包頭封裝數據的長度
  4. 依賴於更復雜的應用層協議

如下為封裝好的發送/接收定長數據包的函數:

 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 }
View Code

並發客戶端(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 }
View Code

 

如下解決粘包問題采用自定義數據包頭,在包頭中封裝一個數據的長度與存放數據的緩存區:

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 }
View Code

並發客戶端(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 }
View Code

 

用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 }
View Code

並發客戶端(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 }
View Code

 

當從一個文件描述符讀,然后又寫到另一個文件描述符時,一般在下列形式的循環中使用阻塞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 }
View Code

並發客戶端(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 }
View Code

 

並發服務器(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 }
View Code

並發客戶端(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 }
View Code

並發客戶端(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 }
View Code

並發客戶端(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 }
View Code

並發客戶端(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 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM