网络编程10——poll的多路IO转接 | 用poll函数实现服务器 | 拓展监听上限方法

poll函数原型

在这里插入图片描述
fds:监听的文件描述符数组
nfds:监听数组的实际有效监听个数
timeout:超时时长,单位是milliseconds毫秒,传入-1阻塞等待;传入0立即返回不阻塞进程;传入>0为等待指定毫秒数,如过当前系统时间精度不够毫秒,向上取值
返回值: 返回满足对应监听事件的文件描述符总个数

在这里插入图片描述
fd:所要监听的文件描述符
events:所要监听的文件描述符的事件(读事件POLLIN、写事件POLLOUT、异常事件POLLERR
revents:return events传入时给0,如果满足对应事件返回非0—>POLLIN 、POLLOUT、 POLLERR

poll函数使用

在这里插入图片描述

注意read的返回值
大于0:表示实际读到的字节数
等于0:在socket中表示对端关闭,close()
等于-1:要分情况,看errno:
若errno== EINTR,表示被异常终端;
若errno == EAGIN或EWOULDBLOCK,表示以非阻塞方式读数据,但没有数据,需要再次读;

errno等于ECONNRSET说明连接被重置,需要close(),移除监听队列

poll的优缺点:
优点:
自带数据结构,可以将 监听事件集合 和 返回事件集合 分离
可拓展监听上限,超出1024限制:
在这里插入图片描述
缺点:
不能跨平台,只能在Linux上实现
无法直接定位满足监听事件的文件描述符,只能通过循环轮询找到

poll函数实现服务器

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<errno.h>
#include<ctype.h>
#include<poll.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000
#define OPEN_MAX 1024

int main(int argc, char* argv[])
{
	//主要就是监听的地方不一样,其他的socketbindlisten包括accept连接上之后的读写都是一样的
	int i, j, maxi, listenfd, connfd, sockfd;//sockfd用于保存pfds中的fd
	int nready;//接收poll返回值,记录满足监听事件的fd个数
	ssize_t n;//读写数据块的大小
	char buf[MAXLINE], str[INET_ADDRSTRLEN];
	socklen_t clilen;

	struct pollfd client[OPEN_MAX];//------poll的结构体数组?
	struct sockadddr_in cliaddr, servaddr;//bind的结构体
//-----------------------------------------socket
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//重用端口
	//bind
	bezro(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
	Bind(listenfd, (struct sockaddr*)servaddr, sizeof(servaddr));
	Listen(listenfd, 128);
	//先将要监听文件描述符lfd存入`struct pollfd client[]`中,第一个就是lfd
	client[0].fd = listenfd;
	client[0].events = POLLIN;//lfd监听普通读事件
	//用-1初始化client[]里的其余元素的fd,0已经存了lfd了
	for(i = 0; i<OPEN_MAX; i++)
	{
		client[i].fd = -1//之后-1就表示这个位没事件
	}
	maxi = 0;//client[]数组有效元素中最大元素下标,方便之后轮询查找有事件的cfd
//---------------------------------------------开始poll监听
	while(1)
	{
		nready = poll(client, maxi+1,-1);//client监听的文件描述符数组,maxi+1实际监听数
		//先判断有没有读事件
		if(client[0].revents & POLLIN)//revents返回对应事件, &POLLIN查看是否有新事件
		{
			//有,给分配新cfd
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
			printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
			//给新的cfd分配client数组位置
			for(i = 0; i<OPEN_MAX;i++)//去找空位置,即-1
			{
				if(client[i].fd < 0)
				{
					client[i].fd = connfd;
					break;
				}
			}
			if(i == OPEN_MAX)
				perr_exit("too many clients");
			
			client[i].events = POLLIN;//设置新cfd的监听事件,监控读
			if(i > maxi).//维护最大有效下标
				maxi = i;
			if(--nready == 0)
				continue;//没有更多的就绪事件时,继续回到poll阻塞
		}
		//------------------------以上,lfd监听判断完,处理完
		//前面的都没有返回,则表示一定还有cfd满足监听事件,轮询找是哪个事件
		for(i = 1; i <= maxi; i++)//maxi作用在这里!
		{
			if((sockfd = client[i].fd) < 0)//空位
				continue;
			//开始一一判断,是不是有读事件 &POLLIN
			if(client[i].revents & POLLIN)
			{
				if((n = Read(sockfd, buf, MAXLINE)) < 0)//判断一下消息号,是关闭了还是非阻塞
				{
					if(errno == ECONNRSET)
					{
						printf("client [%d] aborted connection\n",i);//说明连接被重置,需要close(),移除监听队列  
						Close(sockfd);
						client[i].fd = -1;//重置
					}else
					{
						//正常读写
						for(j = 0; j<n; j++)
							buf[j] = toupper(buf[j]);
						Write(sockfd,buf,n);
					}
					if(--nready <=0)
						break;
				}
			}
		}
	}
	return 0;
}
原创文章 119 获赞 27 访问量 5977

猜你喜欢

转载自blog.csdn.net/qq_37299596/article/details/105991959