第16章 网络io与io多路复用select/pool/epool

第16.1节 写一个服务端代码

  1. 服务端代码

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    
    int main() {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
        servaddr.sin_port = htons(9999);
    
        if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
    		printf("bind failed: %s", strerror(errno));
    		return -1;
        }
    
        listen(sockfd, 10); 
        
    	getchar();
    }
  2. 运行该段代码,可以使用netstat -anop | grep 9999查看某一个端口(如:9999)进程的命令。结果如下图。可以发现代码执行到此处的时候,程序已经开始监听了。

  3. 可以使用第三方的网络助手工具尝试连接该端程序。可以发现可以正常发送成功,但是却没有没有反馈。

  4. 出现上述问题主要原因见下图。通过listen这是监听了,并没有真正的建立连接。建立连接是通过accept来实现的,并且每个客户端都有一个服务对应处理。

  5. 故而添加accept代码如下

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    
    int main() {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
        servaddr.sin_port = htons(9999);
    
        if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
    		printf("bind failed: %s", strerror(errno));
    		return -1;
        }
    
        listen(sockfd, 10); 
    
        //sleep(10);
        
    #if 0
    	printf("sleep\n");
    	int flags = fcntl(sockfd, F_GETFL, 0);
    	flags |= O_NONBLOCK;
    	fcntl(sockfd, F_SETFL, flags);
    #endif
    
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr); 
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
    
        // getchar();
    
    }
  6. 上述代码运行效果如下

    可以发现代码阻塞在accept函数处,此时通过工具建立连接,就会继续运行。而通过代码中的#if...#endif处的代码可以将阻塞io转换为非阻塞io。

  7. 思考:若在listen之后还未到accept的时候建立连接会成功吗?修改代码验证该思考。

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    
    int main() {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
        servaddr.sin_port = htons(9999);
    
        if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr)))
        {
    		printf("bind failed: %s", strerror(errno));
    		return -1;
        }
    
        listen(sockfd, 10); 
    
        sleep(10);
        printf("sleep\n");
    
    #if 0
    	printf("sleep\n");
    	int flags = fcntl(sockfd, F_GETFL, 0);
    	flags |= O_NONBLOCK;
    	fcntl(sockfd, F_SETFL, flags);
    #endif
    
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr); 
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);
        // getchar();
    }

    使用工具在未打印sleep之前点击连接,过一会儿运行结果如下图,可以发现依然可以建立连接。

    所以该思考的结果是:accept和是否能建立连接没有关系。如在listen之后sleep(10),还没有到accept的时候建立连接也是可以的。

  8. 思考:将代码改成非阻塞的,若在accept到来之前还未点击连接会如何?修改案例代码如下:

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    
    int main() {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
        servaddr.sin_port = htons(9999);
    
        if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
    		printf("bind failed: %s", strerror(errno));
    		return -1;
        }
    
        listen(sockfd, 10); 
    
        sleep(10);
    
    #if 1
    	printf("sleep\n");
    	int flags = fcntl(sockfd, F_GETFL, 0);
    	flags |= O_NONBLOCK;
    	fcntl(sockfd, F_SETFL, flags);
    #endif
    
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr); 
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);
        // getchar();
    }

    代码运行结果:

    可以发现失败了,linux下正数表示成功,-1表示失败。而如果在运行到accept之前点击了连接,就会连接成功。此处从3开始,是因为0是标准输入,1是标准输出,2是错误。

  9. 上面只实现了网络io的连接,那么接下来考虑服务端如何接收数据。使用recv,recv返回0表示断开连接

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    
    #define BUFFER_LENGTH		1024
    
    int main() {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
        servaddr.sin_port = htons(9999);
    
        if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
    		printf("bind failed: %s", strerror(errno));
    		return -1;
        }
    
        listen(sockfd, 10); 
    
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr); 
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);
    
        char buffer[BUFFER_LENGTH] = {0};
    	int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
        
        printf("ret: %d, buffer: %s\n", ret, buffer);
    
    	send(clientfd, buffer, ret, 0);
    
        // getchar();
    }

    使用第三方的网络助手工具尝试连接该端程序,代码运行结果如下:

    通过上图可以发现recv也是阻塞的,只会阻塞一次,send 也是一次**。**

  10. 思考:那么放入到while中是否可以发送多次数据呢?代码如下:

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    
    #define BUFFER_LENGTH		1024
    
    int main() {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
        servaddr.sin_port = htons(9999);
    
        if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
    		printf("bind failed: %s", strerror(errno));
    		return -1;
        }
    
        listen(sockfd, 10); 
    
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr); 
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);
    
    
    	while (1) { //slave
    	
    		char buffer[BUFFER_LENGTH] = {0};
    		int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
    		if (ret == 0) {
    			close(clientfd);
    			break; 
    		}
    
    		printf("ret: %d, buffer: %s\n", ret, buffer);
    
    		send(clientfd, buffer, ret, 0);
    	}
    
        // getchar();
    }

    代码运行结果如下:

    可以发现支持数据的多次接收。那么这段代码是否可以接收多个客户端的请求呢。实例如下:

    可以发现无法处理第二个客户端的请求,思考后,这是因为accept只有一次,那么将accept放入到while中是否可以呢?

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    
    #define BUFFER_LENGTH		1024
    
    int main() 
    {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
        servaddr.sin_port = htons(9999);
    
        if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr)))
         {
    		printf("bind failed: %s", strerror(errno));
    		return -1;
        }
    
        listen(sockfd, 10); 
    
        struct sockaddr_in clientaddr;
        socklen_t len = sizeof(clientaddr); 
    
    	while (1) 
      { //slave
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);
    		
        char buffer[BUFFER_LENGTH] = {0};
    		int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
    		if (ret == 0)
        {
    			close(clientfd);
    			break; 
    		}
    
    		printf("ret: %d, buffer: %s\n", ret, buffer);
    
    		send(clientfd, buffer, ret, 0);
    	}
    
        // getchar();
    }

    代码运行结果:

    可以发现只能发送一次,这是因为第二次发送的时候调用了accept,此时没有连接,会被阻塞住。那么为了在while中即调用accept,又可以调用recv,所以考虑使用多线程实现。代码如下所示。该段代码编译需要使用gcc -o xxx xxx.c -lpthread.

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    #include <pthread.h>
    
    #define BUFFER_LENGTH		1024
    
    void *client_thread(void *arg) 
    {
    
    	int clientfd = *(int*)arg;
    
    	while (1) 
      { //slave
    	
    		char buffer[BUFFER_LENGTH] = {0};
    		int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
    		if (ret == 0) {
    			close(clientfd);
    			break; 
    		}
    
    		printf("ret: %d, buffer: %s\n", ret, buffer);
    
    		send(clientfd, buffer, ret, 0);
    	}
    
    }
    
    int main() 
    {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
      	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
      	servaddr.sin_port = htons(9999);
    
      	if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr)))
      	{
       	 	printf("bind failed: %s", strerror(errno));
        	return -1;
      	}
    
      	listen(sockfd, 10); 
    
      	struct sockaddr_in clientaddr;
      	socklen_t len = sizeof(clientaddr); 
    
    	while (1) 
      	{ //slave
        	int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        	printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);
        
        	pthread_t threadid;
    		pthread_create(&threadid, NULL, client_thread, &clientfd);
    	}
        // getchar();
    }

    代码运行结果:

    从上图结果可以发现可以达到我们的预期目标。但是在客户端多的时候,比如有10000个客户端,无法创建10000个线程。那么如何处理这个问题。此时就需要用到本章的重点知识,网络io多路复用技术,对多个服务进行管理。如select, pool, epool,kqueue(mac)等。

