多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,==不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件==。通过这种方式可以同时监测多个文件描述符并且这个过程是阻塞的,一旦检测到有文件描述符就绪( 可以读数据或者可以写数据)程序的阻塞就会被解除,之后就可以基于这些(一个或多个)就绪的文件描述符进行通信了。通过这种方式在单线程 / 进程的场景下也可以在服务器端实现并发。常见的 IO 多路转接方式有:select、poll、epoll。
	与多进程和多线程技术相比,I/O 多路复用技术的最大优势是系统开销小,系统不必创建进程 / 线程,也不必维护这些进程 / 线程,从而大大减小了系统的开销。
1.select 
select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数 
解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力 
 
 
1.1 函数原型 1 2 3 4 5 6 7 8 #include  <sys/select.h>  struct  timeval  {    time_t       tv_sec;              suseconds_t  tv_usec;         }; int  select (int  nfds, fd_set *readfds, fd_set *writefds,            fd_set *exceptfds, struct  timeval * timeout) ;
 
函数参数: 
nfds:    监听的所有文件描述符中,最大文件描述符+1 
readfds:  读 文件描述符监听集合,传入传出参数 
writefds: 写 文件描述符监听集合,传入传出参数 
exceptfds: 异常 文件描述符监听集合,传入传出参数 
timeout:  定时阻塞监控时间,3种情况
NULL,永远等下去,阻塞监听 
设置timeval,等待固定时间 
设置timeval里时间均为0,检查描述字后立即返回,轮询 
 
 
 
函数返回值: 
大于 0:成功,返回集合中已就绪的文件描述符的总个数 
等于 - 1:函数调用失败 
等于 0:超时,没有检测到就绪的文件描述符 
 
另外初始化 fd_set 类型的参数还需要使用相关的一些列操作函数,具体如下:
1 2 3 4 5 6 7 8 void  FD_CLR (int  fd, fd_set *set ) ;int   FD_ISSET (int  fd, fd_set *set ) ;void  FD_SET (int  fd, fd_set *set ) ;void  FD_ZERO (fd_set *set ) ;
 
演示 :
1 2 3 4 5 6 7 8 9 10 void  FD_ZERO (fd_set *set ) ;	--- 清空一个文件描述符集合。  fd_set rset; 	FD_ZERO(&rset); void  FD_SET (int  fd, fd_set *set ) ;	--- 将待监听的文件描述符,添加到监听集合中  FD_SET(3 , &rset);	FD_SET(5 , &rset);	FD_SET(6 , &rset); void  FD_CLR (int  fd, fd_set *set ) ;	--- 将一个文件描述符从监听集合中移除。  FD_CLR(4 , &rset); int  FD_ISSET (int  fd, fd_set *set ) ;	--- 判断一个文件描述符是否在监听集合中。  返回值: 在:1 ;不在:0 ;   FD_ISSET(4 , &rset); 
 
select优缺点
缺点: 
监听上限受文件描述符限制。 最大 1024. 
检测满足条件的fd, 自己添加业务逻辑提高小。 提高了编码难度。 
 
 
优点: 跨平台。win、linux、macOS、Unix、类Unix、mips 
 
 
1.2 select实现多路IO转接设计思路 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int  maxfd = 0 ;lfd = socket() ;			创建套接字 maxfd = lfd; bind();					绑定地址结构 listen();				设置监听上限 fd_set rset, allset;			创建r监听集合 FD_ZERO(&allset);				将r监听集合清空 FD_SET(lfd, &allset);			将 lfd 添加至读集合中。 while (1 ) {	rset = allset;			保存监听集合   ret  = select(lfd+1 , &rset, NULL , NULL , NULL );		监听文件描述符集合对应事件。   if (ret > 0 ) {							有监听的描述符满足对应事件     if  (FD_ISSET(lfd, &rset)) {				     cfd = accept();				建立连接,返回用于通信的文件描述符     maxfd = cfd;     FD_SET(cfd, &allset);				添加到监听通信描述符集合中。     }     for  (i = lfd+1 ; i <= 最大文件描述符; i++){       FD_ISSET(i, &rset)				有read、write事件       read()       小 -- 大       write();      }	 	} } 
 
