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

第16.1节 写一个服务端代码

  1. 服务端代码

    #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)进程的命令。结果如下图。可以发现代码执行到此处的时候,程序已经开始监听了。

    1702393298534.png

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

    1702394609486.png

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

  5. 故而添加accept代码如下

    #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. 上述代码运行效果如下
    1702394975250.png
    可以发现代码阻塞在accept函数处,此时通过工具建立连接,就会继续运行。而通过代码中的#if…#endif处的代码可以将阻塞io转换为非阻塞io。

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

    #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之前点击连接,过一会儿运行结果如下图,可以发现依然可以建立连接。
    1702395456754.png
    所以该思考的结果是:accept和是否能建立连接没有关系。如在listen之后sleep(10),还没有到accept的时候建立连接也是可以的。

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

    #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();
    }
    

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

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

    #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();
    }
    

    使用第三方的网络助手工具尝试连接该端程序,代码运行结果如下:
    1702475309152.png
    通过上图可以发现recv也是阻塞的,只会阻塞一次,send 也是一次**。**

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

    #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();
    }
    

    代码运行结果如下:
    image.png
    可以发现支持数据的多次接收。那么这段代码是否可以接收多个客户端的请求呢。实例如下:
    image.png
    可以发现无法处理第二个客户端的请求,思考后,这是因为accept只有一次,那么将accept放入到while中是否可以呢?

    #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();
    }
    

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

    #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();
    }
    

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

第16.2节 select

16.2.1 介绍

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

16.2.2 代码案例

  1. 代码

    #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();
    }
    

    运行结果
    1702477855762.png
    发现可以达到同样的目的。

  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. 代码

    #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是从内核中反馈出来的。

    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. 代码
    #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();
    }
    
    运行结果
    1702480477212.png

16.4.3 相对select,pool的优点

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

补充:

  1. io的数量意味着什么?意味着并发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/242691.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据技术之Storm的安装与配置

大数据技术之Storm的安装与配置 这篇文章深入研究了大数据技术中实时计算系统 Apache Storm 的安装与配置过程。首先&#xff0c;文章介绍了 Apache Storm 在大数据处理中的重要性&#xff0c;强调其在实时数据处理领域的关键作用。随后&#xff0c;详细阐述了如何在系统中进行…

掌握Web、DNS、FTP、DHCP服务器的配置。掌握简单网络方案的规划和设计

1、Web服务器配置 2、综合设计 配置完后,所有的终端主机都要能够访问外网服务器,并进行测试。(本题可以自行选题,自行设计,但必须包含路由器、服务器(web、dns、DHCP、)、交换机及防火墙)。 3.做好规划并搭建拓扑图: 4.给PC机与服务器配置好IP,网关 5.给每个交换机…

【Spring】02 Bean 的命名

文章目录 1. 定义2. 使用优势3. 如何命名4. 注解驱动5. 最佳实践1&#xff09;使用明确的业务名词2&#xff09;避免缩写和首字母缩略词2&#xff09;不要过度使用别名 结语 在 Spring 框架中&#xff0c;Bean 是应用程序中的主要组件&#xff0c;负责承载和管理应用的核心功能…

bugku -- eval

<?phpinclude "flag.php";$a $_REQUEST[hello];eval( "var_dump($a);");show_source(__FILE__); ?> //这段代码包含了一个PHP脚本。首先&#xff0c;它包含了一个名为"flag.php"的文件。然后&#xff0c;它定义了一个变量$a&#xff0c…

SpringBootWeb入门、HTTP协议、Web服务器-Tomcat

目录 一、SpringBootWeb入门 二、HTTP协议 HTTP-请求协议 HTTP-响应协议 HTTP-协议解析 三、Web服务器-Tomcat 服务器概述 Tomcat 一、SpringBootWeb入门 直接基于SpringFramework进行开发&#xff0c;存在两个问题&#xff1a;配置繁琐、入门难度大 通过springboot就…

MATLAB读写txt文件数据与进制转换

文章目录 前言读txt文件读txt中的十进制数据到MATLAB读txt数据的其他进制数据转为十进制到MATLAB读txt内容到MATLAB 写txt文件总:将MATLAB生成的10进制数据转换成十进制和radix进制写入txt分:将MATLAB中十进制数据以radix进制数据格式写入txt文件分:将MATLAB中十进制数据写入tx…

gRPC-Gateway:高效转换 RESTful 接口 | 开源日报 No.105

grpc-ecosystem/grpc-gateway Stars: 16.4k License: BSD-3-Clause gRPC-Gateway 是一个遵循 gRPC HTTP 规范的 gRPC 到 JSON 代理生成器。它是 Google 协议缓冲编译器 protoc 的插件&#xff0c;可以读取 protobuf 服务定义并生成反向代理服务器&#xff0c;将 RESTful HTTP…

linux下配置vscode中的ros的c++调试

