Linux 并发服务器雏形总结

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

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

如下主要介绍两种实现并发回射服务器的方式,一种是通过子进程方式实现并发,一种是通过I/O多路转接实现并发。

并发服务器(1)[子进程方式]

  1 [[email protected] tcp]# cat echoserv_childprocess.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12 #define ERR_EXIT(message)        13         do       14         {        15                 perror(message);         16                 exit(EXIT_FAILURE);      17         } while(0)
 18
 19 /* 回射服务 */
 20 void echoserv(int conn)
 21 {
 22         char recvbuf[1024];
 23         while (1)
 24         {
 25                 memset(recvbuf, 0, sizeof(recvbuf));
 26                 int ret;
 27                 if ((ret = read(conn, recvbuf, sizeof(recvbuf))) == -1)
 28                         ERR_EXIT("read");
 29                 if (ret == 0)   /* client closed */
 30                 {
 31                         printf("client closed.\n");
 32                         break;
 33                 }
 34
 35                 fputs(recvbuf, stdout);
 36                 if (write(conn, recvbuf, ret) != ret)
 37                         ERR_EXIT("write");
 38         }
 39         exit(EXIT_SUCCESS);
 40 }
 41
 42
 43 int main(void)
 44 {
 45         /* 创建一个监听套接字 */
 46         int listen_fd;
 47         if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
 48                 ERR_EXIT("socket");
 49
 50         /* 初始化服务器地址 */
 51         struct sockaddr_in serv_addr;
 52         memset(&serv_addr, 0, sizeof(serv_addr));
 53         serv_addr.sin_family = AF_INET;
 54         serv_addr.sin_port = htons(5188);
 55         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 56         /**
 57         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
 58         inet_aton("127.0.0.0", &serv_addr.sin_addr); */
 59
 60         /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */
 61         int on = 1;
 62         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
 63                 ERR_EXIT("setsockopt");
 64
 65         /* 将服务器地址绑定到监听套接字上 */
 66         if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
 67                 ERR_EXIT("bind");
 68
 69         /* 监听进入的连接 */
 70         if (listen(listen_fd, SOMAXCONN) == -1)
 71                 ERR_EXIT("listen");
 72
 73         /* 初始化一个客户端地址用于保存接入的客户端 */
 74         struct sockaddr_in clit_addr;
 75         memset(&clit_addr, 0, sizeof(clit_addr));
 76         socklen_t clit_len = sizeof(clit_addr);
 77
 78         pid_t pid;
 79         int conn;
 80         while (1)
 81         {
 82                 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */
 83                 /* 将返回的客户端连接保存在clit_addr中 */
 84                 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1)
 85                         ERR_EXIT("accept");
 86                         printf("client(ip = %s, port = %d) connected.\n", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port));
 87
 88                 /* 创建子进程用于回射服务 */
 89                 if (( pid = fork()) == -1)
 90                         ERR_EXIT("fork");
 91                 if (pid == 0)   /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */
 92                 {
 93                         /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */
 94                         close(listen_fd);
 95
 96                         /* 进行回射服务 */
 97                         echoserv(conn);
 98                 }
 99                 else    /* 父进程 */
100                         close(conn);
101         }
102
103         return 0;
104 }

并发客户端(1)

 1 [[email protected] 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 }

(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 [[email protected] tcp]# cat echoserv_childprocess_n.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12 #define ERR_EXIT(message)        13         do       14         {        15                 perror(message);         16                 exit(EXIT_FAILURE);      17         } while (0)
 18
 19 /* 接收定长数据包 */
 20 size_t readn(int fd, void* buf, size_t count)
 21 {
 22         /* nleft:剩下未接收的数据量
 23  *         nread:每次接收的数据量 */
 24         size_t  nleft = count;
 25         ssize_t nread;
 26
 27         while (nleft > 0)       /* 如果还有未接收的数据 */
 28         {
 29                 if ((nread = read(fd, buf, nleft)) == -1)
 30                 {
 31                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 32                                 return (-1);
 33                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 34                                 break;
 35                 }
 36                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 37                         break;
 38                 else    /* 接收数据后更新变量 */
 39                 {
 40                         buf += nread;
 41                         nleft -= nread;
 42                 }
 43         }
 44
 45         return (count - nleft);         /* 返回成功接收的数据量 */
 46 }
 47
 48 /* 发送定长数据包 */
 49 ssize_t writen(int fd, const void* buf, size_t count)
 50 {
 51         size_t  nleft = count;
 52         ssize_t nwritten;
 53
 54         while (nleft > 0)
 55         {
 56                 if ((nwritten = write(fd, buf, nleft)) == -1)
 57                 {
 58                         if (nleft == count)
 59                                 return (-1);
 60                         else
 61                                 break;
 62                 }
 63                 else if (nwritten == 0)
 64                         break;
 65                 else
 66                 {
 67                         buf += nwritten;
 68                         nleft -= nwritten;
 69                 }
 70         }
 71
 72         return (count - nleft);         /* 返回成功发送的数据量 */
 73 }
 74
 75 void echoserv(int conn)
 76 {
 77         char recvbuf[1024];
 78         while (1)
 79         {
 80                 memset(recvbuf, 0, sizeof(recvbuf));
 81                 int ret;
 82                 if ((ret = readn(conn, recvbuf, sizeof(recvbuf))) == -1)
 83                         ERR_EXIT("readn");
 84
 85                 fputs(recvbuf, stdout);
 86                 if (writen(conn, recvbuf, ret) == -1)
 87                         ERR_EXIT("writen");
 88         }
 89
 90         exit(EXIT_SUCCESS);
 91 }
 92
 93
 94 int main(void)
 95 {
 96         /* 创建一个监听套接字 */
 97         int listen_fd;
 98         if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
 99                 ERR_EXIT("socket");
100
101         /* 初始化服务器地址 */
102         struct sockaddr_in serv_addr;
103         memset(&serv_addr, 0, sizeof(serv_addr));
104         serv_addr.sin_family = AF_INET;
105         serv_addr.sin_port = htons(5188);
106         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
107         /**
108  *         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
109  *                 inet_aton("127.0.0.0", &serv_addr.sin_addr); */
110
111         /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */
112         int on = 1;
113         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
114                 ERR_EXIT("setsockopt");
115
116         /* 将服务器地址绑定到监听套接字上 */
117         if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
118                 ERR_EXIT("bind");
119
120         /* 监听进入的连接 */
121         if (listen(listen_fd, SOMAXCONN) == -1)
122                 ERR_EXIT("listen");
123
124         /* 初始化一个客户端地址用于保存接入的客户端 */
125         struct sockaddr_in clit_addr;
126         memset(&clit_addr, 0, sizeof(clit_addr));
127         socklen_t clit_len = sizeof(clit_addr);
128
129         pid_t pid;
130         int conn;
131         while (1)
132         {
133                 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */
134                 /* 将返回的客户端连接保存在clit_addr中 */
135                 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1)
136                         ERR_EXIT("accept");
137                         printf("client(ip = %s, port = %d) connected.\n", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port));
138
139                 /* 创建子进程用于回射服务 */
140                 if (( pid = fork()) == -1)
141                         ERR_EXIT("fork");
142                 if (pid == 0)   /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */
143                 {
144                         /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */
145                         close(listen_fd);
146
147                         /* 进行回射服务 */
148                         echoserv(conn);
149                 }
150                 else    /* 父进程 */
151                         close(conn);
152         }
153
154         return 0;
155 }

并发客户端(2)[利用发送定长包解决粘包问题]

  1 [[email protected]intuzi tcp]# cat echoclit_n.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12 #define ERR_EXIT(message)  13     do  14     {  15         perror(message);  16         exit(EXIT_FAILURE);  17     } while(0)
 18
 19
 20 /* 接收定长数据包 */
 21 size_t readn(int fd, void* buf, size_t count)
 22 {
 23         /* nleft:剩下未接收的数据量
 24  *  *         nread:每次接收的数据量 */
 25         size_t  nleft = count;
 26         ssize_t nread;
 27
 28         while (nleft > 0)       /* 如果还有未接收的数据 */
 29         {
 30                 if ((nread = read(fd, buf, nleft)) == -1)
 31                 {
 32                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 33                                 return (-1);
 34                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 35                                 break;
 36                 }
 37                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 38                         break;
 39                 else    /* 接收数据后更新变量 */
 40                 {
 41                         buf += nread;
 42                         nleft -= nread;
 43                 }
 44         }
 45
 46         return (count - nleft);         /* 返回成功接收的数据量 */
 47 }
 48
 49 /* 发送定长数据包 */
 50 ssize_t writen(int fd, const void* buf, size_t count)
 51 {
 52         size_t  nleft = count;
 53         ssize_t nwritten;
 54
 55         while (nleft > 0)
 56         {
 57                 if ((nwritten = write(fd, buf, nleft)) == -1)
 58                 {
 59                         if (nleft == count)
 60                                 return (-1);
 61                         else
 62                                 break;
 63                 }
 64                 else if (nwritten == 0)
 65                         break;
 66                 else
 67                 {
 68                         buf += nwritten;
 69                         nleft -= nwritten;
 70                 }
 71         }
 72
 73         return (count - nleft);         /* 返回成功发送的数据量 */
 74 }
 75
 76 void echoclit(int sock_fd)
 77 {
 78         /* 创建一个发送缓冲区和一个接收缓冲区 */
 79         char sendbuf[1024], recvbuf[1024];
 80         /* 从标准输入读取数据,存入发送缓冲区 */
 81         while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
 82         {
 83                 /* 将发送缓冲区的数据写到套接字指定的服务器 */
 84                 int ret;
 85                 if ((writen(sock_fd, sendbuf, sizeof(sendbuf))) == -1)
 86                         ERR_EXIT("writen");
 87
 88                 /* 将服务器返回的数据存入接收缓冲区 */
 89                 if ((readn(sock_fd, recvbuf, sizeof(recvbuf))) == -1)
 90                         ERR_EXIT("recvbuf");
 91
 92                 /* 将接收缓冲区的数据打印到标准输出 */
 93                 fputs(recvbuf, stdout);
 94                 /* 清空数据缓冲区 */
 95                 memset(sendbuf, 0, sizeof(sendbuf));
 96                 memset(recvbuf, 0, sizeof(recvbuf));
 97         }
 98 }
 99
