poll函数的实现及原理:
poll实际上和select类似,都是在内核中开辟一个空间,但是不是监控每种事件,poll监控的是事件结构化的事件集合;
1.函数模型:
struct pollfd{
int fd;
short events;//监控的事件
short revents;//就绪的事件
}
常用的事件就是POLLIN和POLLOUT:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
2.poll函数原理:
1.用户定义事件数组,对描述符添加关心事件;
2.poll实现监控也是将数据拷贝到内核中,然后进行轮询遍历监控,性能随着文件描述符增多而下降;
3.用户根据返回的revents判断哪一个事件就绪然后重新操作;
4.poll也不会告诉用户哪一个描述符就绪,只是告诉用户有就绪事件,需要用户遍历查找;
3.poll的优点和缺点:
优点:
1)采用事件结构的方式对描述符进行监控,简化了多个事件集合的监控方式;
2)没有描述符的具体上限;
缺点:
1)不能跨平台;
2)随着描述符增多性能下降;
poll已经被淘汰了,因为他的功能可以使用其他模型替代,而且性能高,比如epoll;select比poll的优点就是poll不能跨平台;但是比起epoll来说poll一无是处。
4.实现poll版本的服务器:
MakeFile:
poll_server:poll_server.c
gcc $^ -o $@
poll_server.c :
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int startup( int port )
{
// 1. 创建套接字
int sock = socket(AF_INET,SOCK_STREAM,0);//这里第二个参数表示TCP
if( sock < 0 )
{
perror("socket fail...\n");
exit(2);
}
// 2. 解决TIME_WAIT时,服务器不能重启问题;使服务器可以立即重启
int opt = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);// 地址为任意类型
local.sin_port = htons(port);// 这里的端口号也可以直接指定8080
// 3. 绑定端口号
if( bind(sock,(struct sockaddr *)&local,sizeof(local)) < 0 )
{
perror("bind fail...\n");
exit(3);
}
// 4. 获得监听套接字
if( listen(sock,5) < 0 )
{
perror("listen fail...\n");
exit(4);
}
return sock;
}
int main(int argc,char* argv[] )
{
if( argc != 2 )
{
printf("Usage:%s port\n ",argv[0]);
return 1;
}
// 1. 获得监听套接字
int listen_sock = startup(atoi(argv[1]));//端口号传入的时候是以字符串的形式传入的,需要将其转为整型
// 2. 初始化结构体----监听的结构列表
struct pollfd fd_list[1024];
int num = sizeof(fd_list)/sizeof(fd_list[0]);
int i = 0;
for(; i < num ; i++ )
{
fd_list[i].fd = -1;// 文件描述符
fd_list[i].events = 0;// 要监听的事件集合
fd_list[i].revents = 0;// 关心的描述符的就绪事件集合
}
// 3. 添加要关心的文件描述符的只读事件
i = 0;
for( ; i < num; i++ )
{
if( fd_list[i].fd == -1 )
{
fd_list[i].fd = listen_sock;
fd_list[i].events = POLLIN;// 关心只读事件
break;
}
}
while(1)
{
//4 . 开始调用poll等待所关心的文件描述符集就绪
switch( poll(fd_list,num,3000) )
{
case 0:// 表示词状态改变前已经超过了timeout的时间
printf("timeout...\n");
continue;
case -1:// 失败了
printf("poll fail...\n");
continue;
default: // 成功了
{
// 如果是监听文件描述符,则调用accept接受新连接
// 如果是普通文件描述符,则调用read读取数据
int i = 0;
for( ;i < num; i++ )
{
if( fd_list[i].fd == -1 )
{
continue;
}
if( fd_list[i].fd == listen_sock && ( fd_list[i].revents & POLLIN ))
{
// 1. 如果监听套接字上读就绪,此时提供接受连接服务
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock,(struct sockaddr *)&client,&len);
if(new_sock < 0)
{
perror("accept fail...\n ");
continue;
}
//获得新的文件描述符之后,将该文件描述符添加进数组中,以供下一次关心该文件描述符
int i = 0;
for( ; i < num; i++ )
{
if( fd_list[i].fd == -1 )//放到数组中第一个值为-1的位置
break;
}
if( i < num )
{
fd_list[i].fd= new_sock;
fd_list[i].events = POLLIN;
}
else
{
close(new_sock);
}
printf("get a new link![%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
continue;
}
//2. 此时关心的是普通文件描述符
// 此时提供读取数据的服务
if( fd_list[i].revents & POLLIN )
{
char buf[1024];
ssize_t s = read(fd_list[i].fd,buf,sizeof(buf)-1);
if( s < 0 )
{
printf("read fail...\n");
continue;
}
else if( s == 0 )
{
printf("client quit...\n");
close(fd_list[i].fd);
fd_list[i].fd = -1;
}
else
{
buf[s] = 0;
printf("client# %s\n",buf);
}
}
}
}
break;
}
}
returrn 0;
}