IO通信(多路复用)--多路io转接服务器设计思路--select函数的实现

1、例如:

        刚开一家公司,一个老板和四个员工,老板管理员工。假如员工要请假,领取工资等之类的事情,是直接找老板请假,拿工资。随着时间的发展,公司的规模不断地扩大,员工的人数增加到几百号人甚至是几千号人,这时候老板管理员工是不是很麻烦,手忙脚乱,指不定哪天炸裂掉。

这时候呢,要解决这样繁琐的事情,老板要雇佣一个秘书小姐,以后的员工管理琐事交给秘书,老板只负责管理自己的秘书,这样老板就很轻松,才能感受到老板的位置真爽。

来一份简单的草图:

老板就相当于sever ,秘书小姐就相当于select,员工就相当于客户端。

以前的sever是在accept等待客户端主动连接服务器,假如客户端一直不理服务器,那么服务器就一直在那么等待(阻),那么sever(老板)肯定是吃饱撑着。现在呢,有了一个select(秘书小姐),那就将这些等待的事情交给select(秘书小姐)来处理,而sever(老板)呢,该度假度假,该陪媳妇陪媳妇,该干嘛干嘛,这样就减轻了工作量。重点是这个select(秘书小姐)也不傻,也不是干等着客户端,直接留给客户端(员工)留个电话号码,客户端有事情呢,直接打电话(标志位)给select,那么这个select收到(标志位)就来搞一个 accept 给这个客户端处理事情,这样的效率比会更高。但是重大的工程最终还是要sever老板来处理,不要认为select来取代server的位置。小秘书这个标志位也是老板创建的,小秘书拿着这个(lfd)来监听。监听到客户端,秘书就在accept返回cfd,用来和客户端通信监听,那么这个秘书就有两个监听,是否有客服连接,连接的客户是否来进行数据交流。一旦lfd监听到有客户端,select就会报告给sever,sever就会accepot个cfd,与客户端交流。

2、select函数的参数简介

 /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);

头文件不说了,大家都懂。

 int select(int nfds, fd_set *readfds, fd_set *writefds,
               fd_set *exceptfds
, struct timeval *timeout);

int nfds: 监听到的所有(客户端)文件描述符当中的最大文件描述符+1。原理是在select内的传进来的参数要做循环,循环的参数是做for循环的上限,所以要+1;例如循环10次,循环的上限就是11,这就是它为什么要+1的原因。注意:select这个函数的上限是1024个文件描述符,因此在监听的时候,最多监听1024个文件描述符,这个该函数的弊端,不可更改。

fd_set *readfds, fd_set *writefds,fd_set *exceptfds:这三个参数都是传入传出参数,都是文件描述符指针类型,文件描述符指针集合。

readfds:select监视的可读文件句柄集合。

writefds: select监视的可写文件句柄集合。

exceptfds:select监视的异常文件句柄集合。

对于传入传出参数,以第一个参数为例,草图如下:

 假如,传入 lfd,fd1, fd2, fd3,4个文件描述符到reade集合中,经过判断,满足条件的只有2个,lfd和fd3,实际传入来的个数是2,这就是双向参数的特点。

struct timeval *timeout:本次select()的超时结束时间。超时时长;对应的结构体如下:

一个是秒,一个是微秒。

详细如下图:

大概可分为如下步骤,有些参数暂时不加,下面是举例子,方便理解:

1、首先建立集合,fd_set xxx;

2、将新建的集合清空;FD_AERO(&xxx);

3、将客户端时间加入到集合中:FD_SET(fd1,&xxx);

4、启动select函数,返回实际捕捉到满足条件的客户句柄总数

5、然后再进行遍历,找出满足时间的句柄;例如:

for(int i ;i<nfds;i++){

        if(FD_ISSET(fd,&xxx)){

...........

}

}

注意:每当一个客户端连接,它都要遍历一次,这样就显得很麻烦,因此,就需要自己建建立一个数组,将满足条件的fd进行存储。

另外一个是:监听集合和满足条件的监听集合是统一个集合,但每次有新的fd来的时候监听,集合都要对其进行初始化,因此要将该集合进行备份。

3、select函数实现:

服务器端:

#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 <unistd.h>
#include <pthread.h>
#include <pthread.h>
#include <stdlib.h>


#include "common.h"

struct thread_msg {
	int cli_socket;
	pthread_t tid;
	
};


/*

       #include <sys/select.h>
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

	原理: 把很多socket加入到一个集合set中,使用select函数监控(阻塞式)这个集合
			当集合中任何一个socket送来数据,select立马返回,找出这个scoket,处理它
			回去继续监控

		fds: fd set,文件描述符的
			一个文件fd,大概会有三种事件  可读--文件突然变得可读    可写--文件突然变得可写
										异常事件--文件突然出错

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds,       struct timeval *timeout);

			nfds:  是所有文件描述符中数值,最大的哪一个+1
			readfds:      它们都是双向参数
						传参的时候,你把所有需要监控的fd统统放入集合
						当select发现某几个socket有事件,则会 清空该集合
						然后只吧发生事件的fd存入集合中.
			
			writefds:
			exceptfds:
			
			struct timeval *timeout: 超时时间,如果select在指定的时间内都没有等来客户端数据,则放弃
					struct timeval {
					   long    tv_sec;         seconds  
					   long    tv_usec;         microseconds  
				   };
					注意,他也是双向参数, 每次传递前都要赋值
			返回值:
				正数:发生事件的fd的个数
				0: 超时了
				-1: errno
				
				
		四个辅助的函数: 用于  将fd添加/删除 到某个集合中
       void FD_CLR(int fd, fd_set *set);      将fd从集合中删除
       int  FD_ISSET(int fd, fd_set *set);    判断fd是否在集合中
       void FD_SET(int fd, fd_set *set);      将fd加入到该集合
       void FD_ZERO(fd_set *set);				清空集合
*/