100
101 int main(void)
102 {
103         /* 创建连接套接字 */
104         int sock_fd;
105         if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
106                 ERR_EXIT("socket");
107
108         /* 初始化要连接的服务器地址 */
109         struct sockaddr_in serv_addr;
110         memset(&serv_addr, 0, sizeof(serv_addr));
111         serv_addr.sin_family = AF_INET;
112         serv_addr.sin_port = htons(5188);
113         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
114
115         /* 将套接字连接至指定服务器 */
116         if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
117                 ERR_EXIT("connect");
118
119         echoclit(sock_fd);
120         close(sock_fd);
121
122         return 0;
123 }

如下解决粘包问题采用自定义数据包头,在包头中封装一个数据的长度与存放数据的缓存区:

struct packet      /* 包头 */

{

  int len;      /* 表示数据的长度 */

  char buf[1024];  /* 数据缓存区 */

};

并发服务器(3)[子进程方式][利用自定义包头解决粘包问题]

  1 [[email protected] tcp]# cat echoserv_childprocess_packet.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12
 13 #define ERR_EXIT(message)        14         do       15         {        16                 perror(message);         17                 exit(EXIT_FAILURE);      18         } while (0)
 19
 20 /* 自定义包头 */
 21 struct packet
 22 {
 23     int len;            /* 数据长度 */
 24     char buf[1024];     /* 数据缓存区 */
 25 };
 26
 27
 28 /* 接收定长数据包 */
 29 size_t readn(int fd, void* buf, size_t count)
 30 {
 31         /* nleft:剩下未接收的数据量
 32 *          nread:每次接收的数据量 */
 33         size_t  nleft = count;
 34         ssize_t nread;
 35
 36         while (nleft > 0)       /* 如果还有未接收的数据 */
 37         {
 38                 if ((nread = read(fd, buf, nleft)) == -1)
 39                 {
 40                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 41                                 return (-1);
 42                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 43                                 break;
 44                 }
 45                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 46                         break;
 47                 else    /* 接收数据后更新变量 */
 48                 {
 49                         buf += nread;
 50                         nleft -= nread;
 51                 }
 52         }
 53
 54         return (count - nleft);         /* 返回成功接收的数据量 */
 55 }
 56
 57 /* 发送定长数据包 */
 58 ssize_t writen(int fd, const void* buf, size_t count)
 59 {
 60         size_t  nleft = count;
 61         ssize_t nwritten;
 62
 63         while (nleft > 0)
 64         {
 65                 if ((nwritten = write(fd, buf, nleft)) == -1)
 66                 {
 67                         if (nleft == count)
 68                                 return (-1);
 69                         else
 70                                 break;
 71                 }
 72                 else if (nwritten == 0)
 73                         break;
 74                 else
 75                 {
 76                         buf += nwritten;
 77                         nleft -= nwritten;
 78                 }
 79         }
 80
 81         return (count - nleft);         /* 返回成功发送的数据量 */
 82 }
 83
 84 void echoserv(int conn)
 85 {
 86         struct packet recvbuf;
 87         int n;
 88         while (1)
 89         {
 90                 memset(&recvbuf, 0, sizeof(recvbuf));
 91
 92                 /* 先接收包头中的数据长度字段 */
 93                 int ret;
 94                 if ((ret = readn(conn, &recvbuf.len, 4)) == -1)
 95                         ERR_EXIT("readn");
 96                 else if (ret < 4)
 97                 {
 98                         printf("client closed.\n");
 99                         break;
100                 }
101                 else
102                 {
103                         n = ntohl(recvbuf.len);         /* 取出数据长度 */
104                         if ((ret = readn(conn, recvbuf.buf, n)) == -1)
105                                 ERR_EXIT("readn");
106                         else if (ret < n)
107                         {
108                                 printf("client closed.\n");
109                                 break;
110                         }
111
112                         fputs(recvbuf.buf, stdout);     /* 服务器端输出 */
113                         if ((ret = writen(conn, &recvbuf, 4 + n)) == -1)        /* 回射到客户端 */
114                                 ERR_EXIT("writen");
115                 }
116         }
117
118         exit(EXIT_SUCCESS);
119 }
120
121
122
123 int main(void)
124 {
125         /* 创建一个监听套接字 */
126         int listen_fd;
127         if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
128                 ERR_EXIT("socket");
129
130         /* 初始化服务器地址 */
131         struct sockaddr_in serv_addr;
132         memset(&serv_addr, 0, sizeof(serv_addr));
133         serv_addr.sin_family = AF_INET;
134         serv_addr.sin_port = htons(5188);
135         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
136         /**
137  *  *         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
138  *   *                 inet_aton("127.0.0.0", &serv_addr.sin_addr); */
139
140         /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */
141         int on = 1;
142         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
143                 ERR_EXIT("setsockopt");
144
145         /* 将服务器地址绑定到监听套接字上 */
146         if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
147                 ERR_EXIT("bind");
148
149         /* 监听进入的连接 */
150         if (listen(listen_fd, SOMAXCONN) == -1)
151                 ERR_EXIT("listen");
152
153         /* 初始化一个客户端地址用于保存接入的客户端 */
154         struct sockaddr_in clit_addr;
155         memset(&clit_addr, 0, sizeof(clit_addr));
156         socklen_t clit_len = sizeof(clit_addr);
157
158         pid_t pid;
159         int conn;
160         while (1)
161         {
162                 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */
163                 /* 将返回的客户端连接保存在clit_addr中 */
164                 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1)
165                         ERR_EXIT("accept");
166                         printf("client(ip = %s, port = %d) connected.\n", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port));
167
168                 /* 创建子进程用于回射服务 */
169                 if (( pid = fork()) == -1)
170                         ERR_EXIT("fork");
171                 if (pid == 0)   /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */
172                 {
173                         /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */
174                         close(listen_fd);
175
176                         /* 进行回射服务 */
177                         echoserv(conn);
178                 }
179                 else    /* 父进程 */
180                         close(conn);
181         }
182
183         return 0;
184 }