1.3 server 
Socket、Bind、Listen等函数,我们都对其进行了出错处理封装,点我查看源码 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  #include  <unistd.h>  #include  <pthread.h>  #include  <netinet/in.h>  #include  <arpa/inet.h>  #include  <ctype.h>  #include  "wrap.h"  #define  SERV_PORT 9527 int  main (int  argc, char  *argv[]) {     int  listenfd, connfd;     char  buf[BUFSIZ];     struct  sockaddr_in  clie_addr , serv_addr ;     socklen_t  clie_addr_len;     listenfd = Socket(AF_INET, SOCK_STREAM, 0 );     int  opt = 1 ;     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));     bzero(&serv_addr, sizeof (serv_addr));     serv_addr.sin_family = AF_INET;     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);     serv_addr.sin_port = htons(SERV_PORT);     Bind(listenfd, (struct  sockaddr *)&serv_addr, sizeof (serv_addr));     Listen(listenfd, 128 );     fd_set rset, allset;             int  ret, maxfd = 0 , n, i, j;     maxfd = listenfd;            FD_ZERO(&allset);            FD_SET(listenfd, &allset);           while  (1 )     {         rset = allset;               ret = select(maxfd+1 , &rset, NULL , NULL , NULL );              if  (ret < 0 )         {             perr_exit("select error" );         }         if  (FD_ISSET(listenfd, &rset))               {             clie_addr_len = sizeof (clie_addr);             connfd = Accept(listenfd, (struct  sockaddr *)&clie_addr, &clie_addr_len);                    FD_SET(connfd, &allset);                     if (maxfd < connfd)                   {                 maxfd = connfd;             }             if (ret == 1 )                     {                 continue ;             }         }         for (i = listenfd + 1 ; i <= maxfd; i++){                  if (FD_ISSET(i, &allset))                     {                 n = Read(i, buf, sizeof (buf));                 if (n == 0 )                       {                     Close(i);                     FD_CLR(i, &allset);                      }else  if (n == -1 ){                     perr_exit("read error" );                 }                 for (j =0 ; j< n; j++)                 {                     buf[j] = toupper (buf[j]);                 }                 write(i, buf, n);                 write(STDOUT_FILENO, buf, n);             }         }          }     Close(listenfd);     return  0 ; } 
 
1.4 client 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include  <stdio.h>  #include  <string.h>  #include  <unistd.h>  #include  <netinet/in.h>  #include  <arpa/inet.h>  #include  "wrap.h"  #define  MAXLINE 80 #define  SERV_PORT 9527 int  main (int  argc, char  *argv[]) { 	struct  sockaddr_in  servaddr ; 	char  buf[MAXLINE]; 	int  sockfd, n; 	sockfd = Socket(AF_INET, SOCK_STREAM, 0 ); 	bzero(&servaddr, sizeof (servaddr)); 	servaddr.sin_family = AF_INET; 	inet_pton(AF_INET, "10.0.12.16" , &servaddr.sin_addr); 	servaddr.sin_port = htons(SERV_PORT); 	Connect(sockfd, (struct  sockaddr *)&servaddr, sizeof (servaddr)); 	while  (fgets(buf, MAXLINE, stdin ) != NULL ) { 		Write(sockfd, buf, strlen (buf)); 		n = Read(sockfd, buf, MAXLINE); 		if  (n == 0 ) 			printf ("the other side has been closed.\n" ); 		else  			Write(STDOUT_FILENO, buf, n); 	} 	Close(sockfd); 	return  0 ; } 
 
