网络编程5之poll、epoll、超时检测

1.poll机制

poll的实现和select非常相似,只是文件描述符fd集合的方式不同。

poll使用struct pollfd结构而不是select的fd_set结构,其他的都差不多。

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能:poll机制

参数:
    fds:struct pollfd类型的数组
    nfds:准备的文件描述符的个数
    timeout:-1 阻塞,直到文件描述符准备好

返回值:
     成功返回准备好的文件描述符的个数,失败返回-1
            
        truct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
              };
                 fd:文件描述符;
                 event:请求的事件(读、写、异常)
                 revents:对请求事件的反馈(读、写、异常)
          POLLIN:读事件
          POLLOUT:写事件
          
        eg:
            

struct pollfd fds[3];

    memset(fds,0,sizeof(fds));
    fds[0].fd=fd1;
    fds[0].events=POLLIN;

eg: tcpserver_poll.c

/*===============================================
*   文件名称:tcpserver_select.c
*   创 建 者:     
*   创建日期:2022年08月19日
*   描    述:
================================================*/
/*===============================================
*   文件名称:clinet_unix_udp.c
*   创 建 者:     
*   创建日期:2022年08月18日
*   描    述:
================================================*/
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{ 
    
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        perror("socket");
        return -1;
    }
	
	//端口复用
	int on =1;
	int k = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	if(k<0)
	{
		perror("setsockopt");
		return -1;
	}
	
	struct sockaddr_in server;
	memset(&server,0,sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(8888);
	server.sin_addr.s_addr=inet_addr("0");
	
	int len = sizeof(server);
	
	int ret = bind(sockfd,(struct sockaddr *)&server,len);
	if(ret<0)
	{
		perror("bind");
		return -1;
	} 
	
	ret =listen(sockfd , 5);
	if(ret<0)
	{
		perror("listen");
		return -1;
	}

	//1.创建结构体数组
	struct pollfd fds[100];
	//2.清空数组
	memset(&fds,0,sizeof(fds));
	
	//3.添加第一个元素
	fds[0].fd=sockfd;
	fds[0].event=POLLIN;
	
	int m = 1;//结构体数组的长度
	int n = 1;//结构体数组的下标
	int i;
	
	char buf[64] ={0};
		
	while(1)
	{
		
		
		ret = poll(fds,m,-1);//阻塞,直到有文件描述符准备好
		if(ret<0)
		{
			perror("select");
			return -1;
		}
		
		for(i=0;i<m;i++)//检测谁准备好
		{
			if(fds[i].revents & POLLIN )//i准备好
			{
				int fd = fds[i].fd;
				if(fd==sockfd)	//处理sockfd,建立连接
				{
					int connfd = accept(i,NULL,NULL);
					if(connfd<0)
					{
						perror("accept");
						return -1;
					}
					printf("%d is link\n",connfd);
					
					n=connfd-sockfd;
					fds[n].fd=connfd;
					fds[n].event=POLLIN;
					if(m<connfd-2)	//m代表下标的最大值
					{
						m=connfd-2;	
					}
				}
				else//connfd准备好
				{
					ret = recv(fd,buf,sizeof(buf),0);
					if(ret<0)
					{
						perror("recv");
						return -1;
					}
					else if(ret==0)
					{
						printf("%d is unlink\n",fd);
						memset(&fds[i],0,sizeof(fds[i]));//删除队列
						
					}
					else
					{
						printf("%d :message =%s\n",i,buf);
						memset(buf,0,sizeof(buf));
					}
				}
				
			}
		}
	}
	

   close(sockfd);
    
    return 0;
} 

2.epoll机制

1.epoll使用红黑树来管理所有的文件描述符集合,不受大小限制,更新集合时,不需 要重新拷贝整个集合,直接更新红黑树即可

2.利用call back来知道文件描述符是否就绪,只关心已经就绪的文件描述符,不需要遍 历所有的文件描述符。

1.epoll_create

#include <sys/epoll.h>

int epoll_create(int size);

功能:创建一棵树
    
参数:
    size:树的节点
返回值:
    成功返回准备好的文件描述符epfd,失败返回-1

2.epoll_ctl

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:控制epoll

参数:
    epfd:epoll_create的返回值
    op:
        EPOLL_CTL_ADD:添加文件描述符
        EPOLL_CTL_DEL:删除文件描述符
    fd:准备好的文件描述符
    event:指定的结构体(一个)
    
返回值:
    成功返回0,失败返回-1;

    typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
            events:
                EPOLLIN:读事件
                EPOLLOUT:写事件
           
    eg:
        struct epoll_event ev;
        ev.events=;//事件
        ev.data.fd;//

3.epoll_wait

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

功能:
    等待文件描述符就绪
    
参数:
    epfd:epoll_creat的返回值
    events:指定的结构体数组;
        struct epoll_event evs[size];//结构体数组
    maxevents:[size]
    time_out:超时检测,-1阻塞,等待文件描述符就绪    

返回值:
    成功,返回准备好的文件描述符,没有准备好的返回0;
    失败返回-1;