并发客户端(3)[利用自定义包头解决粘包问题]

  1 [[email protected] tcp]# cat echoclit_packet.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12 #define ERR_EXIT(message)        13         do       14         {        15                 perror(message);         16                 exit(EXIT_FAILURE);      17         } while (0)
 18
 19 /* 自定义包头 */
 20 struct packet
 21 {
 22     int len;            /* 数据长度 */
 23     char buf[1024];     /* 数据缓存区 */
 24 };
 25
 26
 27 /* 接收定长数据包 */
 28 size_t readn(int fd, void* buf, size_t count)
 29 {
 30         /* nleft:剩下未接收的数据量
 31  * *          nread:每次接收的数据量 */
 32         size_t  nleft = count;
 33         ssize_t nread;
 34
 35         while (nleft > 0)       /* 如果还有未接收的数据 */
 36         {
 37                 if ((nread = read(fd, buf, nleft)) == -1)
 38                 {
 39                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 40                                 return (-1);
 41                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 42                                 break;
 43                 }
 44                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 45                         break;
 46                 else    /* 接收数据后更新变量 */
 47                 {
 48                         buf += nread;
 49                         nleft -= nread;
 50                 }
 51         }
 52
 53         return (count - nleft);         /* 返回成功接收的数据量 */
 54 }
 55
 56 /* 发送定长数据包 */
 57 ssize_t writen(int fd, const void* buf, size_t count)
 58 {
 59         size_t  nleft = count;
 60         ssize_t nwritten;
 61
 62         while (nleft > 0)
 63         {
 64                 if ((nwritten = write(fd, buf, nleft)) == -1)
 65                 {
 66                         if (nleft == count)
 67                                 return (-1);
 68                         else
 69                                 break;
 70                 }
 71                 else if (nwritten == 0)
 72                         break;
 73                 else
 74                 {
 75                         buf += nwritten;
 76                         nleft -= nwritten;
 77                 }
 78         }
 79
 80         return (count - nleft);         /* 返回成功发送的数据量 */
 81 }
 82
 83
 84 void echoclit(int sock_fd)
 85 {
 86         struct packet sendbuf;
 87         struct packet recvbuf;
 88         memset(&sendbuf, 0, sizeof(sendbuf));
 89         memset(&recvbuf, 0, sizeof(recvbuf));
 90
 91         int n;
 92         while (fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
 93         {
 94                 n = sizeof(sendbuf.buf);
 95                 sendbuf.len = htonl(n);
 96                 int ret;
 97                 if ((ret = writen(sock_fd, &sendbuf, 4 + n)) == -1)
 98                         ERR_EXIT("writen");
 99
100                 if ((ret = readn(sock_fd, &recvbuf.len, 4)) == -1)
101                         ERR_EXIT("readn");
102                 else if (ret < 4)
103                         break;
104                 else
105                 {
106                         n = ntohl(recvbuf.len);
107                         if ((ret = readn(sock_fd, &recvbuf.buf, n)) == -1)
108                                 ERR_EXIT("readn");
109                         else if (ret < n)
110                                 break;
111
112                         fputs(recvbuf.buf, stdout);
113                         memset(&sendbuf, 0, sizeof(sendbuf));
114                         memset(&recvbuf, 0, sizeof(recvbuf));
115                 }
116         }
117 }
118
119
120 int main(void)
121 {
122         /* 创建连接套接字 */
123         int sock_fd;
124         if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
125                 ERR_EXIT("socket");
126
127         /* 初始化要连接的服务器地址 */
128         struct sockaddr_in serv_addr;
129         memset(&serv_addr, 0, sizeof(serv_addr));
130         serv_addr.sin_family = AF_INET;
131         serv_addr.sin_port = htons(5188);
132         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
133
134         /* 将套接字连接至指定服务器 */
135         if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
136                 ERR_EXIT("connect");
137
138         echoclit(sock_fd);
139         close(sock_fd);
140
141         return 0;
142 }

用recv代替read时,如果指定MSG_PEEK标志,那么recv函数在返回缓冲区数据的同时仍然保留该部分数据,并不从缓冲区队列中删除,这样下一次读取时,将依然返回同样的数据。如下封装一个recv_peek函数:

 1 size_t recv_peek(int listen_fd, void* buf, size_t len)
 2 {
 3         while (1)
 4         {
 5                 int ret;
 6                 ret = recv(listen_fd, buf, len, MSG_PEEK);
 7                 if (ret == -1 && errno == EINTR)
 8                         continue;
 9                 return (ret);
10         }
11 }

用recv_peek来实现readline函数的功能:

 1 ssize_t readline(int listen_fd, void* buf, size_t maxline)
 2 {
 3         int ret;
 4         int nread;
 5         char* pbuf = buf;
 6         int nleft = maxline;
 7
 8         while (1)
 9         {
10                 ret = recv_peek(listen_fd, pbuf, nleft);
11                 if (ret < 0)
12                         return (ret);
13                 else if (ret == 0)
14                         return (ret);
15
16                 nread = ret;
17
18                 int i;
19                 for (i = 0; i < nread; i++)
20                 {
21                         if (pbuf[i] == ‘\n‘)
22                         {
23                                 ret = readn(listen_fd, pbuf, i + 1);
24                                 if (ret != i + 1)
25                                         exit(EXIT_FAILURE);
26                                 return (ret);
27                         }
28                 }
29
30                 if (nread > nleft)
31                         exit(EXIT_FAILURE);
32                 nleft -= nread;
33
34                 ret = readn(listen_fd, pbuf, nread);
35                 if (ret != nread)
36                         exit(EXIT_FAILURE);
37                 pbuf += nread;
38         }
39
40         return (-1);
41 }

并发服务器(4)[子进程方式][利用readline函数实现]

  1 [[email protected] tcp]# cat echoserv_childprocess_readline.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12
 13 #define ERR_EXIT(message)        14         do       15         {        16                 perror(message);         17                 exit(EXIT_FAILURE);      18         } while (0)
 19
 20
 21 /* 接收定长数据包 */
 22 size_t readn(int fd, void* buf, size_t count)
 23 {
 24         /* nleft:剩下未接收的数据量
 25  *  * *          nread:每次接收的数据量 */
 26         size_t  nleft = count;
 27         ssize_t nread;
 28
 29         while (nleft > 0)       /* 如果还有未接收的数据 */
 30         {
 31                 if ((nread = read(fd, buf, nleft)) == -1)
 32                 {
 33                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 34                                 return (-1);
 35                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 36                                 break;
 37                 }
 38                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 39                         break;
 40                 else    /* 接收数据后更新变量 */
 41                 {
 42                         buf += nread;
 43                         nleft -= nread;
 44                 }
 45         }
 46
 47         return (count - nleft);         /* 返回成功接收的数据量 */
 48 }
 49
 50 /* 发送定长数据包 */
 51 ssize_t writen(int fd, const void* buf, size_t count)
 52 {
 53         size_t  nleft = count;
 54         ssize_t nwritten;
 55
 56         while (nleft > 0)
 57         {
 58                 if ((nwritten = write(fd, buf, nleft)) == -1)
 59                 {
 60                         if (nleft == count)
 61                                 return (-1);
 62                         else
 63                                 break;
 64                 }
 65                 else if (nwritten == 0)
 66                         break;
 67                 else
 68                 {
 69                         buf += nwritten;
 70                         nleft -= nwritten;
 71                 }
 72         }
 73
 74         return (count - nleft);         /* 返回成功发送的数据量 */
 75 }
 76
 77
 78 size_t recv_peek(int listen_fd, void* buf, size_t len)
 79 {
 80         while (1)
 81         {
 82                 int ret;
 83                 ret = recv(listen_fd, buf, len, MSG_PEEK);
 84                 if (ret == -1 && errno == EINTR)
 85                         continue;
 86                 return (ret);
 87         }
 88 }
 89
 90 ssize_t readline(int listen_fd, void* buf, size_t maxline)
 91 {
 92         int ret;
 93         int nread;
 94         char* pbuf = buf;
 95         int nleft = maxline;
 96
 97         while (1)
 98         {
 99                 ret = recv_peek(listen_fd, pbuf, nleft);
100                 if (ret < 0)
101                         return (ret);
102                 else if (ret == 0)
103                         return (ret);
104
105                 nread = ret;
106
107                 int i;
108                 for (i = 0; i < nread; i++)
109                 {
110                         if (pbuf[i] == ‘\n‘)
111                         {
112                                 ret = readn(listen_fd, pbuf, i + 1);
113                                 if (ret != i + 1)
114                                         exit(EXIT_FAILURE);
115                                 return (ret);
116                         }
117                 }
118
119                 if (nread > nleft)
120                         exit(EXIT_FAILURE);
121                 nleft -= nread;
122
123                 ret = readn(listen_fd, pbuf, nread);
124                 if (ret != nread)
125                         exit(EXIT_FAILURE);
126                 pbuf += nread;
127         }
128         return (-1);
129 }
130
131 void echoserv(int conn)
132 {
133         char recvbuf[1024];
134         while (1)
135         {
136                 memset(&recvbuf, 0, sizeof(recvbuf));
137
138                 int ret;
139                 if ((ret = readline(conn, recvbuf, 1024)) == -1)
140                         ERR_EXIT("readline");
141                 else if (ret == 0)
142                 {
143                         printf("client closed.\n");
144                         break;
145                 }
146                 else
147                 {
148                         fputs(recvbuf, stdout);     /* 服务器端输出 */
149                         if ((ret = writen(conn, recvbuf, strlen(recvbuf))) == -1)        /* 回射到客户端 */
150                                 ERR_EXIT("writen");
151                 }
152         }
153
154         exit(EXIT_SUCCESS);
155 }
156
157
158 int main(void)
159 {
160         /* 创建一个监听套接字 */
161         int listen_fd;
162         if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
163                 ERR_EXIT("socket");
164
165         /* 初始化服务器地址 */
166         struct sockaddr_in serv_addr;
167         memset(&serv_addr, 0, sizeof(serv_addr));
168         serv_addr.sin_family = AF_INET;
169         serv_addr.sin_port = htons(5188);
170         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
171         /**
172  *  *  *         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
173  *   *   *                 inet_aton("127.0.0.0", &serv_addr.sin_addr); */
174
175         /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */
176         int on = 1;
177         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
178                 ERR_EXIT("setsockopt");
179
180         /* 将服务器地址绑定到监听套接字上 */
181         if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
182                 ERR_EXIT("bind");
183
184         /* 监听进入的连接 */
185         if (listen(listen_fd, SOMAXCONN) == -1)
186                 ERR_EXIT("listen");
187
188         /* 初始化一个客户端地址用于保存接入的客户端 */
189         struct sockaddr_in clit_addr;
190         memset(&clit_addr, 0, sizeof(clit_addr));
191         socklen_t clit_len = sizeof(clit_addr);
192
193         pid_t pid;
194         int conn;
195         while (1)
196         {
197                 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */
198                 /* 将返回的客户端连接保存在clit_addr中 */
199                 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1)
200                         ERR_EXIT("accept");
201                         printf("client(ip = %s, port = %d) connected.\n", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port));
202
203                 /* 创建子进程用于回射服务 */
204                 if (( pid = fork()) == -1)
205                         ERR_EXIT("fork");
206                 if (pid == 0)   /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */
207                 {
208                         /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */
209                         close(listen_fd);
210
211                         /* 进行回射服务 */
212                         echoserv(conn);
213                 }
214                 else    /* 父进程 */
215                         close(conn);
216         }
217
218         return (0);
219 }

