Linux&Apue(0.4.1):poll多路复用实现服务器多路并发

(一)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;
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值