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

news2024/11/24 1:23:39

第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的数量意味着什么?意味着并发

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【UE 材质】切换颜色、纹理时的过渡效果

效果 步骤 1. 新建一个工程&#xff0c;创建Basic关卡 2. 创建一个材质&#xff0c;这里命名为“M_Plane”&#xff0c;打开这个材质&#xff0c;在材质图表中添加如下节点 注意“Noise”节点中的函数选择“Voronoi” 3. 对材质“M_Plane”创建材质实例 4. 在场景中放置一个平…

idea SpringBoot target 不自动更新,不自动生成问题

如题,为什么不自动更新? 我使用Maven中的insert命令生成了target文件夹,但是,修改了代码重新启动还是不会自动更新,检查了文件,发现了resources文件夹是一个普通文件夹,没有标记为项目资源文件夹,所以idea不会给你自动生成的

Cglib动态代理从入门到掌握

Cglib 动态代理 本文的写作目的是为了探究 Spring 框架中在使用Transactional标注的方法中使用 this 进行自调用时事务失效的原因&#xff0c;各种视频教程中只是简单指出 this 指向的不是代理类对象&#xff0c;而是目标类对象&#xff0c;但是并没有解释为什么 this 不是代理…

工业性能CCD图像处理

硬件部分 软件部分 CCD新相机的调试处理(更换相机处理,都要点执行检测来查看图像变化) 问题:新相机拍摄出现黑屏,图像拍摄不清晰,(可以点击图像,向下转动鼠标的滚轮(Mouse Wheel)放大图像) 解决办法:进入CCD的设定,选择对应的相机,调试好参数(如下图) 选择好相…

技术Leader:像李云龙一样打造学习型团队

今天跟大家分享一下怎么样构建一个学习型的团队。 首先对于计算机行业而言&#xff0c;不明而喻&#xff0c;我们要接受的东西真的太多了。我们接触的信息和变化也太多了。如果只是因循守旧&#xff0c;排斥新东西&#xff0c;那么我们被时代淘汰只是个时间问题。 想当年我大…

【电子取证:FTK IMAGER 篇】DD、E01系统镜像动态仿真

​ 文章目录 【电子取证&#xff1a;FTK Imager 篇】DD、E01系统镜像动态仿真一、DD、E01系统镜像动态仿真 &#xff08;一&#xff09;使用到的软件 1、FTK Imager (v4.5.0.3)2、VMware Workstation 15 Pro (v15.5.2)&#xff08;二&#xff09;FTK Imager 挂载镜像 1、选择 …

thinkphp6入门(13)-- 一对多关联模型

定义一对一关联&#xff0c;例如&#xff0c;一个用户都有多个工作经历。 一、两表 1.用户表:user 2.工作经验表&#xff1a;work_experience user表的id关联work_experience表的user_id。 注意看&#xff0c;user_id1的有2条工作经验 二、数据模型 主表模型&#xff1a;…

Centos7 安装Redis详细教程

1. 安装依赖 redis是由C语言开发&#xff0c;因此安装之前必须要确保服务器已经安装了gcc&#xff0c;可以通过如下命令查看机器是否安装&#xff1a; gcc -v如果没有安装则通过以下命令安装&#xff1a; yum install -y gcc2.下载redis安装包并解压 # 下载&#xff0c;我是…

欧拉函数与欧拉定理

文章目录 AcWing 873. 欧拉函数题目链接欧拉函数欧拉函数的证明思路CODE时间复杂度分析 AcWing 874. 筛法求欧拉函数题目链接问题分析与时间复杂度CODE思路 欧拉定理 AcWing 873. 欧拉函数 题目链接 https://www.acwing.com/activity/content/problem/content/942/ 欧拉函数 …

java方法引用语法规则以及简单案例

目录 一、方法引用1.1 什么是方法引用1.2 方法引用的语法规则1.3 构造器引用1.4 方法引用的简单案例 参考资料 一、方法引用 1.1 什么是方法引用 方法引用是 Lambda 表达式的一种简写形式&#xff0c;用于表示已有方法的直接引用。 类似于lambda表达式&#xff0c;方法引用也…

插入算法(C语言)

#include<cstdio> #include<iostream> #define N 9 using namespace std; int main() {int arr[N1] { 1,4,7,13,16,19,22,25,280 }; int in,i,j;//要插入的数字//打印要插入数字的数组所有元素printf("插入前的数组: ");for ( i 0; i <N; i){print…

阶段十-java新特性

JDK9新特性 1.模块化系统 jar包结构的变化 jar -》model -》package -》class 通过不同的模块进行开发 每个模块都有自己的模块配置文件module-info.java 2.JShell JDK9自带的命令行开发&#xff0c;在进行简单的代码调试时可以直接编译使用 可以定义变量&#xff0c;方法&…

【改进YOLOv8】矿物尺寸图像分析系统:融合位置感知循环卷积(ParC)改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着科技的不断发展&#xff0c;计算机视觉技术在各个领域中得到了广泛的应用。其中&#xff0c;物体检测是计算机视觉领域中的一个重要研究方向。物体检测的目标…

工作随记:oracle 19c客户端通过service访问PDB异常问题

文章目录 概要技术测试分析测试1&#xff1a;测试2&#xff1a;测试3&#xff1a;测试4&#xff1a; 解决方案&#xff1a;1、修改service2、修改pdb名称 总结 概要 应用端访问提示错误信息为&#xff1a;VersionHelper异常!未将对象引用设置到对象的实例&#xff01; 此问题…

jdk21升级,asm报错Unsupported class file major version 65

环境 jdk21升级&#xff0c;asm报错&#xff0c;spring-core版本5.3.18&#xff0c;项目springboot版本为2.6.6 报错明细 Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file ve…

交易历史记录20231205 记录

昨日回顾&#xff1a; select top 10000 * from dbo.CODEINFO A left join dbo.全部&#xff21;股20231205010101 B ON A.CODE B.代码 left join dbo.全部&#xff21;股20231205CONF D on A.CODED.代码left join dbo.全部&#xff21;股20231205 G on A.CODEG.代码 left…

Ubuntu-rsyslog和systemd-journald日志服务

rsyslog日志服务 rsyslog作为传统的系统日志服务&#xff0c;把所有收集到的日志都记录到/var/log/目录下的各个日志文件中。 常见的日志文件如下&#xff1a; /var/log/messages 绝大多数的系统日志都记录到该文件 /var/log/secure 所有跟安全和认证授权等日志…

Tcl语言语法精炼总结

一、置换符号 1.变量置换 $ TCl解释器会将认为$后面为变量名&#xff0c;将变量名置换成它的值 2.命令置换 [] []内是一个独立的TCL语句 3.反斜杠置换 \ 换行符、空格、[、$等被TCL解释器当作特殊符号处理。加上反斜杠后变成普通字符 \t TAB \n 换行符 4.双引号 “” “…

Tcon基础知识

1、TCON&#xff0c;就是 Timing Controller 的缩写。从主芯片输出的要在 TFT 显示屏上显示的数据&#xff0c;在经过 TCON 模块后可以变换生成 Panel 可以直接利用的 DATA 信号和驱动器&#xff08;包括 source driver 和 gate driver&#xff09;的控制信号。 TV 市场上 TCO…