并发客户端(4)[利用readline函数实现]

  1 [[email protected] tcp]# cat echoclit_readline.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12
 13 #define ERR_EXIT(message)        14         do       15         {        16                 perror(message);         17                 exit(EXIT_FAILURE);      18         } while (0)
 19
 20
 21 /* 接收定长数据包 */
 22 size_t readn(int fd, void* buf, size_t count)
 23 {
 24         /* nleft:剩下未接收的数据量
 25  *  *  * *          nread:每次接收的数据量 */
 26         size_t  nleft = count;
 27         ssize_t nread;
 28
 29         while (nleft > 0)       /* 如果还有未接收的数据 */
 30         {
 31                 if ((nread = read(fd, buf, nleft)) == -1)
 32                 {
 33                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 34                                 return (-1);
 35                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 36                                 break;
 37                 }
 38                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 39                         break;
 40                 else    /* 接收数据后更新变量 */
 41                 {
 42                         buf += nread;
 43                         nleft -= nread;
 44                 }
 45         }
 46
 47         return (count - nleft);         /* 返回成功接收的数据量 */
 48 }
 49
 50 /* 发送定长数据包 */
 51 ssize_t writen(int fd, const void* buf, size_t count)
 52 {
 53         size_t  nleft = count;
 54         ssize_t nwritten;
 55
 56         while (nleft > 0)
 57         {
 58                 if ((nwritten = write(fd, buf, nleft)) == -1)
 59                 {
 60                         if (nleft == count)
 61                                 return (-1);
 62                         else
 63                                 break;
 64                 }
 65                 else if (nwritten == 0)
 66                         break;
 67                 else
 68                 {
 69                         buf += nwritten;
 70                         nleft -= nwritten;
 71                 }
 72         }
 73
 74         return (count - nleft);         /* 返回成功发送的数据量 */
 75 }
 76
 77
 78 size_t recv_peek(int listen_fd, void* buf, size_t len)
 79 {
 80         while (1)
 81         {
 82                 int ret;
 83                 ret = recv(listen_fd, buf, len, MSG_PEEK);
 84                 if (ret == -1 && errno == EINTR)
 85                         continue;
 86                 return (ret);
 87         }
 88 }
 89
 90 ssize_t readline(int sock_fd, void* buf, size_t maxline)
 91 {
 92         int ret;
 93         int nread;
 94         char* pbuf = buf;
 95         int nleft = maxline;
 96
 97         while (1)
 98         {
 99                 ret = recv_peek(sock_fd, pbuf, nleft);
100
101                 if (ret < 0)
102                         return (ret);
103                 else if (ret == 0)
104                         return (ret);
105
106                 nread = ret;
107
108                 int i;
109                 for (i = 0; i < nread; i++)
110                 {
111                         if (pbuf[i] == ‘\n‘)
112                         {
113                                 ret = readn(sock_fd, pbuf, i + 1);
114                                 if (ret != i + 1)
115                                         exit(EXIT_FAILURE);
116                                 return (ret);
117                         }
118                 }
119
120                 if (nread > nleft)
121                         exit(EXIT_FAILURE);
122                 nleft -= nread;
123
124                 ret = readn(sock_fd, pbuf, nread);
125                 if (ret != nread)                                                                               exit(EXIT_FAILURE);
126
127                 pbuf += nread;
128         }
129         return (-1);
130 }
131
132 void echoclit(int sock_fd)
133 {
134         char sendbuf[1024];
135         char recvbuf[1024];
136         memset(&sendbuf, 0, sizeof(sendbuf));
137         memset(&recvbuf, 0, sizeof(recvbuf));
138
139         while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
140         {
141                 writen(sock_fd, sendbuf, strlen(sendbuf));
142
143                 int ret;
144                 ret = readline(sock_fd, recvbuf, 1024);
145                 if (ret == -1)
146                         ERR_EXIT("readline");
147                 else if (ret == 0)
148                         break;
149
150                 fputs(recvbuf, stdout);
151                 memset(sendbuf, 0, sizeof(sendbuf));
152                 memset(recvbuf, 0, sizeof(recvbuf));
153         }
154         exit(EXIT_SUCCESS);
155 }
156
157
158 int main(void)
159 {
160         /* 创建连接套接字 */
161         int sock_fd;
162         if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
163                 ERR_EXIT("socket");
164
165         /* 初始化要连接的服务器地址 */
166         struct sockaddr_in serv_addr;
167         memset(&serv_addr, 0, sizeof(serv_addr));
168         serv_addr.sin_family = AF_INET;
169         serv_addr.sin_port = htons(5188);
170         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
171
172         /* 将套接字连接至指定服务器 */
173         if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
174                 ERR_EXIT("connect");
175
176         echoclit(sock_fd);
177         close(sock_fd);
178
179         return (0);
180 }

当从一个文件描述符读,然后又写到另一个文件描述符时,一般在下列形式的循环中使用阻塞I/O:

while ((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0)

  if (write(STDOUT_FILENO, buf, n) != n)

    printf(“write error.\n”);

这种形式的阻塞I/O随处可见,但是如果必须从两个文件描述符读,该如何处理呢?在这种情况下,我们不能在任一个描述符上进行阻塞read,因为可能会因为被阻塞在一个描述符的read操作上而导致另一个描述符即使有数据也无法处理。

一种比较好的技术是使用I/O多路转接(I/O multiplexing)。在这种技术中,先构造一张我们感兴趣的描述符列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。如下介绍多路转接函数select、pselect、poll。

传给select的参数告诉内核:

我们所关心的描述符、关于每个描述符我们所关心的条件、愿意等待多长时间;

内核返回给我们如下信息:

已准备好的描述符的总数量、对于读、写或异常这3个条件中的每一个,哪些描述符已做好准备。

使用这种返回信息,就可调用相应的I/O函数(一般是read或write),并且确知不会发生阻塞情况。

一般使得这3种事件发生的条件如下:

(1)可读

套接字接收缓冲区有数据可读;

连接的读端关闭,即接收到了FIN段,读操作将返回0;

如果是监听套接字,则已完成三次握手的连接队列不为空;

套接字上发生了一个错误待处理,错误可以通过setsockopt指定的SO_ERROR选项来获取;

(2)可写

套接字发送缓冲区中有空间容纳数据;

连接的写端关闭,即收到RST段,再次调用write操作;

套接字上发生了一个错误待处理,错误可以通过setsockopt指定的SO_ERROR选项来获取;

(3)异常

套接字存在带外数据;

与select不同,poll并非为每个条件都构造一个描述符集,而是构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对该描述符感兴趣的条件。

比较:

用select实现的并发服务器,存在两个方面的限制:

(1)   一个进程可以打开的最大文件描述符限制,当然可以通过调整内核参数参数解决(通过ulimit –n命令或者通过getrlimit/setrlimit函数实现);

(2)   select中的fd_set集合容量存在限制(FD_SETSIZE),可以修改内核,但是需要重新编译内核才能生效。

如果用poll实现并发服务器,则不存在select的第二个限制。

与select和poll相比,epoll的最大优势在于其不会随着监听fd数目的增多而降低效率。原因如下:select与poll中,内核采用轮询来处理,轮询的fd数目越多越耗时。而epoll是基于回调实现的,如果某个fd有预期事件发生,立即通过回调函数将其加入epoll就绪队列中,因此其仅关心“活跃”的fd,与fd的总数关系不大。再者,在内核空间与用户空间通信方面,select与poll采用内存拷贝方式,而epoll采用共享内存方式,效率优于前者。最后,epoll不仅会告诉应用程序有I/O事件到来,而且还会告诉应用程序关于事件相关的信息。根据这些信息,应用程序就可以直接定位事件而不必遍历整个fd集合。

epoll执行一个与poll相似的任务:监控多个文件描述符,从而判断它们中的一个或多个是否可以进行I/O操作。

如下系统调用用于创建和管理epoll实例:

(1)   epoll_create: 创建一个epoll实例,返回一个该实例的文件描述符;

(2)   epoll_ctl: 注册感兴趣事件对于某个文件描述符。注册感兴趣事件后的文件描述符集合有时也被称为epoll集合;

(3)   epoll_wait: 等待I/O事件。如果没有事件发生,则阻塞调用线程。

使用epoll的两种模式:

Level-triggered(LT) and edge-triggered(ET)

应用程序使用EPOLLET标志时,应该同时使用非阻塞的文件描述符来避免阻塞读或者阻塞写。

建议如下情况使用epoll时指定EPOLLET标志:

1.  使用非阻塞的文件描述符;

2.  read或者write操作返回EAGIN后等待事件发生;

此模式下,系统仅仅通知应用程序哪些fds变成了就绪状态,一旦fd变成就绪状态,epoll将不再关注这个fd的任何状态信息了(将该fd从epoll队列中移除),直到应用程序通过读写操作触发EAGAIN状态,此时epoll认为这个fd又变为了空闲状态,那么epoll将重新关注该fd的状态变化(将其重新加入epoll队列中)。随着epoll_wait的返回,epoll队列中的fds在逐渐减少,因此在大并发处理中,ET模式更有优势。

相反地,当时用LT模式时,epoll就是一个比较快的poll。此模式下,应用程序只需处理从epoll_wait返回的fds,这些fds我们认为其处于就绪状态。

如下分别采用select、poll、epoll实现并发服务器:

并发服务器(5)[select方式]

  1 [[email protected] tcp]# cat echoserv_select.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12
 13 #define ERR_EXIT(message)        14         do       15         {        16                 perror(message);         17                 exit(EXIT_FAILURE);      18         } while (0)
 19
 20
 21 /* 接收定长数据包 */
 22 size_t readn(int fd, void* buf, size_t count)
 23 {
 24         /* nleft:剩下未接收的数据量
 25  *  *  * *          nread:每次接收的数据量 */
 26         size_t  nleft = count;
 27         ssize_t nread;
 28
 29         while (nleft > 0)       /* 如果还有未接收的数据 */
 30         {
 31                 if ((nread = read(fd, buf, nleft)) == -1)
 32                 {
 33                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 34                                 return (-1);
 35                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 36                                 break;
 37                 }
 38                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 39                         break;
 40                 else    /* 接收数据后更新变量 */
 41                 {
 42                         buf += nread;
 43                         nleft -= nread;
 44                 }
 45         }
 46
 47         return (count - nleft);         /* 返回成功接收的数据量 */
 48 }
 49
 50 /* 发送定长数据包 */
 51 ssize_t writen(int fd, const void* buf, size_t count)
 52 {
 53         size_t  nleft = count;
 54         ssize_t nwritten;
 55
 56         while (nleft > 0)
 57         {
 58                 if ((nwritten = write(fd, buf, nleft)) == -1)
 59                 {
 60                         if (nleft == count)
 61                                 return (-1);
 62                         else
 63                                 break;
 64                 }
 65                 else if (nwritten == 0)
 66                         break;
 67                 else
 68                 {
 69                         buf += nwritten;
 70                         nleft -= nwritten;
 71                 }
 72         }
 73
 74         return (count - nleft);         /* 返回成功发送的数据量 */
 75 }
 76
 77
 78 size_t recv_peek(int listen_fd, void* buf, size_t len)
 79 {
 80         while (1)
 81         {
 82                 int ret;
 83                 ret = recv(listen_fd, buf, len, MSG_PEEK);
 84                 if (ret == -1 && errno == EINTR)
 85                         continue;
 86                 return (ret);
 87         }
 88 }
 89
 90 ssize_t readline(int listen_fd, void* buf, size_t maxline)
 91 {
 92         int ret;
 93         int nread;
 94         char* pbuf = buf;
 95         int nleft = maxline;
 96
 97         while (1)
 98         {
 99                 ret = recv_peek(listen_fd, pbuf, nleft);
100                 if (ret < 0)
101                         return (ret);
102                 else if (ret == 0)
103                         return (ret);
104
105                 nread = ret;
106
107                 int i;
108                 for (i = 0; i < nread; i++)
109                 {
110                         if (pbuf[i] == ‘\n‘)
111                         {
112                                 ret = readn(listen_fd, pbuf, i + 1);
113                                 if (ret != i + 1)
114                                         exit(EXIT_FAILURE);
115                                 return (ret);
116                         }
117                 }
118
119                 if (nread > nleft)
120                         exit(EXIT_FAILURE);
121                 nleft -= nread;
122
123                 ret = readn(listen_fd, pbuf, nread);
124                 if (ret != nread)
125                         exit(EXIT_FAILURE);
126                 pbuf += nread;
127         }
128         return (-1);
129 }
130
131 void echoserv(int listen_fd)
132 {
133         /** using select to realize a concurrent server */
134
135         struct sockaddr_in clit_addr;
136         memset(&clit_addr, 0, sizeof(clit_addr));
137         socklen_t clit_len = sizeof(clit_addr);
138         int conn;
139         int client[FD_SETSIZE];
140         int i;
141         for (i = 0; i < FD_SETSIZE; i++)
142                 client[i] = -1;
143
144         int maxi = 0;
145         int nready;
146         int maxfd = listen_fd;
147         fd_set rset;
148         fd_set allset;
149         FD_ZERO(&rset);
150         FD_ZERO(&allset);
151         FD_SET(listen_fd, &allset);
152
153         while (1)
154         {
155                 rset = allset;
156                 nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
157                 if (nready == -1)
158                 {
159                         if (errno == EINTR)
160                                 continue;
161                         else
162                                 ERR_EXIT("select");
163                 }
164                 if (nready == 0)
165                         continue;
166
167                 if (FD_ISSET(listen_fd, &rset))
168                 {
169                         conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len);
170                         if (conn == -1)
171                                 ERR_EXIT("accept");
172                         for (i = 0; i < FD_SETSIZE; i++)
173                         {
174                                 if (client[i] < 0)
175                                 {
176                                         client[i] = conn;
177                                         if (i > maxi)
178                                                 maxi = i;
179                                         break;
180                                 }
181                         }
182                         if (i == FD_SETSIZE)
183                         {
184                                 fprintf(stderr, "too many clients.\n");
185                                 exit(EXIT_FAILURE);
186                         }
187
188                         printf("client(ip = %s, port = %d) connected.\n",inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port));
189
190                         FD_SET(conn, &allset);
191                         if (conn > maxfd)
192                                 maxfd = conn;
193                         if (--nready <= 0)
194                                 continue;
195                 }
196
197                 for (i = 0; i <= maxi; i++)
198                 {
199                         conn = client[i];
200                         if (conn == -1)
201                                 continue;
202                         if (FD_ISSET(conn, &rset))
203                         {
204                                 char recvbuf[1024] = {0};
205                                 int ret = readline(conn, recvbuf, 1024);
206                                 if (ret == -1)
207                                         ERR_EXIT("readline");
208                                 if (ret == 0)
209                                 {
210                                         printf("client close.\n");
211                                         FD_CLR(conn, &allset);
212                                         client[i] = -1;
213                                         close(conn);
214                                 }
215                                 fputs(recvbuf, stdout);
216                                 writen(conn, recvbuf, strlen(recvbuf));
217                                 if (--nready <= 0)
218                                         break;
219                         }
220                 }
221
222         }
223         exit(EXIT_SUCCESS);
224 }
225
226
227
228 int main(void)
229 {
230         /* 创建一个监听套接字 */
231         int listen_fd;
232         if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
233                 ERR_EXIT("socket");
234
235         /* 初始化服务器地址 */
236         struct sockaddr_in serv_addr;
237         memset(&serv_addr, 0, sizeof(serv_addr));
238         serv_addr.sin_family = AF_INET;
239         serv_addr.sin_port = htons(5188);
240         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
241         /**
242  *  *  *  *         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
243  *   *   *   *                 inet_aton("127.0.0.0", &serv_addr.sin_addr); */
244
245         /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */
246         int on = 1;
247         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
248                 ERR_EXIT("setsockopt");
249
250         /* 将服务器地址绑定到监听套接字上 */
251         if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
252                 ERR_EXIT("bind");
253
254         /* 监听进入的连接 */
255         if (listen(listen_fd, SOMAXCONN) == -1)
256                 ERR_EXIT("listen");
257
258         echoserv(listen_fd);
259
260         close(listen_fd);
261
262         return (0);
263 }

并发客户端(5)

  1 [[email protected] tcp]# cat echoclit_select.c
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <errno.h>
 10 #include <stdio.h>
 11
 12
 13 #define ERR_EXIT(message)        14         do       15         {        16                 perror(message);         17                 exit(EXIT_FAILURE);      18         } while (0)
 19
 20
 21 /* 接收定长数据包 */
 22 size_t readn(int fd, void* buf, size_t count)
 23 {
 24         /* nleft:剩下未接收的数据量
 25  *  *  *  * *          nread:每次接收的数据量 */
 26         size_t  nleft = count;
 27         ssize_t nread;
 28
 29         while (nleft > 0)       /* 如果还有未接收的数据 */
 30         {
 31                 if ((nread = read(fd, buf, nleft)) == -1)
 32                 {
 33                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 34                                 return (-1);
 35                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 36                                 break;
 37                 }
 38                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 39                         break;
 40                 else    /* 接收数据后更新变量 */
 41                 {
 42                         buf += nread;
 43                         nleft -= nread;
 44                 }
 45         }
 46
 47         return (count - nleft);         /* 返回成功接收的数据量 */
 48 }
 49
 50 /* 发送定长数据包 */
 51 ssize_t writen(int fd, const void* buf, size_t count)
 52 {
 53         size_t  nleft = count;
 54         ssize_t nwritten;
 55
 56         while (nleft > 0)
 57         {
 58                 if ((nwritten = write(fd, buf, nleft)) == -1)
 59                 {
 60                         if (nleft == count)
 61                                 return (-1);
 62                         else
 63                                 break;
 64                 }
 65                 else if (nwritten == 0)
 66                         break;
 67                 else
 68                 {
 69                         buf += nwritten;
 70                         nleft -= nwritten;
 71                 }
 72         }
 73
 74         return (count - nleft);         /* 返回成功发送的数据量 */
 75 }
 76
 77
 78 size_t recv_peek(int listen_fd, void* buf, size_t len)
 79 {
 80         while (1)
 81         {
 82                 int ret;
 83                 ret = recv(listen_fd, buf, len, MSG_PEEK);
 84                 if (ret == -1 && errno == EINTR)
 85                         continue;
 86                 return (ret);
 87         }
 88 }
 89
 90 ssize_t readline(int sock_fd, void* buf, size_t maxline)
 91 {
 92         int ret;
 93         int nread;
 94         char* pbuf = buf;
 95         int nleft = maxline;
 96
 97         while (1)
 98         {
 99                 ret = recv_peek(sock_fd, pbuf, nleft);
100
101                 if (ret < 0)
102                         return (ret);
103                 else if (ret == 0)
104                         return (ret);
105
106                 nread = ret;
107
108                 int i;
109                 for (i = 0; i < nread; i++)
110                 {
111                         if (pbuf[i] == ‘\n‘)
112                         {
113                                 ret = readn(sock_fd, pbuf, i + 1);
114                                 if (ret != i + 1)
115                                         exit(EXIT_FAILURE);
116                                 return (ret);
117                         }
118                 }
119
120                 if (nread > nleft)
121                         exit(EXIT_FAILURE);
122                 nleft -= nread;
123
124                 ret = readn(sock_fd, pbuf, nread);
125                 if (ret != nread)                                                                               exit(EXIT_FAILURE);
126
127                 pbuf += nread;
128         }
129         return (-1);
130 }
131
132 void echoclit(int sock_fd)
133 {
134         fd_set  rset;
135         FD_ZERO(&rset);
136
137         int nready;
138         int maxfd;
139         int fd_stdin = fileno(stdin);
140         if (fd_stdin > sock_fd)
141                 maxfd = fd_stdin;
142         else
143                 maxfd = sock_fd;
144
145         char sendbuf[1024];
146         char recvbuf[1024];
147
148         while (1)
149         {
150                 FD_SET(fd_stdin, &rset);
151                 FD_SET(sock_fd, &rset);
152                 nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
153                 if (nready == -1)
154                         ERR_EXIT("select");
155                 if (nready == 0)
156                         continue;
157
158                 if (FD_ISSET(sock_fd, &rset))
159                 {
160                         int ret = readline(sock_fd, recvbuf, 1024);
161                         if (ret == -1)
162                         ERR_EXIT("readline");
163                         else if (ret == 0)
164                                 break;
165
166                         fputs(recvbuf, stdout);
167                         memset(recvbuf, 0, sizeof(recvbuf));
168                 }
169                 if (FD_ISSET(fd_stdin, &rset))
170                 {
171                         if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
172                                 break;
173                         writen(sock_fd, sendbuf, strlen(sendbuf));
174                         memset(sendbuf, 0, sizeof(sendbuf));
175                 }
176         }
177         exit(EXIT_SUCCESS);
178 }
179
180 int main(void)
181 {
182         /* 创建连接套接字 */
183         int sock_fd;
184         if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
185                 ERR_EXIT("socket");
186
187         /* 初始化要连接的服务器地址 */
188         struct sockaddr_in serv_addr;
189         memset(&serv_addr, 0, sizeof(serv_addr));
190         serv_addr.sin_family = AF_INET;
191         serv_addr.sin_port = htons(5188);
192         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
193
194         /* 将套接字连接至指定服务器 */
195         if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
196                 ERR_EXIT("connect");
197
198         echoclit(sock_fd);
199         close(sock_fd);
200
201         return (0);
202 }

并发服务器(6)[poll方式]

  1 [[email protected] tcp]# cat echoserv_poll.c
  2 #include <poll.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/socket.h>
  6 #include <netinet/in.h>
  7 #include <arpa/inet.h>
  8 #include <stdlib.h>
  9 #include <string.h>
 10 #include <errno.h>
 11 #include <stdio.h>
 12
 13
 14 #define ERR_EXIT(message)        15         do       16         {        17                 perror(message);         18                 exit(EXIT_FAILURE);      19         } while (0)
 20
 21
 22 /* 接收定长数据包 */
 23 size_t readn(int fd, void* buf, size_t count)
 24 {
 25         /* nleft:剩下未接收的数据量
 26  *  *  *  * *          nread:每次接收的数据量 */
 27         size_t  nleft = count;
 28         ssize_t nread;
 29
 30         while (nleft > 0)       /* 如果还有未接收的数据 */
 31         {
 32                 if ((nread = read(fd, buf, nleft)) == -1)
 33                 {
 34                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 35                                 return (-1);
 36                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 37                                 break;
 38                 }
 39                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 40                         break;
 41                 else    /* 接收数据后更新变量 */
 42                 {
 43                         buf += nread;
 44                         nleft -= nread;
 45                 }
 46         }
 47
 48         return (count - nleft);         /* 返回成功接收的数据量 */
 49 }
 50
 51 /* 发送定长数据包 */
 52 ssize_t writen(int fd, const void* buf, size_t count)
 53 {
 54         size_t  nleft = count;
 55         ssize_t nwritten;
 56
 57         while (nleft > 0)
 58         {
 59                 if ((nwritten = write(fd, buf, nleft)) == -1)
 60                 {
 61                         if (nleft == count)
 62                                 return (-1);
 63                         else
 64                                 break;
 65                 }
 66                 else if (nwritten == 0)
 67                         break;
 68                 else
 69                 {
 70                         buf += nwritten;
 71                         nleft -= nwritten;
 72                 }
 73         }
 74
 75         return (count - nleft);         /* 返回成功发送的数据量 */
 76 }
 77
 78
 79 size_t recv_peek(int listen_fd, void* buf, size_t len)
 80 {
 81         while (1)
 82         {
 83                 int ret;
 84                 ret = recv(listen_fd, buf, len, MSG_PEEK);
 85                 if (ret == -1 && errno == EINTR)
 86                         continue;
 87                 return (ret);
 88         }
 89 }
 90
 91 ssize_t readline(int listen_fd, void* buf, size_t maxline)
 92 {
 93         int ret;
 94         int nread;
 95         char* pbuf = buf;
 96         int nleft = maxline;
 97
 98         while (1)
 99         {
100                 ret = recv_peek(listen_fd, pbuf, nleft);
101                 if (ret < 0)
102                         return (ret);
103                 else if (ret == 0)
104                         return (ret);
105
106                 nread = ret;
107
108                 int i;
109                 for (i = 0; i < nread; i++)
110                 {
111                         if (pbuf[i] == ‘\n‘)
112                         {
113                                 ret = readn(listen_fd, pbuf, i + 1);
114                                 if (ret != i + 1)
115                                         exit(EXIT_FAILURE);
116                                 return (ret);
117                         }
118                 }
119
120                 if (nread > nleft)
121                         exit(EXIT_FAILURE);
122                 nleft -= nread;
123
124                 ret = readn(listen_fd, pbuf, nread);
125                 if (ret != nread)
126                         exit(EXIT_FAILURE);
127                 pbuf += nread;
128         }
129
130         return (-1);
131 }
132
133 void echoserv(int listen_fd)
134 {
135         /** using poll to realize a concurrent server */
136         struct sockaddr_in clit_addr;
137         memset(&clit_addr, 0, sizeof(clit_addr));
138         socklen_t clit_len = sizeof(clit_addr);
139
140         struct pollfd client[256];
141         int maxi = 0;
142
143         int i;
144         for (i = 0; i < 256; i++)
145                 client[i].fd = -1;
146
147         int nready;
148         client[0].fd = listen_fd;
149         client[0].events = POLLIN;      /* 对监听套接字的可读事件感兴趣 */
150
151         int conn;
152         while (1)
153         {
154                 nready = poll(client, maxi + 1, -1);
155                 if (nready == -1)
156                 {
157                         if (errno == EINTR)
158                                 continue;
159                         else
160                                 ERR_EXIT("poll");
161                 }
162                 if (nready == 0)
163                         continue;
164
165                 if (client[0].revents & POLLIN)
166                 {
167                         conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len);
168                         if (conn == -1)
169                                 ERR_EXIT("accept");
170                         for (i = 0; i < 256; i++)
171                         {
172                                 if (client[i].fd < 0)   /* 寻找空闲位置保存连接 */
173                                 {
174                                         client[i].fd = conn;
175                                         if (i > maxi)
176                                                 maxi = i;
177                                         break;
178                                 }
179                         }
180                         if (i == 256)
181                         {
182                                 fprintf(stderr, "too many clients.\n");
183                                 exit(EXIT_FAILURE);
184                         }
185
186                         printf("client(ip = %s, port = %d) connected.\n",inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port));
187
188                         client[i].events = POLLIN;
189
190                         if (--nready <= 0)
191                                 continue;
192
193                 }
194
195                 for (i = 1; i <= maxi; i++)
196                 {
197                         conn = client[i].fd;
198                         if (conn == -1)
199                                 continue;
200                         if (client[i].events & POLLIN)
201                         {
202                                 char recvbuf[1024] = {0};
203                                 int ret = readline(conn, recvbuf, 1024);
204                                 if (ret == -1)
205                                         ERR_EXIT("readline");
206                                 if (ret == 0)
207                                 {
208                                         printf("client close.\n");
209                                         client[i].fd = -1;
210                                         close(conn);
211                                 }
212
213                                 fputs(recvbuf, stdout);
214                                 writen(conn, recvbuf, strlen(recvbuf));
215                                 if (--nready <= 0)
216                                         break;
217                         }
218                 }
219
220         }
221
222         exit(EXIT_SUCCESS);
223 }
224
225 int main(void)
226 {
227         /* 创建一个监听套接字 */
228         int listen_fd;
229         if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
230                 ERR_EXIT("socket");
231
232         /* 初始化服务器地址 */
233         struct sockaddr_in serv_addr;
234         memset(&serv_addr, 0, sizeof(serv_addr));
235         serv_addr.sin_family = AF_INET;
236         serv_addr.sin_port = htons(5188);
237         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
238         /**
239  *  *  *  *  *         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
240  *   *   *   *   *                 inet_aton("127.0.0.0", &serv_addr.sin_addr); */
241
242         /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */
243         int on = 1;
244         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
245                 ERR_EXIT("setsockopt");
246
247         /* 将服务器地址绑定到监听套接字上 */
248         if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
249                 ERR_EXIT("bind");
250
251         /* 监听进入的连接 */
252         if (listen(listen_fd, SOMAXCONN) == -1)
253                 ERR_EXIT("listen");
254
255         echoserv(listen_fd);
256         close(listen_fd);
257
258         return (0);
259 }

并发客户端(6)---同(5)

并发服务器(7)[epoll方式]

  1 [[email protected] tcp]# cat echoserv_epoll.cpp
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <sys/wait.h>
  6 #include <sys/epoll.h>
  7 #include <netinet/in.h>
  8 #include <arpa/inet.h>
  9 #include <fcntl.h>
 10 #include <stdlib.h>
 11 #include <string.h>
 12 #include <errno.h>
 13 #include <stdio.h>
 14
 15 #include <vector>
 16 #include <algorithm>
 17
 18 typedef std::vector<struct epoll_event> EventList;
 19
 20 #define ERR_EXIT(message)        21         do       22         {        23                 perror(message);         24                 exit(EXIT_FAILURE);      25         } while (0)
 26
 27
 28 /* 接收定长数据包 */
 29 size_t readn(int fd, void* buf, size_t count)
 30 {
 31         /* nleft:剩下未接收的数据量
 32  *  *  * *          nread:每次接收的数据量 */
 33         size_t  nleft = count;
 34         ssize_t nread;
 35
 36         while (nleft > 0)       /* 如果还有未接收的数据 */
 37         {
 38                 if ((nread = read(fd, buf, nleft)) == -1)
 39                 {
 40                         if (nleft == count)     /* 剩下的数据量与初始数据量相等,说明未成功接收任何数据,失败返回 */
 41                                 return (-1);
 42                         else    /* 本次read操作失败,返回到目前为止成功接收的数据量 */
 43                                 break;
 44                 }
 45                 else if (nread == 0)    /* EOF,说明没有数据可供接收了 */
 46                         break;
 47                 else    /* 接收数据后更新变量 */
 48                 {
 49                         buf += nread;
 50                         nleft -= nread;
 51                 }
 52         }
 53
 54         return (count - nleft);         /* 返回成功接收的数据量 */
 55 }
 56
 57 /* 发送定长数据包 */
 58 ssize_t writen(int fd, const void* buf, size_t count)
 59 {
 60         size_t  nleft = count;
 61         ssize_t nwritten;
 62
 63         while (nleft > 0)
 64         {
 65                 if ((nwritten = write(fd, buf, nleft)) == -1)
 66                 {
 67                         if (nleft == count)
 68                                 return (-1);
 69                         else
 70                                 break;
 71                 }
 72                 else if (nwritten == 0)
 73                         break;
 74                 else
 75                 {
 76                         buf += nwritten;
 77                         nleft -= nwritten;
 78                 }
 79         }
 80
 81         return (count - nleft);         /* 返回成功发送的数据量 */
 82 }
 83
 84
 85 size_t recv_peek(int listen_fd, void* buf, size_t len)
 86 {
 87         while (1)
 88         {
 89                 int ret;
 90                 ret = recv(listen_fd, buf, len, MSG_PEEK);
 91                 if (ret == -1 && errno == EINTR)
 92                         continue;
 93                 return (ret);
 94         }
 95 }
 96
 97 ssize_t readline(int listen_fd, void* buf, size_t maxline)
 98 {
 99         int ret;
100         int nread;
101         char* pbuf = (char*)buf;
102         int nleft = maxline;
103
104         while (1)
105         {
106                 ret = recv_peek(listen_fd, pbuf, nleft);
107                 if (ret < 0)
108                         return (ret);
109                 else if (ret == 0)
110                         return (ret);
111
112                 nread = ret;
113
114                 int i;
115                 for (i = 0; i < nread; i++)
116                 {
117                         if (pbuf[i] == ‘\n‘)
118                         {
119                                 ret = readn(listen_fd, pbuf, i + 1);
120                                 if (ret != i + 1)
121                                         exit(EXIT_FAILURE);
122                                 return (ret);
123                         }
124                 }
125
126                 if (nread > nleft)
127                         exit(EXIT_FAILURE);
128                 nleft -= nread;
129
130                 ret = readn(listen_fd, pbuf, nread);
131                 if (ret != nread)
132                         exit(EXIT_FAILURE);
133                 pbuf += nread;
134         }
135         return (-1);
136 }
137
138 void activate_nonblock(int fd)
139 {
140         int flags;
141         if ((flags = fcntl(fd, F_GETFL)) == -1)
142                 ERR_EXIT("fcntl");
143         flags |= O_NONBLOCK;
144
145         int ret;
146         if ((ret = fcntl(fd, F_SETFL, flags)) == -1)
147                 ERR_EXIT("fcntl");
148 }
149
150 void handle_sigchld(int sig)
151 {
152         /* wait(NULL); */
153         while (waitpid(-1, NULL, WNOHANG) > 0)
154                 ;
155 }
156
157 void handle_sigpipe(int sig)
158 {
159         printf("recv a sig = %d.\n", sig);
160 }
161
162 void echoserv(int listen_fd, int conn)
163 {
164         std::vector<int> clients;
165         int epoll_fd;
166         epoll_fd = epoll_create1(EPOLL_CLOEXEC);
167         if (epoll_fd == -1)
168                 ERR_EXIT("epoll_create1");
169
170         struct epoll_event event;
171         event.data.fd = listen_fd;
172         event.events = EPOLLIN | EPOLLET;
173         epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event);
174
175         EventList events(16);
176         struct sockaddr_in clit_addr;
177         socklen_t clit_len = sizeof(clit_addr);
178         int nready;
179         while (1)
180         {
181                 nready = epoll_wait(epoll_fd, &*events.begin(), static_cast<int>(events.size()), -1);
182                 if (nready == -1)
183                 {
184                         if (errno == EINTR)
185                                 continue;
186                         ERR_EXIT("epoll_wait");
187                 }
188                 if (nready == 0)
189                         continue;
190                 if ((size_t)nready == events.size())    /* 如果存储空间已满,则扩充容量 */
191                         events.resize(events.size() * 2);
192
193                 int i;
194                 for (i = 0; i < nready; i++)
195                 {
196                         if (events[i].data.fd == listen_fd)
197                         {
198                                 conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len);
199                                 if (conn == -1)
200                                         ERR_EXIT("accept");
201                                 printf("client(ip = %s, port = %d) connected.\n", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port));
202                                 clients.push_back(conn);
203                                 activate_nonblock(conn);        /* 设置当前连接为非阻塞模式 */
204                                 event.data.fd = conn;
205                                 event.events = EPOLLIN | EPOLLET;
206                                 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn, &event);
207                         }
208                         else if (events[i].events & EPOLLIN)
209                         {
210                                 conn = events[i].data.fd;
211                                 if (conn < 0)
212                                         continue;
213
214                                 char recvbuf[1024] = {0};
215                                 int ret = readline(conn, recvbuf, 1024);
216                                 if (ret == -1)
217                                         ERR_EXIT("readline");
218                                 if (ret == 0)
219                                 {
220                                         printf("client closed.\n");
221                                         close(conn);
222
223                                         event = events[i];
224                                         epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn, &event);
225                                         clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());
226                                 }
227
228                                 fputs(recvbuf, stdout);
229                                 writen(conn, recvbuf, strlen(recvbuf));
230                         }
231                 }
232         }
233 }
234
235
236
237 int main(void)
238 {
239         /* 产生如下信号时,我们可以捕捉并处理,但是通常忽略即可 */
240         // signal(SIGPIPE, handle_sigpipe);     /* 在收到RST段后,再次调用write操作就会产生SIGPIPE信号 */
241         // signal(SIGCHLD, handle_sigchld);     /* 避免僵尸进程 */
242         signal(SIGPIPE, SIG_IGN);
243         signal(SIGCHLD, SIG_IGN);
244
245         /* 创建一个监听套接字 */
246         int listen_fd;
247         if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
248                 ERR_EXIT("socket");
249
250         /* 初始化服务器地址 */
251         struct sockaddr_in serv_addr;
252         memset(&serv_addr, 0, sizeof(serv_addr));
253         serv_addr.sin_family = AF_INET;
254         serv_addr.sin_port = htons(5188);
255         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
256         /**
257  *  *  *  *         serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
258  *   *   *   *                 inet_aton("127.0.0.0", &serv_addr.sin_addr); */
259
260         /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */
261         int on = 1;
262         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
263                 ERR_EXIT("setsockopt");
264
265         /* 将服务器地址绑定到监听套接字上 */
266         if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
267                 ERR_EXIT("bind");
268
269         /* 监听进入的连接 */
270         if (listen(listen_fd, SOMAXCONN) == -1)
271                 ERR_EXIT("listen");
272
273         int conn;
274         echoserv(listen_fd, conn);
275
276         close(listen_fd);
277
278         return (0);
279 }

并发客户端(7)---同(5)

利用线程也可以实现并发功能,如下使用POSIX线程库实现,编译时要加入-pthread选项。传统函数的返回值在失败时一般会返回-1,并设置errno变量。pthreads函数出错时不会设置全局变量errno,而是返回错误码,然后用strerror函数打印与该错误码相关的信息。

并发服务器(8)[线程方式]

 1 [[email protected] thread]# cat echoserv_thread.c
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <sys/socket.h>
 5 #include <netinet/in.h>
 6 #include <arpa/inet.h>
 7 #include <stdlib.h>
 8 #include <string.h>
 9 #include <pthread.h>
