Linux网络编程5-多路Iselect函数

一. 多路I/O转接服务器

多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。
在这里插入图片描述
如上图的select函数,假设c1客服端想连接服务器(老板)端,会先找select函数中的监听套接字,然后select函数告诉服务器有客户端连接 ,服务器调用accept函数连接,并返回cfd1套接字给select函数继续监听,如果c1有数据写入,select函数会通知服务器端再调用read函数。常用的多路函数有select

二. select函数

1.select函数简介

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
	   参数:
		(1)nfds:监听的所有文件描述符中,最大文件描述符+12)readfds: 读(事件 )文件描述符监听集合。传入、传出参数
		    传入的是你监听的  传出的是实际发送数据的 
		    注意:客户端向服务器端写数据 意思就是服务端要做读事件。
		(3)writefds:写事件 文件描述符监听集合。	传入、传出参数 NULL大部分用
		(4)exceptfds:异常事件 文件描述符监听集合	传入、传出参数 NULL大部分用
		(5)timeout: 	> 0: 	设置监听超时时长。
				NULL:	阻塞监听
				0:	非阻塞监听,轮询
		返回值:
			>0:	所有监听集合(3个)中, 满足对应事件的总数。
			 0:没有满足监听条件的文件描述符
			-1: errno

传入传出参数是指传入的是select函数需要监听的客户端文件描述,传出的是实际发送数据的,满足的话返回如下图所示,传入是监督 的数据,传出是视实际产生数据交互的fd
在这里插入图片描述

2.select相关函数简介

可以看到上图,我们需要标记这些监听的文件描述符。这里采用位图来对这些监听的文件描述符进行记录。下面介绍一下select要用到的函数的具体含义:
位图操作函数:


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)				位图所有二进制位置零
select多路IO转接原理:  借助内核, select 来监听, 客户端连接、数据通信事件。
函数的具体实现:
	第一个:void FD_ZERO(fd_set *set);	
	功能:清空一个文件描述符集合。这个文件描述符集合用来存储客服端fd。
		fd_set rset;
		FD_ZERO(&rset);
		
	第二个:void FD_SET(int fd, fd_set *set);	
	功能:将待监听的文件描述符,添加到监听集合中
	FD_SET(3, &rset);	FD_SET(5, &rset);	FD_SET(6, &rset);
	参数:待监听的文件描述符,文件描述符集合

	第三个:void FD_CLR(int fd, fd_set *set);	
	功能:将一个文件描述符从监听集合中 移除。
		FD_CLR(4&rset);

	第四个int  FD_ISSET(int fd, fd_set *set);	
	功能:判断一个文件描述符是否在监听集合中。
		返回值: 在:1;不在:0;
		FD_ISSET(4&rset);

端口可以重复使用。设置端口复用 setsockopt()函数:SO_REUSERADDR选项

函数原型:
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
头文件:
	#include <sys/types.h>
	#include <sys/socket.h>
参数: 
	sock:将要被设置或者获取选项的套接字。
	level:选项所在的协议层。
		1)SOL_SOCKET:通用套接字选项.	
	optname:需要访问的选项名。
		SO_REUSERADDR      允许重用本地地址和端口         int
	optval:对于setsockopt(),指向包含新选项值的缓冲。
		设置端口复用只要这样设置:int opt = 1;		// 设置端口复用。
	optlen:对于setsockopt(),现选项的长度。
返回值:
	成功返回0,
	失败返回-1,
	errno被设为以下的某个值  
	EBADF:sock不是有效的文件描述词
	EFAULT:optval指向的内存并非有效的进程空间
	EINVAL:在调用setsockopt()时,optlen无效
	ENOPROTOOPT:指定的协议层不能识别选项
	ENOTSOCK:sock描述的不是套接字
使用示例:端口复用就这样用
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

3.select设计思路

在这里插入图片描述
listend函数只有有客服端连接时才会返回数据。服务端才会有读事件发生

思路分析:	
1.创建套接字
  	int maxfd = 0;
  	lfd = socket();			
  	maxfd = lfd;
2.绑定地址结构 
	bind();					
3.设置监听上限  
	listen();				
4. 位图的初始化设置
	fd_set rset, allset;			创建r监听集合
	FD_ZERO(&allset);				将r监听集合清空
	FD_SET(lfd, &allset);			将 lfd 添加至读集合中。

