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;
}