/*===============================================
*   文件名称:tcpserver_select.c
*   创 建 者:     
*   创建日期:2022年08月19日
*   描    述:
================================================*/
/*===============================================
*   文件名称:clinet_unix_udp.c
*   创 建 者:     
*   创建日期:2022年08月18日
*   描    述:
================================================*/
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>


int main(int argc, char *argv[])
{ 
    
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        perror("socket");
        return -1;
    }
	
	//端口复用
	int on =1;
	int k = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	if(k<0)
	{
		perror("setsockopt");
		return -1;
	}
	
	struct sockaddr_in server;
	memset(&server,0,sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(8888);
	server.sin_addr.s_addr=inet_addr("0");
	
	int len = sizeof(server);
	
	int ret = bind(sockfd,(struct sockaddr *)&server,len);
	if(ret<0)
	{
		perror("bind");
		return -1;
	} 
	
	ret =listen(sockfd , 5);
	if(ret<0)
	{
		perror("listen");
		return -1;
	}

	//1.创建一课树
	int epfd = epoll_create(1003);
	if(epfd<0)
	{
		perror("epoll_create");
		return -1;
	}
	//2.添加到树中
	struct epoll_event ev,evs[100];//ev用于修改evs
	ev.events = EPOLLIN;	//设置为增加
	ev.data.fd = sockfd;	
	epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);//通过epfd把ev加到树中
	
	int m,i;//m用于接收epoll_wait的返回值,准备就绪的文件描述符
		//i用于循环
	
	char buf[64] ={0};
			
	while(1)
	{
		m = epoll_wait(epfd,evs,100,-1);
		if(m==-1)
		{
			perror("epoll_wait");
			return -1;
		}
		for(i=0;i<m;i++)
		{
			if(evs[i].events & EPOLLIN)//事件是可读的
			{
				int fd = evs[i].data.fd;
				if(fd==sockfd)//处理sockfd,建立连接
				{
					int connfd = accept(fd,NULL,NULL);
					printf("%d is link\n",connfd);
					
					ev.events = EPOLLIN;
					ev.data.fd = connfd;
					epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);//把connfd添加到树中
				}
				else//处理connfd
				{
					ret = recv(fd,buf,sizeof(buf),0);
					if(ret<0)
					{
						perror("recv");
					}
					else if(ret == 0)
					{
						printf("%d is unlink\n",fd);
						epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev);//从树中删除fd
						close(fd);
					}
					else
					{
						printf("%d message=%s\n",fd,buf);
						memset(buf,0,sizeof(buf));	
					}
				}
			}
		}		
	}
	

   close(sockfd);
    
    return 0;
} 

3.超时检测

方法1:设置套接字属性,设置超时值

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>

int tcpserver_init(int port);
int recv_data(int connfd);

int main(int argc, const char *argv[])
{

	int sockfd = tcpserver_init(6666);

	struct sockaddr_in clientaddr;
	
	int m = sizeof(clientaddr);

	while(1)
	{
		printf("wait a client.............\n");

		//connfd 进行数据的收发
		int connfd = accept(sockfd,  (struct sockaddr *)&clientaddr , &m );
		if(connfd < 0)
		{
			perror("accept");
			return -1;
		}

		printf("client ip: %s client port: %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

		struct  timeval tv = {5,0}; 

		setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

		recv_data(connfd);
	}

	close(sockfd);

	return 0;
}



int tcpserver_init(int port)
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		perror("sockfd");
		return -1;
	}

	printf("sockfd = %d\n", sockfd);

	//端口复用函数:解决端口号被系统占用的情况
	int on = 1;
	int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	if(k == -1)
	{
		perror("setsockopt");
		return -1;
	}

	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0 ,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(port);
	serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址

	int len = sizeof(serveraddr);

	int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}


	ret = listen(sockfd, 5);
	if(ret == -1)
	{
		perror("listen");
		return -1;
	}

	return sockfd;
}

int recv_data(int connfd)
{
		//接收和发送数据
		char buf[1024] = {0};

		while(1)
		{
			int n = recv(connfd, buf, sizeof(buf), 0);	
			if(n < 0)
			{
				printf("%d\n", errno);
				if(errno == 11)
				{
					printf("overtime\n");
					continue;
				}

				perror("read");
				return -1;
			}
			else if(n == 0) //客户端关闭了
			{
				close(connfd);
				break;	
			}

			printf("message:%s\n", buf);

			memset(buf, 0 , sizeof(buf));
		}

		return 0;
}

方法2:设置闹钟

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>

int tcpserver_init(int port);
int recv_data(int connfd);

void signal_handler(int sig)
{
	printf("overtime\n");
	//......
}

int main(int argc, const char *argv[])
{
	signal(SIGALRM, signal_handler);

	alarm(5);

	int sockfd = tcpserver_init(6666);

	struct sockaddr_in clientaddr;
	
	int m = sizeof(clientaddr);

	while(1)
	{
		printf("wait a client.............\n");

		//connfd 进行数据的收发
		int connfd = accept(sockfd,  (struct sockaddr *)&clientaddr , &m );
		if(connfd < 0)
		{
			perror("accept");
			return -1;
		}

		printf("client ip: %s client port: %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

		recv_data(connfd);
	}

	close(sockfd);

	return 0;
}



int tcpserver_init(int port)
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		perror("sockfd");
		return -1;
	}

	printf("sockfd = %d\n", sockfd);

	//端口复用函数:解决端口号被系统占用的情况
	int on = 1;
	int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	if(k == -1)
	{
		perror("setsockopt");
		return -1;
	}

	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0 ,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(port);
	serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址

	int len = sizeof(serveraddr);

	int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}


	ret = listen(sockfd, 5);
	if(ret == -1)
	{
		perror("listen");
		return -1;
	}

	return sockfd;
}

int recv_data(int connfd)
{
		//接收和发送数据
		char buf[1024] = {0};

		while(1)
		{
			int n = recv(connfd, buf, sizeof(buf), 0);	
			if(n < 0)
			{
				perror("read");
				return -1;
			}
			else if(n == 0) //客户端关闭了
			{
				close(connfd);
				break;	
			}

			printf("message:%s\n", buf);

			memset(buf, 0 , sizeof(buf));
		}

		return 0;
}

方法3:在select函数 或poll函数中设置超时值

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

int main(int argc, const char *argv[])
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		perror("sockfd");
		return -1;
	}

	printf("sockfd = %d\n", sockfd);

	//端口复用函数:解决端口号被系统占用的情况
	int on = 1;
	int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	if(k == -1)
	{
		perror("setsockopt");
		return -1;
	}

	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0 ,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(8888);
	serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址

	int len = sizeof(serveraddr);

	int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}

	ret = listen(sockfd, 5);
	if(ret == -1)
	{
		perror("listen");
		return -1;
	}

	//select机制	
	fd_set readfds,tmpfds;

	FD_ZERO(&readfds);

	FD_SET(sockfd, &readfds);

	int maxfd = sockfd;

	tmpfds = readfds;

	char buf[64] = {0};

	int  i;

	struct timeval tv = {5, 0};

	while(1)
	{
		readfds = tmpfds;

		ret = select(maxfd+1, &readfds, NULL, NULL, &tv);
		if(ret == -1)
		{
			perror("select");
			exit(-1);
		}
		else if(ret == 0) //超时检测
		{
			printf("overtime\n");
			tv.tv_sec = 2;
			continue;
		}

		for(i=sockfd; i<maxfd+1; i++)
		{
			if(FD_ISSET(i, &readfds))
			{
				if(i== sockfd)
				{
					printf("%d 已经准备好了\n", i);

					//建立连接,用accept函数
					int connfd = accept(i, NULL, NULL);

					printf("%d is link\n", connfd);

					FD_SET(connfd, &tmpfds);

					if(maxfd < connfd)
					{
						maxfd = connfd;
					}
				}

				else{
					
					memset(buf, 0, sizeof(buf));

					//处理connfd
					ret = recv(i, buf, sizeof(buf), 0);
					if(ret == -1)
					{
						close(i);
						perror("recv");
						return -1;
					}
					else if(ret == 0) 
					{
						printf("%d is unlink\n", i);
						FD_CLR(i, &tmpfds);
						close(i);
						break;
					}
					else{

						printf("%d:message=%s\n", i,buf);

					}
				}
			}	

		}

	}

	close(sockfd);

	return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
select、pollepoll是三种多路复用的机制,用于处理多个I/O事件的方式。它们在网络编程中常用于监听多个文件描述符的状态变化,以提高程序的并发处理能力。 1. select:select是最古老的一种多路复用机制。它的原理是通过一个位图数组来标记文件描述符的状态变化,并提供了三个监视集合,分别是读集合、写集合和异常集合。通过调用select函数,将需要监视的文件描述符及其对应的集合传入,然后select会阻塞程序,直到有文件描述符状态发生变化或超时。缺点是select所能监视的文件描述符数量存在限制。 2. pollpoll是select的改进版,它使用链表来存储文件描述符和对应的事件集合。与select不同,poll没有限制监视的文件描述符数量。通过调用poll函数,将需要监视的文件描述符及其对应的事件集合传入,然后poll会阻塞程序,直到有文件描述符状态发生变化或超时。 3. epollepoll是Linux特有的一种多路复用机制,与select和poll相比,在大规模并发连接时具有更好的性能。epoll通过在内核中创建一个事件表,将需要监视的文件描述符注册到这个事件表中,并通过epoll_ctl函数来控制事件的注册和删除。通过调用epoll_wait函数,程序可以等待多个文件描述符上的事件,并将就绪的文件描述符返回。 总结来说,select、pollepoll都是用于处理多个I/O事件的方式,但epoll在性能上更为出色。在选择使用哪种机制时,需要根据实际情况考虑并发连接数、平台兼容性以及对性能的要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值