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