编译运行,结果如下:
1.5 代码优化 
如果最大fd是1023,每次确定有事件发生的fd时,就要扫描3-1023的所有文件描述符,这看起来很蠢。于是定义一个数组,把要监听的文件描述符存下来,每次扫描这个数组就行了。看起来科学得多,这样就不需要每次扫描一大堆无关文件描述符了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  #include  <netinet/in.h>  #include  <arpa/inet.h>  #include  <unistd.h>  #include  <ctype.h>  #include  "wrap.h"  #define  MAXLINE 80 #define  SERV_PORT 6666 int  main (int  argc, char  *argv[]) { 	int  i, maxi, maxfd, listenfd, connfd, sockfd; 	int  nready, client[FD_SETSIZE]; 	 	ssize_t  n; 	fd_set rset, allset; 	char  buf[MAXLINE]; 	char  str[INET_ADDRSTRLEN]; 			 	socklen_t  cliaddr_len; 	struct  sockaddr_in  cliaddr , servaddr ; 	listenfd = Socket(AF_INET, SOCK_STREAM, 0 );     bzero(&servaddr, sizeof (servaddr));     servaddr.sin_family = AF_INET;     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);     servaddr.sin_port = htons(SERV_PORT);     Bind(listenfd, (struct  sockaddr *)&servaddr, sizeof (servaddr));     Listen(listenfd, 20 ); 		     maxfd = listenfd; 			     maxi = -1 ;					     for  (i = 0 ; i < FD_SETSIZE; i++)         client[i] = -1 ; 		     FD_ZERO(&allset);     FD_SET(listenfd, &allset);      for  ( ; ; ) {         rset = allset; 			         nready = select(maxfd+1 , &rset, NULL , NULL , NULL );         if  (nready < 0 )             perr_exit("select error" );         if  (FD_ISSET(listenfd, &rset)) {              cliaddr_len = sizeof (cliaddr);             connfd = Accept(listenfd, (struct  sockaddr *)&cliaddr, &cliaddr_len);             printf ("received from %s at PORT %d\n" ,                     inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof (str)),                     ntohs(cliaddr.sin_port));             for  (i = 0 ; i < FD_SETSIZE; i++) {                 if  (client[i] < 0 ) {                     client[i] = connfd;                      break ;                 }             }                          if  (i == FD_SETSIZE) {                 fputs ("too many clients\n" , stderr );                 exit (1 );             }             FD_SET(connfd, &allset); 	             if  (connfd > maxfd)                 maxfd = connfd; 		             if  (i > maxi)                 maxi = i; 				             if  (--nready == 0 )                 continue ; 				             }             for  (i = 0 ; i <= maxi; i++) { 	                 if  ( (sockfd = client[i]) < 0 )                     continue ;                 if  (FD_ISSET(sockfd, &rset)) {                     if  ( (n = Read(sockfd, buf, MAXLINE)) == 0 ) {                         Close(sockfd);		                         FD_CLR(sockfd, &allset);                          client[i] = -1 ;                     } else  {                         int  j;                         for  (j = 0 ; j < n; j++)                             buf[j] = toupper (buf[j]);                         Write(sockfd, buf, n);                     }                     if  (--nready == 0 )                         break ;                 }             }         }         close(listenfd);         return  0 ; } 
 
2.poll poll是对select的改进,但是它是个半成品,相对select提升不大。最终版本是epoll,所以poll了解一下就完事儿,重点掌握epoll。
2.1 函数原型 1 2 3 4 5 6 7 8 9 10 #include  <poll.h>  struct  pollfd  {    int    fd;              short  events;          short  revents;     }; struct  pollfd  myfd [100];int  poll (struct  pollfd *fds, nfds_t  nfds, int  timeout) ;
 
函数参数: 
poll优缺点
 
