(一)poll多路复用实现服务器多路并发
(1) poll()函数的基础知识
poll()函数 :和select实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列(不受1024个限制,但是随着个数上升效率会降低)。
#include <poll.h> //头文件包含
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数 | 功能 |
---|---|
struct pollfd *fds | 用来指向一个struct pollfd结构类型的数组 |
nfds_t nfds | 指定数组中监听的元素个数 |
int timeout | 指定等待的毫秒数。(小于0:无限超时。等于0:指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件) |
返回值:
大于0:返回结构体中revents域不为0的文件描述符个数
等于0:在超时前没有任何事件发生
小于0:失败,同时会自动设置全局变量errno:
参数 | 功能 |
---|---|
EBADF | 一个或多个结构体中指定的文件描述符无效。 |
EFAULTfds | 指针指向的地址超出进程的地址空间。 |
EINTR | 请求的事件之前产生一个信号,调用可以重新发起。 |
EINVALnfds | 参数超出PLIMIT_NOFILE值。 |
ENOMEM | 可用内存不足,无法完成请求。 |
1.1 struct pollfd结构类型数组
struct pollfd
{
int fd; //文件描述符
short events; //等待的事件
short revents; //实际发生了的事件
} ;
poll函数可用的测试值:
(2) poll多路复用实现服务器多路并发
2.1 服务器多路并发流程图
2.2 服务器多路并发代码
2.2.1 长选项命令解析&判断命令参数的导入
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.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_server_init(char *listen_ip,int listen_port);
static inline void print_usage(char *progname);
int main(int argc,char **argv)
{
int daemon_run=0;
int serv_port=0;
int listen_fd;
char *progname=NULL;
int opt;
struct pollfd fds_array[1024]; //poll 只能寻1024
int i,k;
int conn_fd;
int found;
int max_fd=0;
char buf[1024];
int rv;
int max;
//一、命令帮助提示
//另外:打印命令提示进行函数抽象
progname=basename(argv[0]); //从arg[0]中截取文件名
//1.opts结构体定义
struct option opts[]=
{
{"daemon",no_argument,NULL,'b'}, //守护进程
{"port",required_argument,NULL,'p'},
{"help",no_argument,NULL,'h'},
{0,0,0,0}
};
//2.命令获取与解析
while((opt=getopt_long(argc,argv,"d:p:h",opts,NULL))!=-1)
{
switch(opt)
{
case'd':
daemon_run=1;
break;
case'p':
serv_port=atoi(optarg);
break;
case'h':
print_usage(progname);
return EXIT_SUCCESS;
//出错
default:
break;
}
}
//3.判断输入值是否正确
if(!serv_port)
{
print_usage(progname);
return -1;
}
//判断是否需要守护进程(即是否要后台运行)
if(daemon_run)
{
daemon(0,0);
}
//判断服务器是否监听端口
if((listen_fd=socket_server_init(NULL,serv_port))<0)
{
printf("ERROR:%s server listen on port %d failure",argv[0],serv_port);
return -2;
}
//把监听到的listen_fd存放在数组中
for(i=0;i<ARRAY_SIZE(fds_array);i++) //ARRAY_SIZE
{
fds_array[i].fd=-1;
}
fds_array[0].fd=listen_fd;
fds_array[0].events=POLLIN;
max =0;
2.2.2 poll()函数实现
//二、poll()
for( ; ;)
{
rv=poll(fds_array,max+1,-1); //数组,元素个数,等待时间
if(rv<0)
{
printf("poll failure:%s\n",strerror(errno));
break;
}
else if(rv==0)
{
printf("poll get timeout\n");
continue;
}
//用revents返回判断listen_fd是否以读。
if(fds_array[0].revents& POLLIN) /
{
if((conn_fd=accept(listen_fd,(struct sockaddr *)NULL,NULL))<0)
{
printf("accept new client failure:%s\n",strerror(errno));
continue;
}
else
{
//判断当前连接客户端数量,是否达到上限
found=0;
for(i=0;i<ARRAY_SIZE(fds_array);i++)
{
if(fds_array[i].fd<0)
{
printf("accept new client[%d] and add it into arry\n",conn_fd);
fds_array[i].fd=conn_fd;
found =1;
break;
}
}
if(!found)
{
printf("accepting a new client[%d] reach the ceiling\n",conn_fd);
close(conn_fd);
}
max=i>max?i:max;
if(--rv<=0)
{
continue;
}
}
}
else //已连客户:1.数据请求 2.中断连接
{
for(i=0;i<ARRAY_SIZE(fds_array);i++)
{
//判断是否有已连接的conn_fd,判断这个conn_fd所对应events是否可读输入。
if(fds_array[i].fd<0||!fds_array[i].events&POLLIN)
{
continue; //结束循环
}
//IO操作
if((rv=read(fds_array[i].fd,buf,sizeof(buf)))<=0)
{
printf("socket[%d] read failure or get disconnect.\n",fds_array[i]);
close(fds_array[i].fd);
fds_array[i].fd=-1;
}
else
{
printf("socket[%d] read get %d bytes data \n",fds_array[i],rv);
//字符:小转大字母
for(k=0;k<rv;k++)
{
buf[k]=toupper(buf[k]);
}
if(write(fds_array[i].fd,buf,rv)<0)
{
printf("socket[%d]write failure :%s\n",fds_array[i],strerror(errno));
close(fds_array[i].fd);
fds_array[i].fd=-1;
}
}
}
}
}
CleanUp:
close(listen_fd);
return 0;
}
2.2.3 命令提示打印函数
static inline void print_usage(char *progname) //了解static inlin 意义
{
printf("Usage:%s [OPTION]...\n",progname);
printf("%s is a socket server program\n",progname);
printf("Mandatory arguments:long or short options\n");
printf("-b[daemon] set program running on background\n");
printf("-p[port] socket server port address\n");
printf("-h[help] display this help information\n");
printf("\nExample: %s -b -p 1111 \n",progname);
return ;
}
2.2.4 server四步骤
//server四步
int socket_server_init(char *listen_ip,int listen_port)
{
int on=1;
int rv=0;
int listen_fd;
struct sockaddr_in servaddr;
//1.socket
if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("socket () create a TCP socket failure:%s\n",strerror(errno));
return -1;
}
//端口重用
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//初始化操作
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(listen_port);
//判断IP地址,并初始化。意义:这里if else相当于两用。监听特定IP,或监听所有IP。
if(!listen_ip)
{
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
printf("listen other IP address [%d]",listen_ip);
}
else
{
if(inet_pton(AF_INET,listen_ip,&servaddr.sin_addr)<=0)
{
printf("inet_pton() set listen IP address failure.\n");
rv=-2;
goto CleanUp;
}
}
//2.bind
if(bind(listen_fd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
printf("bind() bind the TCP socket failure: %s\n",strerror(errno));
rv=-3;
goto CleanUp;
}
//3.listen
if(listen(listen_fd,13)<0)
{
printf("bind() bind the TCP socket failure: %s\n",strerror(errno));
rv=-4;
goto CleanUp;
}
CleanUp:
if(rv<0)
{
close(listen_fd);
}
else
{
rv=listen_fd;
return rv;
}
}