我发现,当天复习的知识点,当天写博客印象会很深刻,我昨天复习了poll模型的服务器,打算学了epoll之后再来写poll的博客,可是正在梳理poll的时候,却回想了很久才梳理通顺,写博客还是要趁热打铁,每天都回顾一下自己以前写的一些博客,能让知识点更加牢靠。
poll简介
poll模型与select模型相差其实并不大,代码只用改一点就可以了,他们的区别在于,poll突破了select对文件描述符的限制,但是对于poll来说,当客户端连接数过大时,还是会导致其性能大打折扣,因为,select与poll在接收到客户端的连接申请后,后会将其存储在一个数组中,对于poll,这个数组是一个结构体数组,这也是他不同于select的其中一点,但是,当有客户端准备就绪时,他们都需要遍历整个数组(对于select是遍历fd_set),无论时间是否发生。这也是他们再后来慢慢被淘汰的原因;
poll函数
#include <poll.h>
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd *fds : poll函数的第一个参数,是一个结构体数组,他的成员如代码所示,需要注意的是,通select服务器中用来存储文件描述符的数组一样,在使用该结构体数组前,我们同样需要将结构体里的 fd 成员全部置为 -1;这样就便是这个房间是空的,可以用来存储描述符了;第二个参数是我们等待文件发生的事件,revents是实际发生的事件,他们可以有下面这些取值:
常量 | 说明 | 是否能作为 events 的输入 | 是否能作为revents的返回结果 |
---|---|---|---|
POLLIN | 普通或者优先级带数据可读 | 能 | 能 |
POLLRDNORM | 普通数据可读 | 能 | 能 |
POLLRDBAND | 优先级带数据可读 | 能 | 能 |
POLLPRI | 高优先级数据可读 | 能 | 能 |
POLLOUT | 普通数据可写 | 能 | 能 |
POLLWRNORM | 普通数据可写 | 能 | 能 |
POLLWRBAND | 优先级带数据可写 | 能 | 能 |
POLLERR | 发生错误 | 能 | 能 |
POLLHUP | 发生挂起 | 能 | |
POLLNVAL | 描述字不是一个打开的文件 | 能 |
POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于
POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
nfds_t nfds :指定数组中监听的文件个数。
第三个参数:
timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
EBADF 一个或多个结构体中指定的文件描述符无效。
EFAULTfds 指针指向的地址超出进程的地址空间。
EINTR 请求的事件之前产生一个信号,调用可以重新发起。
EINVALnfds 参数超出PLIMIT_NOFILE值。
ENOMEM 可用内存不足,无法完成请求。
poll服务器
/*********************************************************************************
* Copyright: (C) 2020 Xiao yang IoT System Studio
* All rights reserved.
*
* Filename: poll_server.c
* Description: This file server of poll
*
* Version: 1.0.0(03/10/2020)
* Author: Lu Xiaoyang <[email protected]>
* ChangeLog: 1, Release initial version on "03/10/2020 06:30:39 PM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <pthread.h>
#include <getopt.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
int socket_Be_ready(char *listen_ip,int listen_port); //将socket(),bind(),listen() 打包
void print_usage(char *progname) //执行程序的帮助信息
{
printf("progname usage:\n");
printf("-p(--port) input the port\n");
printf("-d(--daemon) progranm will running at background\n");
printf("-h(--help) get help massage\n");
return;
}
int main(int argc, char *argv[])
{
int clifd,listenfd;
char *listen_ip;
char *progname;
char buf[1024];
int i,j;
int max = 0;
int listen_port;
int rv;
int found = 0;
int ch;
int daemon_run = 0; //后台运行
struct pollfd fds_array[1024]; //存储感兴趣的描述符以及他们待发生的事件
struct option opts[] = {
{"port",required_argument,NULL,'p'},
{"daemon",no_argument,NULL,'d'},
{"help",no_argument,NULL,'h'},
{NULL,0,NULL,0}
}; //注册命令行命令,当用户执行命令时,可从命令行获取参数
progname = basename(argv[0]);
while((ch = getopt_long(argc,argv,"p:dh",opts,NULL)) != -1)
{
switch(ch)
{
case 'p':
listen_port = atoi(optarg);
break;
case 'd':
daemon_run = 1;
break;
case 'h':
print_usage(progname);
break;
default:
break;
}
} // 获取命令行参数
if(!listen_port)
{
print_usage(progname);
printf("Get no argument,Please try again\n");
return -1;
}
if((listenfd = socket_Be_ready(NULL,listen_port)) < 0)
{
printf("Create server failure:%s\n",strerror(errno));
return -2;
}
printf("Create server ok\n");
if(daemon_run)
{
daemon(0,0); //将程序调到后台(background)运行
}
for(i = 0;i < ARRAY_SIZE(fds_array);i++) //遍历结构体数组,将每个结构体的成员 fd 设置为 -1,即这个位置时空位
{
fds_array[i].fd = -1;
}
fds_array[0].fd = listenfd; //将监听文件描述符存入数组
fds_array[0].events = POLLIN; //设置可读
max = 0;
for( ; ; )
{
printf("Waiting client connect or massage come......\n");
rv = poll(fds_array,max+1,-1); //poll将会阻塞在此,直到数组中有一个或多个描述符准备就绪,并返回该值
if(rv < 0) //poll 失败
{
perror("poll failure");
close(listenfd);
return -3;
}
else if(0 == rv)
{
perror("Time out");
continue;
}
if(fds_array[0].revents & POLLIN) //如果数组的第一个文件描述符(listenfd)的返回事件是 POLLIN,则说明有新客户端请求连接
{
if((clifd = accept(listenfd,(struct sockaddr *)NULL,NULL)) < 0) //接受客户端,返回一个文件描述符
{
perror("accept failure");
continue;
}
for(i = 1;i < ARRAY_SIZE(fds_array);i++) //找出空房间,让客人入住
{
if(fds_array[i].fd < 0)
{
fds_array[i].fd = clifd;
fds_array[i].events = POLLIN; //设置等待事件
printf("Put new client[%d] into fds_array[%d].fd\n",clifd1,i);
found = 1;
break;
}
}
if(!found)
{
printf("Put clifd into array failure:FULL\n");
close(clifd);
continue;
}
max = i>max ? i : max; //找到最大值
if (--rv <= 0)
continue;
}
else //已连接的客户端发送数据可读
{
for(i = 1;i < ARRAY_SIZE(fds_array);i++) //遍历整个数组,找到调皮的客户端收拾
{
if(fds_array[i].fd < 0 || !(fds_array[i].revents & POLLIN)) //两个条件
continue;
if((rv = read(fds_array[i].fd,buf,sizeof(buf))) <= 0)
{
printf("Read client[%d] failure\n",fds_array[i].fd);
fds_array[i].fd = -1;
break;
}
printf("Read %d bytes data from client[%d]:%s\n",rv,fds_array[i].fd,buf);
for(j = 0;j < rv;j++)
{
buf[j] = toupper(buf[j]);
}
if((rv = write(fds_array[i].fd,buf,sizeof(buf))) <= 0)
{
printf("Write to client[%d] failure:%s\n",fds_array[i].fd,strerror(errno));
close(fds_array[i].fd);
fds_array[i].fd = -1;
break;
}
close(fds_array[i].fd);
printf("client[%d] exit\n",fds_array[i].fd);
fds_array[i].fd = -1;
}
}
}
return 0;
}
int socket_Be_ready(char *listen_ip,int listen_port)
{
int on = 1;
int listenfd = -1;
int rv;
struct sockaddr_in servaddr;
socklen_t addrlen = sizeof(servaddr);
if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
perror("Socket failure");
return -1;
}
bzero(&servaddr,addrlen);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(listen_port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(bind(listenfd,(struct sockaddr *)&servaddr,addrlen) < 0)
{
perror("Bind on port failure");
close(listenfd);
return -1;
}
listen(listenfd,13);
return listenfd;
}
代码已经描述的非常清楚了,如果想知道多个客户端具体的工作流程,可以先了解select模型服务器的工作原理,
参考:select服务器精讲;吃饭;