10 #include <errno.h>
11 #include <stdio.h>
12
13 #define ERR_EXIT(message) 14     do 15     { 16         perror(message); 17         exit(EXIT_FAILURE); 18     } while(0)
19
20
21 void echoserv(int conn)
22 {
23         char recvbuf[1024];
24         while (1)
25         {
26                 memset(recvbuf, 0, sizeof(recvbuf));
27                 int ret;
28                 if ((ret = read(conn, recvbuf, sizeof(recvbuf))) < 0)
29                         ERR_EXIT("read");
30                 if (ret == 0)           /* client closed */
31                 {
32                         printf("client closed.\n");
33                         break;
34                 }
35                 fputs(recvbuf, stdout);
36                 if (write(conn, recvbuf, ret) != ret)
37                         ERR_EXIT("write");
38         }
39
40 }
41
42 void* thread_routine(void* arg)
43 {
44         int conn = (int)arg;
45         echoserv(conn);
46         printf("exit thread.\n");
47
48         return NULL;
49 }
50
51 int main(void)
52 {
53         int sock_fd;
54         if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
55                 ERR_EXIT("socket");
56
57         struct sockaddr_in serv_addr;
58         memset(&serv_addr, 0, sizeof(serv_addr));
59         serv_addr.sin_family = AF_INET;
60         serv_addr.sin_port = htons(5188);
61         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
62
63         int on = 1;
64         if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
65                 ERR_EXIT("setsockopt");
66
67         if (bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
68                 ERR_EXIT("bind");
69
70         if (listen(sock_fd, SOMAXCONN) < 0)
71                 ERR_EXIT("listen");
72         struct sockaddr_in peer_addr;
73         socklen_t peer_len = sizeof(peer_addr);
74         int conn;
75         while (1)
76         {
77                 if ((conn = accept(sock_fd, (struct sockaddr*)&peer_addr, &peer_len)) < 0)
78                         ERR_EXIT("accept");
79                 printf("client(ip = %s, port = %d) connected.\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
80
81                 pthread_t tid;
82                 int ret;
83                 ret = pthread_create(&tid, NULL, thread_routine, (void*)conn);
84                 if (ret != 0)
85                 {
86                         fprintf(stderr, "pthread_create: %s.\n", strerror(ret));
87                         exit(EXIT_FAILURE);
88                 }
89         }
90
91         return 0;
92 }

并发客户端(8)---同(1)

与tcp相比,udp有时显得更为高效。但是udp报文可能会丢失、重复、乱序,其缺乏流量控制,upd编程中,recvfrom返回0并不代表连接关闭,因为udp是无连接的。

并发服务器(9)[udp]

 1 [[email protected] udp]# cat echoserv.c
 2 #include <netinet/in.h>
 3 #include <sys/types.h>
 4 #include <sys/socket.h>
 5 #include <unistd.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <stdio.h>
 9 #include <errno.h>
10
11 #define ERR_EXIT(msg)   12         do      13         {       14                 perror(msg);    15                 exit(EXIT_FAILURE);     16         } while (0)
17
18 void echoserv(int sock_fd)
19 {
20         char recv_buf[1024] = {0};
21         struct sockaddr_in peer_addr;
22         socklen_t peer_len;
23         int n;
24         while (1)
25         {
26                 peer_len = sizeof(peer_addr);
27                 memset(recv_buf, 0, sizeof(recv_buf));
28
29                 if ((n = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&peer_addr, &peer_len)) == -1)
30                 {
31                         if (errno == EINTR)
32                                 continue;
33                         else
34                                 ERR_EXIT("recvfrom");
35                 }
36                 else if (n == 0)
37                 {
38                         /* recvfrom返回0不代表连接关闭,因为udp是无连接的 */
39                 }
40                 else
41                 {
42                         fputs(recv_buf, stdout);
43                         sendto(sock_fd, recv_buf, n, 0, (struct sockaddr*)&peer_addr, peer_len);
44                 }
45         }
46
47         close(sock_fd);
48 }
49
50 int main(void)
51 {
52         int sock_fd;
53         if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
54                 ERR_EXIT("socket");
55
56         struct sockaddr_in serv_addr;
57         memset(&serv_addr, 0, sizeof(serv_addr));
58         serv_addr.sin_family = AF_INET;
59         serv_addr.sin_port = htons(5188);
60         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
61
62         if (bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
63                 ERR_EXIT("bind");
64
65         echoserv(sock_fd);
66
67         return 0;
68 }

并发客户端(9)[udp]

 1 [[email protected] 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 }

时间: 2024-07-31 00:53:27

Linux 并发服务器雏形总结的相关文章

linux并发服务器设计

linux 并发服务器: http://blog.csdn.net/ygl840455828ygl/article/details/52438167 http://www.2cto.com/os/201309/245280.html http://www.cnblogs.com/venow/archive/2012/11/22/2779667.html http://blog.csdn.net/turkeyzhou/article/details/8609360 1 背景简介 H330S模块集成

linux网络编程-----&gt;高并发---&gt;多进程并发服务器

在做网络服务的时候并发服务端程序的编写必不可少.前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定. 常见的linux并发服务器模型: 多进程并发服务器 多线程并发服务器 select多路I/O转接服务器 poll多路I/O转接服务器 epool多路I/O转接服务器. 本次主要讨论多线程并发服务器模型: 使用多进程并发服务器时要考虑以下几点: 父进程最大文件描述个数(父进程中需要close关闭accpet返回的新文件描述符) 系统内创建进程个数(与内

LINUX环境并发服务器的三种实现模型

服务器设计技术有很多,按使用的协议来分有TCP服务器和UDP服务器.按处理方式来分有循环服务器和并发服务器. 1  循环服务器与并发服务器模型 在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求,对服务端的程序就提出了特殊的要求. 目前最常用的服务器模型有: ·循环服务器:服务器在同一时刻只能响应一个客户端的请求 ·并发服务器:服务器在同一时刻可以响应多个客户端的请求 1.1 UDP循环服务器的实现方法: UDP循环服务器每次从套接字上读取一个客户端的请求->处理->然后将

Linux下几种并发服务器的实现模式

Linux下的几种并发服务器的设计模式 1>单线程或者单进程 相当于短链接,当accept之后,就开始数据的接收和数据的发送,不接受新的连接,即一个server,一个client 不存在并发. 2>循环服务器和并发服务器 1.循环服务器:一个server只能一次只能接收一个client,当当前client结束访问之后才能进行下一个client的连接. 2.并发服务器:一个server同一时间可以响应很多客户端的访问. 3>select+多线程模式 并发服务器的三种实现方式 1.多进程并发

linux网络编程-----&gt;高并发---&gt;多线程并发服务器

做网络服务的时候并发服务端程序的编写必不可少.前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定. 常见的linux并发服务器模型: 多进程并发服务器 多线程并发服务器 select多路I/O转接服务器 poll多路I/O转接服务器 epool多路I/O转接服务器. 本次主要讨论多线程并发服务器模型: 使用多线程模型开发服务时需要考虑以下问题 1.  调整进程内最大文件描述符上限. 2.  线程如有共享数据, 考虑线程同步. 3.  服务于客户端线程

linux僵死进程与并发服务器编程

序 僵死(zombie)进程简而言之就是:子进程退出时,父进程并未对其发出的SIGCHILD信号进行适当处理,导致子进程停留在僵死状态等待其父进程为其收尸,这个状态下的子进程就是僵死进程. 因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源.对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求.如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源.如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能. 查

Linux 网络编程——并发服务器的三种实现模型

服务器设计技术有很多,按使用的协议来分有 TCP 服务器和 UDP 服务器,按处理方式来分有循环服务器和并发服务器. 循环服务器与并发服务器模型 在网络程序里面,一般来说都是许多客户对应一个服务器(多对一),为了处理客户的请求,对服务端的程序就提出了特殊的要求. 目前最常用的服务器模型有: ·循环服务器:服务器在同一时刻只能响应一个客户端的请求 ·并发服务器:服务器在同一时刻可以响应多个客户端的请求 UDP 循环服务器的实现方法 UDP 循环服务器每次从套接字上读取一个客户端的请求 -> 处理

Linux网络编程——tcp并发服务器(多进程)

一.tcp并发服务器概述 一个好的服务器,一般都是并发服务器(同一时刻可以响应多个客户端的请求).并发服务器设计技术一般有:多进程服务器.多线程服务器.I/O复用服务器等. 二.多进程并发服务器 在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器.多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求.父进程继续等待其它客户的请求.这种方法的优点是当客户有请求时,服务器能及时处理客户,特别是在客户服务器交互系统中.对于一个 TCP 服务器,客户与服务器的连接可能并不

Linux客户/服务器程序设计范式1&mdash;&mdash;并发服务器(进程)

引言 本文会写一个并发服务器(concurrent server)程序,它为每个客户请求fork出一个子进程. 注意 1. 信号处理问题 对于相同信号,按信号的先后顺序依次处理.可能会产生的问题是,正在处理sig1信号时,又来了2个或更多的sig1信号,此sig1时只会在处理完原来的sig1信号后,再处理1个sig1信号.因此对于相同信号,会产生信号掉包的问题. 一个儿子退了之后,程序在处理handler(),如果此时又退了两个儿子,那么必然有一个儿子的资源回收不到,称为僵尸进程. 对于不同信号