第一步 这块是launch.json {"version": "0.2.0","configurations": [{"name": "g - 生成和调试活动文件","type": "cppdbg","request": "launch","program": "${wo…

Facebook运营技巧详解,Facebook多店铺如何运营?

在前不久的文章中就讲过Facebook养号和广告的投放技巧&#xff0c;今天东哥就趁热打铁来接着讲讲Facebook的运营技巧&#xff0c;现在做外贸和跨境电商的人基本上都用过Facebook&#xff0c;像在流量这么庞大的平台上想要抓住更多机遇&#xff0c;懂得一些运营技巧是必不可少的…

解决前端VUE前端框架报错Error: error:0308010C:digital envelope routines::unsupported的几种方法

一、报错信息&#xff1a; Error: error:0308010C:digital envelope routines::unsupportedat new Hash (node:internal/crypto/hash:67:19)at Object.createHash (node:crypto:135:10)at module.exports (E:\Projects\platform-code\platform-cloud\ruoyi-ui\node_modules\we…

鸿蒙开发之页面与组件生命周期

一、页面间的跳转 创建文件的时候记得选择创建page文件&#xff0c;这样就可以在main->resources->profile->main_pages.json中自动形成页面对应的路由了。如果创建的时候你选择了ArkTS文件&#xff0c;那么需要手动修改main_pages.json文件中&#xff0c;添加相应的…

缓存雪崩问题与应对策略

目录 1. 缓存雪崩的原因 1.1 缓存同时失效 1.2 缓存层无法应对高并发 1.3 缓存和后端系统之间存在紧密关联 2. 缓存雪崩的影响 2.1 系统性能下降 2.2 数据库压力激增 2.3 用户请求失败率增加 3. 应对策略 3.1 多级缓存 3.2 限流与降级 3.3 异步缓存更新 3.4 并发控…

OpenHarmony应用开发——更改应用名称和图标

一、前言 相比其他&#xff0c;可能学者更希望学到的就是更改应用名称和图标&#xff0c;当一个自己的程序运行在手机上的时候&#xff0c;或许更有成就感...... 二、详细步骤 首先&#xff0c;我们要先找到声明应用图标和应用名称的地方。如下图所示&#xff0c;在entry ->…

互联网,我们的虚拟世界

同学们&#xff0c;你们知道互联网是干什么的吗&#xff1f;它就像一个虚拟的世界&#xff0c;让我们能够连接到任何地方&#xff0c;获取任何信息&#xff0c;就像你现在正在通过互联网阅读我的文章一样。 互联网 你们有没有想过&#xff0c;如果没有互联网&#xff0c;我们的…

JavaWeb笔记之MySQL数据库

#Author 流云 #Version 1.0 一、引言 1.1 现有的数据存储方式有哪些&#xff1f; Java程序存储数据&#xff08;变量、对象、数组、集合&#xff09;&#xff0c;数据保存在内存中&#xff0c;属于瞬时状态存储。 文件&#xff08;File&#xff09;存储数据&#xff0c;保存…

webpack学习-4.开发环境

webpack学习-4.开发环境 1.mode2.使用source map3.自动编译代码3.1 webpack 的 观察模式3.2 使用 webpack-dev-server3.3 使用 webpack-dev-middleware 4.总结 1.mode 本章的标题一看就是开发环境&#xff0c;那就要引入webpack配置文件的mode了。 mode 属性用于指定 Webpack …

总结了人工智能领域,能源领域,电气领域比较好中的一些sci期刊!!仅供参考

文章目录 前言一、总结了人工智能领域&#xff0c;能源领域&#xff0c;电气领域比较好中的一些sci期刊 总结 前言 期刊查询网站&#xff1a; https://www.letpub.com.cn/index.php?pagejournalapp&viewsearch 链接: 点我跳转期刊查询网站 一、总结了人工智能领域&#…

hive数据仓库工具

1、hive是一套操作数据仓库的应用工具&#xff0c;通过这个工具可实现mapreduce的功能 2、hive的语言是hql[hive query language] 3、官网hive.apache.org 下载hive软件包地址 Welcome! - The Apache Software Foundationhttps://archive.apache.org/ 4、hive在管理数据时分为元…

[Excel] vlookup函数

VLOOKUP用法 VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup])其中&#xff1a; lookup_value是你要查找的值table_array是你要在其中进行查找的表格区域col_index_num是你要返回的在table_array中列索引号range_lookup是一个可选参数&#xff0c;用于指定…

控制笔记本电脑性能,增强性能/控制发热---Thinkpad x280

1、引言 手上有一台收来办公的Thinkpad x280,但安装的联想管家却没有性能调节选项&#xff0c;导致电脑性能释放很不顺手。由于有室外办公需求&#xff0c;也就有续航需求&#xff0c;也是让它减少发热&#xff1b;同时我想在室内的时候&#xff0c;完整发挥它的性能&#xff…