第16.2节 select

16.2.1 介绍

网络IO复用是指在单线程或少数线程的情况下,通过一种机制同时监控多个IO流的状态,当某个IO流有数据到达时,就通知相应的线程进行处理。其中,select是一种比较常用的IO多路复用技术,它可以同时监控多个文件描述符,当某个文件描述符就绪(一般是读就绪或写就绪)时,就会通知应用程序进行相应的操作。

16.2.2 代码案例

  1. 代码

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    #include <sys/select.h>
    
    #define BUFFER_LENGTH		1024
    
    int main() 
    {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
      servaddr.sin_port = htons(9999);
    
      if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr)))
      {
        printf("bind failed: %s", strerror(errno));
        return -1;
      }
    
      listen(sockfd, 10); 
    
      struct sockaddr_in clientaddr;
      socklen_t len = sizeof(clientaddr); 
    
      fd_set rfds, rset;
    	FD_ZERO(&rfds);
    	FD_SET(sockfd, &rfds);
    
    	int maxfd = sockfd;
    	int clientfd = 0;
    
    	while (1) 
      { 
    
    		rset = rfds;
    		
    		int nready = select(maxfd+1, &rset, NULL, NULL, NULL);
    
    		if (FD_ISSET(sockfd, &rset)) 
        { 
    
    			clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
    			printf("accept: %d\n", clientfd);
    
    			FD_SET(clientfd, &rfds);
    			if (clientfd > maxfd) maxfd = clientfd; // 这是因为有回收机制,所以始终找最大值。
    
    			if (-- nready == 0) continue;
    		}
    
    		int i = 0;
    		for (i = sockfd + 1; i <= maxfd; i++) {
    
    			if (FD_ISSET(i, &rset))
          {
    
    				char buffer[BUFFER_LENGTH] = {0};
    				int ret = recv(i, buffer, BUFFER_LENGTH, 0);
    				if (ret == 0) 
            {
    					close(i);
    					break; 
    				}
    
    				printf("ret: %d, buffer: %s\n", ret, buffer);
    
    				send(i, buffer, ret, 0);
    			}
    		}
    	}
        // getchar();
    }

    运行结果

    发现可以达到同样的目的。

  2. 代码说明

    1. select(maxfd, &rfds, &wfds, efds, timeout)

      maxfd - 表示的是所有的accept连接中返回值最大的id,

      rfds - 可读的集合,记录了可以读的io集合

      wfds - 可写的集合,记录了可以写的io集合

      efds - 出错的集合,记录了上次出错的io集合

      timeout - 表示多久轮询上面三个集合一次

      返回值 - 表示当前有多少个io连接

    2. fd_set rfds: fd_set内部是按照bit位来的

    3. FD_ZERO(&rfds): 是设置fd_set中的每一个bit位为0

    4. FD_SET(x, &rfds):将rfds中的dix位置为1

    5. FD_ISSET(socked, &rfds):表示查询rfds的第socked位是否为1

  3. 关于send是否可以写的问题,是应用将需要发送的数据放入到内核的sendbuffer中。通常而言都是可以send成功的,只有在循环send或sendbuffer()非常小的时候才会失败,这个配置可以在sysconfig文件中修改,所以send是否可以写是需要判断的。

