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

(一)select函数实现多路并发服务器

(1) select()函数的基础知识

Select()函数:用于监视文件描述符的变化情况——读写或是异常

#include <sys/select.h>				//头文件包含
#include<sys/types.h>				//头文件包含
#include<sys/time.h>				//头文件包含
#include<unistd.h>					//头文件包含
int select (int max_fd + 1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * timeout);
参数功能
max_fd+1指待测试的fd的总个数,它的值是待测试的最大文件描述符加1
fd_set *readset,fd_set用于检查可读性(不需要:NULL)
fd_set *writeset用于检查可写性(不需要:NULL)
fd_set *exceptset用于检查带外数据(不需要:NULL)
const struct timeval * timeout一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。

返回值
大于0:就绪描述字的正数目
等于0:超时
等于-1:出错
拓展
①带外数据:使用与普通数据不同的通道独立传送给用户,是相连的每一对流套接口间一个逻辑上独立的传输通道。(linux系统的套接字机制支持低层协议发送和接受带外数据。但是TCP协议没有真正意义上的带外数据。)

1.1 struct fd_set结构体

struct fd_set:可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄(这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内。)。
①fd_set集合可以通过下面四个宏由人为来操作。
②在Linux内核有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数。(FD_SET有限,select()默认同时处理1024个链接)

fd_set	feset;			//fd_set的数据类型
FD_ZERO(fd_set* fds)			 //清空集合(一如果不初始化,会导致不可预期的后果)
FD_SET(int fd, fd_set* fds)		 //将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds)	 //判断指定描述符是否在集合中
FD_CLR(int fd, fd_set* fds)		 //将给定的描述符从文件中删除

1.2 struct timeeval结构体

struct timeval{
	long tv_sec; // seconds
	long tv_usec; // microseconds
}

(2) select()多路复用实现服务器多路并发

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>

#define ARRAY_SIZE(x)		(sizeof(x)/sizeof(x[0]))  //字节长除于首字节=个数

static inline void msleep(unsigned long ms);
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;
	int		fds_array[1024];  		//select 只能寻1024
	int 	i,k;
	int		conn_fd;
	int		found;
	int		max_fd=0;
	char	buf[1024];
	int		rv;
	fd_set		rdset;		

	//一、命令帮助提示
	//另外:打印命令提示进行函数抽象	
	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.判断输入值是否正确
	//serv_port置-1,还需要if(!...)吗
	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;
	}
	//将数组中所有元素初始化为-1;
	for(i=0;i<ARRAY_SIZE(fds_array);i++)	//ARRAY_SIZE     
	{
		fds_array[i]=-1;
	}
	fds_array[0]=listen_fd;

简析
①命令解析这里就不用再多说了,之前都有提到过。
②宏定义一个类型:#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
我们知道,FD的数据类型是整型。那么现在我想知道传入FD的数目,即用(全部/首位)即可。
③下面这段代码的作用

	for(i=0;i<ARRAY_SIZE(fds_array);i++)	
	{
		fds_array[i]=-1;
	}
	fds_array[0]=listen_fd;

首先,我们知道,这时候还没有进行FD的接收。我们对这个fds_array数组中所有元素进行一个初始化为-1,这样当有FD接收就可以知道了。
当然刚开始的listen_fd也需要被监听。

2.2.2 select()函数实现
	//二、select()
	for( ; ;)
	{	
		FD_ZERO(&rdset);		//清空集合
		//用for循环判断是否有fd进入,没有则继续等待
		for(i=0;i<ARRAY_SIZE(fds_array);i++)
		{
			if(fds_array[i]<0)
			{	
				continue;	//等待
			}
			max_fd=fds_array[i]>max_fd?fds_array[i]:max_fd;		 //listen_fd的个数
			FD_SET(fds_array[i],&rdset);						//将给指定的listen_fd加入集合
		}
		//阻塞ing、等待FD_SET放入的指定FD
		rv=select(max_fd+1,&rdset,NULL,NULL,NULL);	//0超时,-1出错
		if(rv<0)
		{
			printf("select failure:%s\n",strerror(errno));
			break;
		}
		else if(rv==0)
		{
			printf("select get timeout\n");
			continue;
		}
		//rv>0   
		//判断listen_fd是否在fd集合中:是则进行连接,不是则可能已连接
		if(FD_ISSET(listen_fd,&rdset))	//fd在集合中则返回非零值
		{
			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]<0)
					{
						printf("accept new client[%d] and add it into arry\n",conn_fd);
						fds_array[i]=conn_fd;
						found =1;
						break;
					}
				}
				//当fds_array达到上限,则found=0
				if(!found)
				{
					printf("accepting a new client[%d] reach  the ceiling\n",conn_fd);
					close(conn_fd);			
				}
			}
		}
		else //已连客户:1.数据请求 2.中断连接
		{
			 for(i=0;i<ARRAY_SIZE(fds_array);i++)
			 {	
				 //判断是否集合中有FD,判断指定FD是否在集合中
				 if(fds_array[i]<0||!FD_ISSET(fds_array[i],&rdset))
				 {
					 continue;	//结束循环
				 }

				 //IO操作
				 if((rv=read(fds_array[i],buf,sizeof(buf)))<=0)
				 {
					 printf("socket[%d] read failure or get disconnect.\n",fds_array[i]);
					 close(fds_array[i]);
					 fds_array[i]=-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],buf,rv)<0)
					 {
						 printf("socket[%d]write failure :%s\n",fds_array[i],strerror(errno));
						 close(fds_array[i]);
						 fds_array[i]=-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 socket 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;
	}
}
2.2.5 select等待I/O的时间函数
static inline void msleep(unsigned long ms)
{
	struct	timeval tv;
	tv.tv_sec=ms/1000;
	tv.tv_usec=(ms%1000)*1000;
	select(0,NULL,NULL,NULL,&tv);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值