benxintuzi

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

如下介绍一个并发回射客户端/服务器的雏形,所谓回射:就是客户端输入一条数据,服务器端读取并显示,然后服务器端再把刚读取的信息发送回客户端进行显示。示意图如下:

 

所谓并发服务器:就是一个服务器可以同时为多个连入的客户端提供服务,示意图如下:

 

如下主要介绍两种实现并发回射服务器的方式,一种是通过子进程方式实现并发,一种是通过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

 

posted on 2015-09-19 15:25  benxintuzi  阅读(3405)  评论(5编辑  收藏  举报