16.2.3 缺陷

  1. 由于select的fd_set需要通过select先传入到内核,再从内核传出来,所以会很消耗性能。
  2. 在select中,一个fd_set是128个bit位,如果io的数量超过128个后,就会出现资源不够。
  3. 在内核中会循环遍历,这也会比较耗时。

第16.3节 poll

16.3.1 介绍

poll是一种常见的IO多路复用技术,它可以同时监视多个文件描述符,当其中任意一个文件描述符就绪时,就会通知应用程序进行相应的操作。

16.3.2 代码案例

  1. 代码

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    #include <sys/poll.h>
    
    #define BUFFER_LENGTH		1024
    #define POLL_SIZE   1024
    int main() 
    {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
      	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
     	servaddr.sin_port = htons(9999);
    
      	if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr)))
      	{
        	printf("bind failed: %s", strerror(errno));
        	return -1;
      	}
    
      	listen(sockfd, 10); 
    
      	struct pollfd fds[POLL_SIZE] = {0};
    
    	fds[sockfd].fd = sockfd;
    	fds[sockfd].events = POLLIN; // 表示可读
    
    	int maxfd = sockfd;
    	int clientfd = 0;
    
    	while (1) {
    
    		int nready = poll(fds, maxfd + 1, -1);
    
    		if (fds[sockfd].revents & POLLIN) 
        	{
    
    			clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
    			printf("accept: %d\n", clientfd);
    
    			fds[clientfd].fd = clientfd;
    			fds[clientfd].events = POLLIN;
    
    			if (clientfd > maxfd) maxfd = clientfd;
    
    			if (-- nready == 0) continue;
    			
    		}
    
    		int i = 0;
    		for (i = 0;i <= maxfd; i ++) 
            {
    			if (fds[i].revents & POLLIN) 
          		{
    				char buffer[BUFFER_LENGTH] = {0};
    				int ret = recv(i, buffer, BUFFER_LENGTH, 0);
    				if (ret == 0) 
            		{
    					fds[i].fd = -1; // 需要将fd置为无效才行。
    					fds[i].events = 0;
    					
    					close(i);
    					break; 
    				}
    
    				printf("ret: %d, buffer: %s\n", ret, buffer);
    				send(i, buffer, ret, 0);
    			}
    		}
    	}
        // getchar();
    }
  2. 代码说明

    结构中的events是我们传入到内核中的可读的项,而revents是从内核中反馈出来的。

    cpp 复制代码
    struct poolfd
    {
    	int fd;
    	short events;
    	short revent;
    }

