第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的数量意味着什么?意味着并发
相关推荐
无所谓จุ๊บ30 分钟前
树莓派开发相关知识十 -小试服务器
服务器·网络·树莓派
TeYiToKu35 分钟前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
dsywws38 分钟前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
道法自然040239 分钟前
Ethernet 系列(8)-- 基础学习::ARP
网络·学习·智能路由器
yeyuningzi1 小时前
Debian 12环境里部署nginx步骤记录
linux·运维·服务器
上辈子杀猪这辈子学IT1 小时前
【Zookeeper集群搭建】安装zookeeper、zookeeper集群配置、zookeeper启动与关闭、zookeeper的shell命令操作
linux·hadoop·zookeeper·centos·debian
minihuabei1 小时前
linux centos 安装redis
linux·redis·centos
EasyCVR2 小时前
萤石设备视频接入平台EasyCVR多品牌摄像机视频平台海康ehome平台(ISUP)接入EasyCVR不在线如何排查?
运维·服务器·网络·人工智能·ffmpeg·音视频
lldhsds2 小时前
书生大模型实战营第四期-入门岛-1. Linux前置基础
linux
明月看潮生2 小时前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学