探索 poll 的威力
1.poll函数原型
以下是 poll()
函数的原型:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中:
struct pollfd
是一个结构体,用于表示要监视的文件描述符及其所关注的事件。poll()
函数使用fds
参数传递一个指向struct pollfd
数组的指针,该数组中的每个元素表示一个文件描述符及其关注的事件。nfds
表示fds
数组中元素的数量,即要监视的文件描述符的数量。timeout
表示等待事件发生的超时时间。它可以有以下几种取值:< 0
:表示无限期等待,直到有事件发生。0
:表示立即返回,非阻塞模式,只检查一次文件描述符的状态。> 0
:表示等待指定的毫秒数后返回,无论是否有事件发生。
poll()
函数返回一个非负整数,表示有多少个文件描述符满足了指定的事件。此时可以通过遍历 struct pollfd
数组来确定哪些文件描述符触发了事件。
需要注意的是,在调用 poll()
函数之前,必须正确设置 struct pollfd
数组中每个元素的 fd
字段(要监视的文件描述符)和 events
字段(关注的事件)。函数执行完毕后,revents
字段将被填充,用于指示实际发生的事件。
struct pollfd
是一个结构体,用于描述poll()
函数中要监视的文件描述符及其关注的事件。
pollfd结构体
这是 struct pollfd
的定义:
struct pollfd {
int fd; // 要监视的文件描述符
short events; // 关注的事件(用位掩码表示)
short revents; // 实际发生的事件(用位掩码表示)
};
struct pollfd
包含了以下字段:
fd
:表示要监视的文件描述符。将会检查该文件描述符上是否发生了要关注的事件。events
:表示关注的事件。它是一个位掩码(bitmask),可以使用以下几个常量进行设置:POLLIN
:有数据可读(包括普通数据、TCP连接关闭或者可读的文件描述符)。POLLOUT
:数据可写入(仅适用于TCP/Unix域流连接)。POLLERR
:产生错误。POLLHUP
:挂起状态。POLLNVAL
:无效请求:文件描述符未打开。
revents
:在调用poll()
函数后,由系统填充,用于指示实际发生的事件。它也是一个位掩码,包含以下几个可能的常量:POLLIN
:文件描述符上有数据可读。POLLOUT
:文件描述符可写。POLLERR
:发生错误。POLLHUP
:关闭的连接或挂起状态。POLLNVAL
:无效请求。
在使用 poll()
函数之前,需要正确设置 struct pollfd
数组中的每个元素的 fd
和 events
字段。然后在调用 poll()
函数之后,通过检查 revents
字段来确定发生了哪些事件。
请注意,具体的平台可能对支持的事件类型有所不同,因此在使用时请参考相关的文档和手册。
二.poll特点
Linux 的 poll
是一种多路复用的系统调用,用于监视多个文件描述符上的事件。它具有以下特点:
高效的事件驱动机制:
poll
可以同时监视多个文件描述符,当其中一个文件描述符就绪(可读、可写或出错)时,poll
会返回该事件,从而实现了事件驱动的编程模型。跨平台支持:
poll
是一个通用的系统调用,在大多数类Unix系统(如Linux、FreeBSD等)上都得到支持,因此可以方便地编写可移植的代码。但是相对于select
较差,poll
不支持Windows
,select
支持。无最大限制:
poll
没有对监视的文件描述符数量进行限制,可以同时监视大量的文件描述符,不受系统资源的限制。没有文件描述符数量限制:与
select
不同,poll
函数没有对文件描述符进行数量限制,因此可以同时监视大量的文件描述符。不用复制监听的文件描述符:与
select
类似,poll
不会复制文件描述符集合,也不会影响文件描述符的数量。这使得poll
在使用过程中更加灵活。
需要注意的是,poll
也有一些缺点,比如效率相对较低,无法处理大量的并发连接。在需要处理大量并发连接的场景下,更好的选择是使用 epoll
或者其他高性能的事件驱动框架。
三.poll特点
poll
是一个多路复用的系统调用,用于监视多个文件描述符上的事件。它具有以下特点:
高效的事件驱动机制:
poll
可以同时监视多个文件描述符,当其中一个文件描述符就绪时(可读、可写或异常),poll
会返回该事件,从而实现了基于事件驱动的编程模型。高效的轮询模型:与
select
不同,poll
使用了一个包含所有监视文件描述符的数组,不再需要重新构造文件描述符集合。这样可以避免select
中所谓的"位图"传递导致的性能损失。没有文件描述符数量限制:与
select
不同,poll
函数没有对文件描述符数量进行限制,因此可以同时监视大量的文件描述符。没有文件描述符复制:与
select
类似,poll
不会复制文件描述符集合,也不会影响文件描述符的数量。这使得poll
在使用过程中更加灵活。
需要注意的是,poll
也有一些限制,例如效率相对较低,无法处理大量的并发连接。在需要处理大量并发连接的场景下,更好的选择是使用更高效的事件驱动框架,例如 epoll
。
四.poll编程步骤
使用 poll
进行编程的一般步骤如下:
- 创建并初始化
struct pollfd
数组:对于要监视的每个文件描述符,需要创建一个对应的struct pollfd
结构体,并设置相关的参数。
#include <poll.h>
struct pollfd fds[2];
fds[0].fd = fd1;
fds[0].events = POLLIN; // 监视可读事件
fds[1].fd = fd2;
fds[1].events = POLLOUT; // 监视可写事件
这里示例创建了一个包含两个文件描述符的数组 fds
,并设置了要监视的事件类型。
- 调用
poll
函数进行多路复用:
int ret = poll(fds, 2, timeout_ms);
poll
函数的第一个参数是指向 struct pollfd
数组的指针,第二个参数是数组中的元素数量,第三个参数是超时时间(以毫秒为单位)。poll
函数会在超时时间内阻塞,直到有一个或多个文件描述符就绪或超时。
- 检查
poll
的返回值和事件:
if (ret > 0) {
// 有事件就绪
for (int i = 0; i < 2; i++) {
if (fds[i].revents & POLLIN) {
// 可读事件就绪
// 处理可读事件
}
if (fds[i].revents & POLLOUT) {
// 可写事件就绪
// 处理可写事件
}
// 其他事件类型类似
}
} else if (ret == 0) {
// 超时
} else {
// 发生错误
}
poll
函数返回大于 0 表示有事件就绪的文件描述符数量,等于 0 表示超时,小于 0 表示错误。通过检查 revents
字段可以确定哪些事件就绪,可以根据需要处理相应的事件。
需要注意的是,以上只是 poll
的一般编程步骤,具体的实现可能会根据实际需求有所变化。同时,为了编写更健壮的代码,应该对返回的异常情况进行适当处理,例如处理 poll
错误或文件描述符的异常断开。
五.编程实战
利用poll实现一个高并发服务器,当服务连接成功后,客户端发送小写字母的字符串,服务器端发送其大写形式。
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <poll.h>
#include <sys/time.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//1.socket创建套接字
int socked = socket(AF_INET, SOCK_STREAM, 0);
if (socked < 0)
{
perror("socket is err");
return -1;
}
//2.bind绑定服务器ip地址和端口号
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int len = sizeof(caddr);
int ret = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
if (ret < 0)
{
perror("bind is err");
return -1;
}
//3.listen设置同时最大链接数
ret = listen(socked, 5);
if (ret < 0)
{
perror("listen is err");
return -1;
}
//4.poll前置工作
struct pollfd fds[2048]; //创建结构体数组
fds[0].fd = socked; //初始化监听的文件描述符
fds[0].events = POLLIN;
//5.poll进行相关逻辑操作
int count,size;
int count_fd = 1; //监听个数
int accepted;
char buf[1024] = {0};
while (1)
{
count = poll(fds, count_fd, -1);
if (count < 0)
{
perror("poll is err");
break;
}
for (int i = 0; i < count_fd; ++i)
{
if (fds[i].revents == POLLIN)
{
if (fds[i].fd == socked)
{
accepted = accept(socked, (struct sockaddr *)(&caddr), &len);
printf("port:%d ip: %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
fds[count_fd].fd = accepted;
fds[count_fd++].events = POLLIN;
if (--count == 0)
break;
}
else
{
int flage = recv(fds[i].fd, buf, sizeof(buf), 0);
if (flage < 0)
{
perror("recv is err");
}
else if (flage == 0)
{
printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
close(fds[i].fd);
fds[i] = fds[--count_fd]; //交换
--i; //交换后的i没有验证
}
else
{
size = strlen(buf);
for (int i = 0; i < size; ++i)
{
if (buf[i] >= 'a' && buf[i] <= 'z')
buf[i] = buf[i] + ('A' - 'a');
else
buf[i] = buf[i] + ('a' - 'A');
}
printf("%s\n", buf);
send(fds[i].fd, buf, sizeof(buf), 0);
}
if (--count == 0)
break;
}
}
}
}
close(socked);
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//1.socket建立文件描述符
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror("socket is err");
}
//2.connect连接服务器
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
if (flage < 0)
{
perror("connect is err");
}
//3.服务器端不断发送数据,接受服务器转化后的数据
char buf[1024] = {0};
while (1)
{
//memset(buf,0,sizeof(buf));
fgets(buf, sizeof(buf), stdin);
if (strncmp(buf,"quit#",5)==0)
{
break;
}
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
send(fd, buf, sizeof(buf), 0);
flage = recv(fd, buf, sizeof(buf), 0);
if (flage < 0)
{
perror("recv is err");
}
else
{
fprintf(stdout, "%s\n", buf);
}
}
close(fd);
return 0;
}
- 点赞
- 收藏
- 关注作者
评论(0)