16.3.3 相对select的优缺点

  1. 与select相比,poll没有最大文件描述符数量的限制,因此可以处理更多的并发连接。poll的使用方法与select类似,但是poll的效率比select更高,因为它不需要遍历整个文件描述符集合,而是只需要遍历就绪的文件描述符集合。
  2. pool只有一个数组
  3. 接口简单只有一个

第16.4节 epoll

16.4.1 介绍

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。epoll可以同时处理大量的文件描述符,是基于事件驱动的IO操作方式,可以取代select和poll函数。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。另外,epoll使用红黑树存储管理事件,每次插入和删除事件的效率都是O(logn)的,其中n是红黑树中节点的个数。

16.4.2 代码案例

  1. 代码

    cpp 复制代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <fcntl.h>
    #include <sys/epoll.h>
    
    #define BUFFER_LENGTH		1024
    
    int main() 
    {
    	//open
    	
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
    	
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    	servaddr.sin_family = AF_INET;
      	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
      	servaddr.sin_port = htons(9999);
    
      	if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr)))
      	{
        	printf("bind failed: %s", strerror(errno));
        	return -1;
      	}
    
      	listen(sockfd, 10); 
      	struct sockaddr_in clientaddr;
      	socklen_t len = sizeof(clientaddr);
      
      	int epfd = epoll_create(1);//1000  //list
    
    	struct epoll_event ev;
    	ev.events = EPOLLIN;
    	ev.data.fd = sockfd;
    
    	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); //
    
    	struct epoll_event events[1024] = {0};
    
    	while (1)
      	{  // mainloop
    
    		int nready = epoll_wait(epfd, events, 1024, -1); //-1, 0, 
    		if (nready < 0) continue;
    
    		int i = 0;
    		for (i = 0;i < nready;i ++) {
    
    			int connfd = events[i].data.fd;
    
    			if (sockfd == connfd) 
         		{ // accept
    				
    				int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
    				if (clientfd <= 0) 
            		{
    					continue;
    				}
    
    				printf(" clientfd: %d\n", clientfd);
    				
    				ev.events = EPOLLIN | EPOLLET;
    				ev.data.fd = clientfd;
    				epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
    				
    				}
          		else if (events[i].events & EPOLLIN) 
          		{
    				char buffer[10] = {0};
    				short len = 0;
    				recv(connfd, &len, 2, 0);
    				len = ntohs(len);
    
    				int n = recv(connfd, buffer, 10, 0);
    				if (n > 0) 
            		{
    					printf("recv : %s\n", buffer);
    					send(connfd, buffer, n, 0);
    				} 
            		else if (n == 0) 
            		{
    
    					printf("close\n");
    
    					epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
    				
    					close(connfd);
    				
    				}
    			}
    		}
    	}
        // getchar();
    }

    运行结果

16.4.3 相对select,pool的优点

  1. 可以处理大量请求
  2. 底层使用红黑树实现,效率更高

补充:

  1. io的数量意味着什么?意味着并发
相关推荐
Wy_编程13 分钟前
Linux-文本搜索工具grep
linux·运维·服务器
xujiangyan_16 分钟前
linux的sysctl系统以及systemd系统。
linux·服务器·网络
Lovyk19 分钟前
Linux Shell 常用操作与脚本示例详解
linux·运维·服务器
朱皮皮呀3 小时前
Spring Cloud——服务注册与服务发现原理与实现
运维·spring cloud·eureka·服务发现·php
吱吱企业安全通讯软件4 小时前
吱吱企业通讯软件保证内部通讯安全,搭建数字安全体系
大数据·网络·人工智能·安全·信息与通信·吱吱办公通讯
yuanpan4 小时前
ubuntu系统上的conda虚拟环境导出方便下次安装
linux·ubuntu·conda
云边云科技5 小时前
零售行业新店网络零接触部署场景下,如何选择SDWAN
运维·服务器·网络·人工智能·安全·边缘计算·零售
rainFFrain5 小时前
Boost搜索引擎项目(详细思路版)
网络·c++·http·搜索引擎
AOwhisky5 小时前
Linux 文本处理三剑客:awk、grep、sed 完全指南
linux·运维·服务器·网络·云计算·运维开发
Gavin_9156 小时前
从零开始部署经典开源项目管理系统最新版redmine6-Linux Debian12
linux·ruby on rails·开源·debian·ruby·redmine