網絡編程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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章