5.循环调用select函数监听
	while1{
		5.1 保存监听集合
		rset = allset;
		5.2 监听文件描述符集合对应事件。		
   		ret  = select(lfd+1&rset, NULLNULLNULL);
		if(ret > 0{	
			有监听的描述符满足对应事件		
			if (FD_ISSET(lfd, &rset)) 
			{	//看lfd还在不在 &rset中,注意这个 &rset 之前的 &rset了		 1 在。 0不在。
				cfd = accept();				建立连接,返回用于通信的文件描述符
				maxfd = cfd;
				FD_SET(cfd, &allset);				添加到监听通信描述符集合中。
			}  
           //不仅要处理连接还要处理通信
			for (i = lfd+1; i <= 最大文件描述符; i++{
				FD_ISSET(i, &rset)				有read、write事件
				read()
				操作函数
				write();
			}	
		}
	}

4.代码实现

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>

#include "wrap.h"

#define SERV_PORT 6666


int main(int argc, char *argv[])
{
	int i, j, n, nready;
	int maxfd = 0;
	int listenfd, connfd;
	char buf[BUFSIZ];

	struct sockaddr_in clie_addr, serv_addr;
	socklen_t clie_addr_len;
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	//设置端口复用
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	//清空地址结构
	bzero(&serv_addr, sizeof(serv_addr));  
	serv_addr.sin_family= AF_INET;  
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    serv_addr.sin_port= htons(SERV_PORT);  
    Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  
    Listen(listenfd, 128); 

	/*定义两个位图:rset读事件描述符集合 allset用来暂存*/
	fd_set rset, allset;

	maxfd = listenfd;

	FD_ZERO(&allset);
	/*将监听文件描述符,添加到监听集合,
	 *构造了select函数初始的监控文件描述符集合*/
	FD_SET(listenfd, &allset);

	/*调用select函数循环监听是否有客户端连接*/
	while(1)
	{
		/*每次循环都重新设置select监控信号集*/
		rset = allset;
		/*第一次进入循环,maxfd为listenfd,监听read集合中只有listenfd*/
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		if (nready < 0)
		{
			perr_exit("select error");
		}
        /*如果有客户端连接,listenfd在返回的rest文件描述符集合中*/
		/*rest传入的是需要监听的集合,listenfd文件描述符在监听集合中
		*如果有客户端则连接,则监听的listenfd会被返回*/
		if (FD_ISSET(listenfd, &rset))
		{
			//有客户端连接,调用accept函数,accept不会阻塞
			clie_addr_len = sizeof(clie_addr);
			connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);

			/*将新连接的文件描述符,添加到文件描述符集合allset中*/
			FD_SET(connfd, &allset);

			/*切换最大文件描述符的值*/
			if(maxfd < connfd)
			{
				maxfd = connfd;
			}

			/*如果只有listenfd有事件,后面for调用read读取客户端的操作就不需要了
			* 1.第一次有客户端连接的时候只有listenfd
			  2.监听的集合中只有连接请求,都没有数据传向服务器的情况*/
			if(0 == --nready)
			{
				/*退出循环,nready = 1:listenfd*/
				continue;
			}
			/*遍历所有的监听的文件描述符集合,检测哪个client有数据就绪*/
			for(i = listenfd+1; i<=maxfd; i++)
			{
				//判读哪个client有数据就绪
				if(FD_ISSET(i, &rset))
				{
					/*当服务器端client关闭连接是,服务器也关闭对于的连接*/
					if ((n = Read(i, buf, sizeof(buf))) == 0)
					{
						Close(i); 
						/* 解除select对此文件描述符的监控 */
						FD_CLR(i, &allset);
					}
					/*处理读取的数据*/
					else if (n > 0) 
					{   
                   		for (j = 0; j < n; j++)  
                        	buf[j] = toupper(buf[j]);  
                   		Write(i, buf, n);  
                	}  
				}
			}
		}		
	}
	return 0;
}

编译运行,结果如下:
在这里插入图片描述

5. select优缺点

缺点: 监听上限受文件描述符限制。 最大 1024,检测满足条件的fd, 自己添加业务逻辑提高小。 提高了编码难度。
优点: 跨平台。win、linux、macOS、Unix、类Unix、mips

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值