fd_set readfds,readfds_backup;
int max_fd;
struct timeval timeout;


int cli_socket_arr[24];
int cli_socket_pos = 0;


void do_serv_for_client(int socket)
{
	int ret;

	struct msg_struct serv_msg;

	memset(&serv_msg,0,sizeof(serv_msg));
	ret = recv(socket,&serv_msg,sizeof(serv_msg),0);
	if(ret<0){
		perror("recv err"); close(socket);   return  ;
	}else if(ret == 0){
		printf("serv found peer shutdwon\n"); close(socket);   
		
		return  ;
	}
	
	printf("serv got msg:temp%d %d %d cnt%d des:%s\n",\
		serv_msg.temp,serv_msg.humidity,serv_msg.wind,\
			serv_msg.sendcnt,serv_msg.des);		
	serv_msg.serv_response++;

	ret = send(socket,&serv_msg,sizeof(serv_msg),0);
	if(ret<0){
		perror("send err");  return  ;
	}
 
	return ;
}


int main()
{
	/*清空读集合*/
	FD_ZERO(&readfds);

	int socket_main = socket(AF_INET,SOCK_STREAM, 0 );
	if(socket_main <0){
		perror("create main socket err");
		return -3;
	}

	struct sockaddr_in  serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(10086);   
	serv_addr.sin_addr.s_addr = INADDR_ANY;      //inet_addr("192.168.3.197"); 
	int ret =  bind(socket_main,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	if(ret<0){
		perror("bind err");
		return -34;
	}
	
	ret = listen(socket_main,5);
	if(ret<0){
		perror("listen err");
		return -3;
	}
	
	struct sockaddr_in cli_addr;
	socklen_t addrlen=sizeof(cli_addr);
	int socket_new;
 

	/*
		将第一个fd加入到读集合中
	*/
	FD_SET(socket_main,&readfds);
	max_fd = socket_main ;

loop:	
	readfds_backup = readfds;   //因为select会修改传参,所以我们提前准备备胎
	timeout.tv_sec = 5;
	timeout.tv_usec=0;
	ret = select(max_fd + 1,&readfds_backup,NULL,NULL, &timeout);
	if(ret<0){
		perror("select err");
		return -3;
	}else if(ret == 0 ){
		printf("found select timeout\n");
		void do_something(void);
		goto loop; 
	}
	//readfds_backup是一个黑盒子,里面存放了发生事件的fd 
	//我们没有办法,只能拿着 所有的socket,依次去判断是否存在fd中
	if( FD_ISSET(socket_main,&readfds_backup)  ){
			//第一步判断 主socket是否在集合,如果在 接听
			socket_new = accept(socket_main ,(struct sockaddr *)&cli_addr, &addrlen  );
			if(socket_new<0){
				perror("accept err");
				return -3;
			}
			printf("serv accept client ip=%s port=%d\n",\
					inet_ntoa (cli_addr.sin_addr  ),   ntohs(cli_addr.sin_port)  );
					
			FD_SET(socket_new,&readfds);
			max_fd = max_fd < socket_new ? socket_new : max_fd;
			//记录这次的新socket,方便以后 遍历集合
			cli_socket_arr[ cli_socket_pos ] = socket_new;
			cli_socket_pos++;
	}
 
	for(int i=0;i< cli_socket_pos;i++){
		int socket_cli = cli_socket_arr[i];

		if(  FD_ISSET( socket_cli,&readfds_backup)   ){
				do_serv_for_client(socket_cli);	
		}
	}
	
	goto loop;
	

	close(socket_main);  //一般最后才能关闭
	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 <unistd.h>

#include "common.h"

int main()
{
	int socket_cli = socket(AF_INET,SOCK_STREAM, 0 );
	if(socket_cli <0){
		perror("create client socket err");
		return -3;
	}
	
	/*
	
		//int connect(int socket_cli, serv_ip,serv_port);
	       #include <sys/types.h>          
       #include <sys/socket.h>

         int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

				struct sockaddr_in {
				   sa_family_t    sin_family; 	永远等于AF_INET.   address family: AF_INET 
				   in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
				   struct in_addr sin_addr;     internet address  
			   };

			   Internet address.  
			   struct in_addr {
				   uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
			   };
			成功返回 0
			失败 -1,errno 
			

	*/
	struct sockaddr_in  serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(10086); 
	serv_addr.sin_addr.s_addr = inet_addr("192.168.3.197"); 
	int ret = connect(socket_cli,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	if(ret <0){
		perror("connect err");
		return -3;
	} 
	

	struct msg_struct cli_msg;
	
	while(1){
		cli_msg.temp=45;
		cli_msg.humidity=40;
		cli_msg.wind=10;
		strcpy(cli_msg.des,"today sun day");
		
		ret = send(socket_cli,&cli_msg,sizeof(cli_msg),0);
		if(ret<0){
			perror("send err");
			return -34;
		}


		memset(&cli_msg,0,sizeof(cli_msg));
		ret = recv(socket_cli,&cli_msg,sizeof(cli_msg),0);
		if(ret<0){
			perror("recv err");
			return -34;
		}else if(ret ==0){
			printf("cli found serv shutdown\n");
			return 0;
		}
		
		printf("cli got msg:response=%d\n",cli_msg.serv_response);
		 
		sleep(1);
	}
}

自定义头文件端:

#ifndef COMM_HH
#define COMM_HH


struct msg_struct {
	char temp;
	int humidity;
	int wind;
	
	int sendcnt;
	int serv_response;
	char des[32];
	

};

#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值