1.poll 的接口介绍
poll 系统调用和 select 类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。poll 的原型如下:
#include <poll.h>
int poll( struct pollfd *fds, nfds_t nfds, int timeout);
/*
poll 系统调用成功返回就绪文件描述符的总数,超时返回 0,失败返回-1
nfds 参数指定被监听事件集合 fds 的大小。
timeout 参数指定 poll 的超时值,单位是毫秒,timeout 为-1 时,poll 调用将永久阻塞,直到某个事件发生,timeout 为 0 时,poll 调用将立即返回。
*/
/*
fds 参数是一个 struct pollfd 结构类型的数组,它指定所有用户感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd 结构体定义如下:
*/
12. struct pollfd
13. {
14. int fd; // 文件描述符
15. short events; // 注册的关注事件类型
16. short revents; // 实际发生的事件类型,由内核填充
17. };
//其中,fd 成员指定文件描述符,events 成员告诉 poll 监听 fd 上的哪些事件类型。它是一系列事件的按位或,revents 成员则有内核修改,通知应用程序 fd 上实际发生了哪些事件。poll 支持的事件类型如下
2.示例代码
服务端示例代码:
#define _GNU_SOURCE
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/poll.h>
#define MAX_FD 1024
#define DATALEN 128
//初始化服务器的socket套接字
int InitSocket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
res = listen(sockfd,5);
assert(res != -1);
return sockfd;
}
//初始化记录服务器套接字的数组
void InitPollFds(struct pollfd fds[],int n)
{
for(int i = 0; i < n; i++)
{
fds[i].fd = -1;
}
}
//将套接字描述符添加到数组中
void AddFdToPollFds(struct pollfd fds[],int n, int fd, short events)
{
for(int i = 0; i < n; i++)
{
if(fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = events;
break;
}
}
}
//接收客户端连接
void GetClientLink(struct pollfd fds[],int n, int sockfd)
{
struct sockaddr_in caddr;
memset(&caddr,0,sizeof(caddr));
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < 0)
{
return;
}
else//接收连接成功,将客户端套接字描述符和我们关心的事件类型添加到fds中
{
printf("cilent %d link sucess\n",c);
AddFdToPollFds(fds,n,c,POLLIN | POLLRDHUP);//关心事件类型为可读数据和客户端关闭
}
}
//处理客户端数据
int DealClientData(int clifd)
{
char data[DATALEN] = {0};
int num = recv(clifd,data,DATALEN-1,0);
if(num <= 0)//读取失败,返回
{
return -1;
}
else
{
printf("%d:%s\n",clifd,data);
send(clifd,"OK",2,0);
}
return 0;
}
//处理poll返回的就绪事件
void DealReadyEvent(struct pollfd fds[],int n,int sockfd)
{
for(int i = 0; i < n; i++)
{
if(fds[i].fd != -1)
{
if(fds[i].revents & POLLRDHUP)//判断客户端是否关闭,客户端关闭,这里也要关闭客户端描述符
{
printf("client %d over\n",fds[i].fd);
close(fds[i].fd);
fds[i].fd = -1;
continue;
}
else if(fds[i].revents & POLLIN)//客户端有可读事件处理
{
if(fds[i].fd == sockfd)//如果是服务器套接字描述符有可读事件处理,就是有客户端连接
{
GetClientLink(fds,n,fds[i].fd);
}
else//客户端套接字描述符有可读事件处理,去读取数据
{
DealClientData(fds[i].fd);
}
}
}
}
}
int main()
{
int sockfd = InitSocket();
assert(sockfd != -1);
struct pollfd fds[MAX_FD];
InitPollFds(fds,MAX_FD);
AddFdToPollFds(fds,MAX_FD,sockfd,POLLIN);
while(1)
{
int n = poll(fds,MAX_FD,5000);//5秒时间
if(n < 0)//poll失败,服务器退出
{
printf("poll error\n");
break;
}
else if(n == 0)//规定时间内没有就绪事件产生
{
printf("time out\n");
continue;
}
else//有就绪事件产生
{
DealReadyEvent(fds,MAX_FD,sockfd);
}
}
exit(0);
}
客户端示例代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
int InitSocket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
return sockfd;
}
int main()
{
int sockfd = InitSocket();
assert(sockfd != -1);
while(1)
{
printf("please input:\n");
char buff[128] = {0};
fgets(buff,127,stdin);
if(strncmp(buff,"end",3) == 0)
{
break;
}
int n = send(sockfd,buff,strlen(buff)-1,0);
if(n < 0)
{
printf("send error\n");
break;
}
memset(buff,0,128);
n = recv(sockfd,buff,127,0);
if(n < 0)
{
printf("recv error\n");
break;
}
printf("%s\n");
}
close(sockfd);
exit(0);
}
运行结果 :
服务端:
客户端1和客户端2: