一、在谈epoll和poll网络编程代码之前,首先简单的介绍一下poll函数和epoll函数组
1、poll函数原型
使用此函数的头文件为
#include<poll.h>
poll函数原型如下:
#include<poll.h>
int poll(struct poll_fd *fds, nfds_t nfds, int timeout)
参数解释
(1)fds:指向元素类型为struct poll_fd类型的首元素
(2)nfds:fds结构体数组的大小
(3)timeout:表示poll函数超时的时间限制
poll_fd结构
struct poll_fd
{
int events;
int revents;
int fd;
}
变量说明
(1)events:告诉内核需要检测的事件(此为用户设置)
(2)revents:对文件描述符进行操作结果事件,在内核中进行操作(内核返回时的完成时间)
(3)fd:每个结构体中都会有一个被监视的文件描述符,内核可以处理多个结构体,就说明可以监测多个文件描述符
以下为events和revents的取值
2、poll函数实现的原理
内核将用户的fds结构体数组拷贝到内核中,当有时间发生时内核再将所有时间都返回到fds数组中,
polll函数只返回已就绪时间的个数,所以用户要操作就绪事件,就得用轮询的方法
二、poll网络编程实例
注意:poll函数是阻塞函数,当没有就绪文件描述符的时候,poll一直处于阻塞状态,知道有就绪文件描述符
程序代码(包含注释)
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#define NFDS 100//fds数组的大小
// 创建一个用于监听的socket
int CreateSocket()
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(-1 != listenfd);
struct sockaddr_in ser;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(listenfd, (struct sockaddr*)&ser, sizeof(ser));
assert(-1 != res);
listen(listenfd, 5);
return listenfd;
}
// 初始化fds结构体数组
void InitFds(struct pollfd *fds)
{
int i = 0;
for(; i < NFDS; ++i)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
}
// 向fds结构体数组中插入一个文件描述符
void InsertFd(struct pollfd *fds, int fd, int flag)//此处flag是为了判断是文件描述符c,还是listenfd,来设置events
{
int i = 0;
for(; i < NFDS; ++i)
{
if(fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events |= POLLIN;
if(flag)
{
fds[i].events |= POLLRDHUP;
}
break;
}
}
}
// 从fds结构体数组中删除一个文件描述符
void DeleteFd(struct pollfd *fds, int fd)
{
int i = 0;
for(; i < NFDS; ++i)
{
if(fds[i].fd == fd)
{
fds[i].fd = -1;
fds[i].events = 0;
break;
}
}
}
// 获取一个已完成三次握手的连接
void GetClientLink(int fd, struct pollfd *fds)
{
struct sockaddr_in cli;
socklen_t len = sizeof(cli);
int c = accept(fd, (struct sockaddr*)&cli, &len);
assert(c != -1);
printf("one client link success\n");
InsertFd(fds, c, 1);
}
// 断开一个用户连接
void UnlinkClient(int fd, struct pollfd *fds)
{
close(fd);
DeleteFd(fds, fd);
printf("one client unlink\n");
}
// 处理客户端发送来的数据
void DealClientData(int fd, struct pollfd *fds)
{
char buff[128] = {0};
int n = recv(fd, buff, 127, 0);
if(n <= 0)
{
UnlinkClient(fd, fds);
return;
}
printf("%s\n", buff);
send(fd, "ok", 2, 0);
}
// poll返回后,处理就绪的文件描述符
void DealFinishFd(struct pollfd *fds, int listenfd)
{
int i = 0;
for(; i < NFDS; ++i)
{
if(fds[i].fd == -1)
{
continue;
}
int fd = fds[i].fd;
if(fd == listenfd && fds[i].revents & POLLIN)
{
GetClientLink(fd, fds);
//获取连接
}
else if(fds[i].revents & POLLRDHUP)
{
UnlinkClient(fd, fds);
//断开连接
}
else if(fds[i].revents & POLLIN)
{
DealClientData(fd, fds);
//处理客户端数据
}
}
}
int main()
{
int listenfd = CreateSocket();
struct pollfd *fds = (struct pollfd*)malloc(sizeof(struct pollfd) * NFDS);
//malloc一个fds结构体数组
assert(NULL != fds);
InitFds(fds);
//初始化fds结构体数组
InsertFd(fds, listenfd, 0);
//插入文件描述符listenfd
while(1)
{
int n = poll(fds, NFDS, -1);
if(n <= 0)
{
printf("poll error\n");
continue;
}
DealFinishFd(fds, listenfd);
//处理就绪的文件描述符
}
free(fds);
}
以上就是poll简单网络编程的代码,当然,如果要测试此代码的话,得写一个客户端,如果读者有兴趣的话,
可以自己写一个客户端测试一下。
三、poll的优缺点
1、优点
(1)poll() 不要求开发者计算最大文件描述符加一的大小。
(2)poll() 在应付大数目的文件描述符的时候速度更快,相比于select。
(3)它没有最大连接数的限制,原因是它是基于链表来存储的。
(4)在调用函数时,只需要对参数进行一次设置就好了
2、缺点
(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义(epoll可以解决此问题)
(2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符,这样会使性能下降
(3)同时连接的大量客户端在一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降