2.2 server 
Socket、Bind、Listen等函数,我们都对其进行了出错处理封装,点我查看源码 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  #include  <netinet/in.h>  #include  <arpa/inet.h>  #include  <poll.h>  #include  <errno.h>  #include  <ctype.h>  #include  "wrap.h"  #define  MAXLINE 80 #define  SERV_PORT 9527 #define  OPEN_MAX 1024 int  main (int  argc, char  *argv[]) { 	int  i, j, maxi, listenfd, connfd, sockfd; 	int  nready; 	ssize_t  n; 	char  buf[MAXLINE], str[INET_ADDRSTRLEN]; 	socklen_t  clilen; 	struct  pollfd  client [OPEN_MAX ]; 	struct  sockaddr_in  cliaddr , servaddr ; 	listenfd = Socket(AF_INET, SOCK_STREAM, 0 ); 	bzero(&servaddr, sizeof (servaddr)); 	servaddr.sin_family = AF_INET; 	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 	servaddr.sin_port = htons(SERV_PORT); 	Bind(listenfd, (struct  sockaddr *)&servaddr, sizeof (servaddr)); 	Listen(listenfd, 20 ); 	client[0 ].fd = listenfd; 	client[0 ].events = POLLRDNORM; 					 	for  (i = 1 ; i < OPEN_MAX; i++) 		client[i].fd = -1 ; 							 	maxi = 0 ; 										 	for  ( ; ; ) { 		nready = poll(client, maxi+1 , -1 ); 			 		if  (client[0 ].revents & POLLRDNORM) { 		 			clilen = sizeof (cliaddr); 			connfd = Accept(listenfd, (struct  sockaddr *)&cliaddr, &clilen); 			printf ("received from %s at PORT %d\n" , 					inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof (str)), 					ntohs(cliaddr.sin_port)); 			for  (i = 1 ; i < OPEN_MAX; i++) { 				if  (client[i].fd < 0 ) { 					client[i].fd = connfd; 	 					break ; 				} 			} 			if  (i == OPEN_MAX) 				perr_exit("too many clients" ); 			client[i].events = POLLRDNORM; 		 			if  (i > maxi) 				maxi = i; 						 			if  (--nready <= 0 ) 				continue ; 						 		} 		for  (i = 1 ; i <= maxi; i++) { 			 			if  ((sockfd = client[i].fd) < 0 ) 				continue ; 			if  (client[i].revents & (POLLRDNORM | POLLERR)) { 				if  ((n = Read(sockfd, buf, MAXLINE)) < 0 ) { 					if  (errno == ECONNRESET) {  						 						printf ("client[%d] aborted connection\n" , i); 						Close(sockfd); 						client[i].fd = -1 ; 					} else  { 						perr_exit("read error" ); 					} 				} else  if  (n == 0 ) { 					 					printf ("client[%d] closed connection\n" , i); 					Close(sockfd); 					client[i].fd = -1 ; 				} else  { 					for  (j = 0 ; j < n; j++) 						buf[j] = toupper (buf[j]); 						Writen(sockfd, buf, n); 				} 				if  (--nready <= 0 ) 					break ; 				 			} 		} 	} 	return  0 ; } 
 
2.3 client 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include  <stdio.h>  #include  <string.h>  #include  <unistd.h>  #include  <netinet/in.h>  #include  <arpa/inet.h>  #include  "wrap.h"  #define  MAXLINE 80 #define  SERV_PORT 9527 int  main (int  argc, char  *argv[]) { 	struct  sockaddr_in  servaddr ; 	char  buf[MAXLINE]; 	int  sockfd, n; 	sockfd = Socket(AF_INET, SOCK_STREAM, 0 ); 	bzero(&servaddr, sizeof (servaddr)); 	servaddr.sin_family = AF_INET; 	inet_pton(AF_INET, "10.0.12.16" , &servaddr.sin_addr); 	servaddr.sin_port = htons(SERV_PORT); 	Connect(sockfd, (struct  sockaddr *)&servaddr, sizeof (servaddr)); 	while  (fgets(buf, MAXLINE, stdin ) != NULL ) { 		Write(sockfd, buf, strlen (buf)); 		n = Read(sockfd, buf, MAXLINE); 		if  (n == 0 ) 			printf ("the other side has been closed.\n" ); 		else  			Write(STDOUT_FILENO, buf, n); 	} 	Close(sockfd); 	return  0 ; } 
 
编译运行,结果如下:
3.epoll 	epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
	目前epell是linux大规模并发网络程序中的热门首选模型。
	epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
可以使用cat命令查看一个进程可以打开的socket描述符上限。
1 cat /proc/sys/fs/file-max 
 
如有需要,可以通过修改配置文件的方式修改该上限值。
1 2 3 4 sudo vi /etc/security/limits.conf 	在文件尾部写入以下配置,soft软限制,hard硬限制。如下图所示。 	* soft nofile 65536 	* hard nofile 100000 
 
3.1 操作函数 在 epoll 中一共提供是三个 API 函数,分别处理不同的操作,函数原型如下:
1 2 3 4 5 6 7 #include  <sys/epoll.h>  int  epoll_create (int  size) ;int  epoll_ctl (int  epfd, int  op, int  fd, struct  epoll_event *event) ;int  epoll_wait (int  epfd, struct  epoll_event * events, int  maxevents, int  timeout) ;
 
3.1.1 epoll_create() epoll_create() 函数的作用是创建一个红黑树模型的实例,用于管理待检测的文件描述符的集合。
1 2 #include  <sys/epoll.h>  int  epoll_create (int  size) 		size:监听数目
 
3.1.2 epoll_ctl() epoll_ctl() 函数的作用是管理红黑树实例上的节点,可以进行添加、删除、修改操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include  <sys/epoll.h>  typedef  union  epoll_data  { 	void         *ptr; 	int           fd;	 	uint32_t      u32; 	uint64_t      u64; } epoll_data_t ; struct  epoll_event  {	uint32_t      events;       	epoll_data_t  data;         }; int  epoll_ctl (int  epfd, int  op, int  fd, struct  epoll_event *event) ;
 
函数参数: 
epfd:epoll_create () 函数的返回值,通过这个参数找到 epoll 实例 
op:这是一个枚举值,控制通过该函数执行什么操作
EPOLL_CTL_ADD:往 epoll 模型中添加新的节点 
EPOLL_CTL_MOD:修改 epoll 模型中已经存在的节点 
EPOLL_CTL_DEL:删除 epoll 模型中的指定的节点 
 
 
fd:文件描述符,即要添加 / 修改 / 删除的文件描述符 
event:epoll 事件,用来修饰第三个参数对应的文件描述符的,指定检测这个文件描述符的什么事件 
events:委托 epoll 检测的事件
EPOLLIN:读事件,接收数据,检测读缓冲区,如果有数据该文件描述符就绪 
EPOLLOUT:写事件,发送数据,检测写缓冲区,如果可写该文件描述符就绪 
EPOLLERR:异常事件 
 
 
data:用户数据变量,这是一个联合体类型,通常情况下使用里边的 fd 成员,用于存储待检测的文件描述符的值,在调用 epoll_wait() 函数的时候这个值会被传出。 
 
函数返回值: 
3.1.3 epoll_wait() epoll_wait() 函数的作用是检测创建的epoll实例中有没有就绪的文件描述符。
1 2 #include  <sys/epoll.h>  int  epoll_wait (int  epfd, struct  epoll_event *events, int  maxevents, int  timeout) 
 
函数参数: 
epfd:epoll_create () 函数的返回值,通过这个参数找到 epoll 实例
 
events: 传出参数,这是一个结构体数组的地址,里边存储了已就绪的文件描述符的信息
 
maxevents: 告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
 
timeout:  是超时时间
-1:函数一直阻塞,直到 epoll 实例中有已就绪的文件描述符之后才解除阻塞 
0: 函数不阻塞,不管 epoll 实例中有没有就绪的文件描述符,函数被调用后都直接返回 
>0:如果 epoll 实例中没有已就绪的文件描述符,函数阻塞对应的毫秒数再返回 
 
 
 
函数返回值: 
成功:
等于 0:函数是阻塞被强制解除了,没有检测到满足条件的文件描述符 
大于 0:检测到的已就绪的文件描述符的总个数 
 
 
失败:返回 - 1 
 
3.2 epoll实现多路IO转接思路: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 lfd = socket();			监听连接事件lfd bind () ;listen(); int  epfd = epoll_create(1024 );				epfd, 监听红黑树的树根。struct  epoll_event  tep , ep [1024]; 			tep, 用来设置单个fd属性, ep 是 epoll_wait() 传出的满足监听事件的数组。tep.events = EPOLLIN;					初始化  lfd的监听属性。 tep.data.fd = lfd epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep);		将 lfd 添加到监听红黑树上。 while  (1 ) {	ret = epoll_wait(epfd, ep,1024 , -1 );			实施监听 	for  (i = 0 ; i < ret; i++) {		 		if  (ep[i].data.fd == lfd) {				 			cfd = Accept(); 			tep.events = EPOLLIN;				初始化  cfd的监听属性。 			tep.data.fd = cfd; 			epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep); 		} else  {						cfd 们 满足读事件, 有客户端写数据来。 			n = read(ep[i].data.fd, buf, sizeof (buf)); 			if  ( n == 0 ) { 				close(ep[i].data.fd);  				epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL );	 			} else  if  (n > 0 ) { 				小--大 				write(ep[i].data.fd, buf, n); 			} 		} 	} 
 
3.3 server 
Socket、Bind、Listen等函数,我们都对其进行了出错处理封装,点我查看源码 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  #include  <netinet/in.h>  #include  <arpa/inet.h>  #include  <sys/epoll.h>  #include  <errno.h>  #include  <ctype.h>  #include  <unistd.h>  #include  "wrap.h"  #define  MAXLINE 80 #define  SERV_PORT 9527 #define  OPEN_MAX 1024 int  main (int  argc, char  *argv[]) { 	int  i, j, maxi, listenfd, connfd, sockfd; 	int  nready, efd, res; 	ssize_t  n; 	char  buf[MAXLINE], str[INET_ADDRSTRLEN]; 	socklen_t  clilen; 	int  client[OPEN_MAX]; 	struct  sockaddr_in  cliaddr , servaddr ; 	struct  epoll_event  tep , ep [OPEN_MAX ]; 	listenfd = Socket(AF_INET, SOCK_STREAM, 0 ); 	bzero(&servaddr, sizeof (servaddr)); 	servaddr.sin_family = AF_INET; 	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 	servaddr.sin_port = htons(SERV_PORT); 	Bind(listenfd, (struct  sockaddr *) &servaddr, sizeof (servaddr)); 	Listen(listenfd, 20 ); 	for  (i = 0 ; i < OPEN_MAX; i++) 		client[i] = -1 ; 	maxi = -1 ; 	efd = epoll_create(OPEN_MAX); 	if  (efd == -1 ) 		perr_exit("epoll_create" ); 	tep.events = EPOLLIN; tep.data.fd = listenfd; 	res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); 	if  (res == -1 ) 		perr_exit("epoll_ctl" ); 	while  (1 ) { 		nready = epoll_wait(efd, ep, OPEN_MAX, -1 );  		if  (nready == -1 ) 			perr_exit("epoll_wait" ); 		for  (i = 0 ; i < nready; i++) { 			if  (!(ep[i].events & EPOLLIN)) 				continue ; 			if  (ep[i].data.fd == listenfd) { 				clilen = sizeof (cliaddr); 				connfd = Accept(listenfd, (struct  sockaddr *)&cliaddr, &clilen); 				printf ("received from %s at PORT %d\n" ,  						inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof (str)),  						ntohs(cliaddr.sin_port)); 				for  (j = 0 ; j < OPEN_MAX; j++) { 					if  (client[j] < 0 ) { 						client[j] = connfd;  						break ; 					} 				} 				if  (j == OPEN_MAX) 					perr_exit("too many clients" ); 				if  (j > maxi) 					maxi = j; 		 				tep.events = EPOLLIN;  				tep.data.fd = connfd; 				res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); 				if  (res == -1 ) 					perr_exit("epoll_ctl" ); 			} else  { 				sockfd = ep[i].data.fd; 				n = Read(sockfd, buf, MAXLINE); 				if  (n == 0 ) { 					for  (j = 0 ; j <= maxi; j++) { 						if  (client[j] == sockfd) { 							client[j] = -1 ; 							break ; 						} 					} 					res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL ); 					if  (res == -1 ) 						perr_exit("epoll_ctl" ); 					Close(sockfd); 					printf ("client[%d] closed connection\n" , j); 				} else  { 					for  (j = 0 ; j < n; j++) 						buf[j] = toupper (buf[j]); 					Writen(sockfd, buf, n); 				} 			} 		} 	} 	close(listenfd); 	close(efd); 	return  0 ; } 
 
3.4 client 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include  <stdio.h>  #include  <string.h>  #include  <unistd.h>  #include  <netinet/in.h>  #include  <arpa/inet.h>  #include  "wrap.h"  #define  MAXLINE 80 #define  SERV_PORT 9527 int  main (int  argc, char  *argv[]) { 	struct  sockaddr_in  servaddr ; 	char  buf[MAXLINE]; 	int  sockfd, n; 	sockfd = Socket(AF_INET, SOCK_STREAM, 0 ); 	bzero(&servaddr, sizeof (servaddr)); 	servaddr.sin_family = AF_INET; 	inet_pton(AF_INET, "10.0.12.16" , &servaddr.sin_addr); 	servaddr.sin_port = htons(SERV_PORT); 	Connect(sockfd, (struct  sockaddr *)&servaddr, sizeof (servaddr)); 	while  (fgets(buf, MAXLINE, stdin ) != NULL ) { 		Write(sockfd, buf, strlen (buf)); 		n = Read(sockfd, buf, MAXLINE); 		if  (n == 0 ) 			printf ("the other side has been closed.\n" ); 		else  			Write(STDOUT_FILENO, buf, n); 	} 	Close(sockfd); 	return  0 ; } 
 
编译运行,结果如下: