(六)Linux网络编程之IO多路 - select

一、IO多路转接

1、IO操作方式
  • 阻塞等待
    好处:不占用CPU的宝贵时间片
    缺点:同一时刻只能处理一个操作,效率低
  • 非阻塞,忙轮询
    好处:提高了程序的执行效率
    缺点:需要占用更多的CPU和系统资源

2、解决方案:使用IO多路转接技术select/poll/epoll
  • 第一种:select/poll 【线性表】
    select代收员比较懒,他只会告诉你几个快递到了,但是哪个快递,你需要挨个遍历一遍。【委托内核做的】
  • 第二种:epoll 【红黑树】
    epoll代收员很勤快,不仅概诉你有几个,还会告诉你是哪几个(如对应的客户端)
3、什么是I/O多路转接技术
  • 先构造一张有关文件描述符的列表,将要监听的文件描述符添加到该表中

  • 然后调用一个函数,监听该表中的文件描述符,知道这些描述符表中的一个进行I/O操作时(即查看读的内核缓冲区),该函数才返回。
    • 该函数为阻塞函数
    • 函数对文件描述符的检测操作是由内核完成的

  • 在返回时,它告诉进行有多少(哪些)描述符要进行I/O操作

在这里插入图片描述
在这里插入图片描述


二、select

struct timeval{
	long tv_sec;//秒
	long tv_usec;//微秒
	//tv_sec+tv_usec
};//在settimer()使用

int select(int nfds,
			fd_set *readfds,//传入传出参数,
			fd_set *writefds,
			fd_set *exceptfds,
			struct timeval *timeout);

fd-读、写、异常

参数
nfds:要检测的文件描述符中最大的fd+1 - 1024(fd_set最大存储的文件描述符个数为1024,为什么是1024:fd_set用数组实现)
readfds:读集合 检测读文件描述符对应的读缓冲区
writefds:写集合 NULL(写是主动的,一般不去检测)
exceptfds:异常集合 NULL
timeout
1)NULL:永久阻塞,当检测到文件描述符fd发生变化时返回
2)经过10s返回
timeval a;
a.tv_sec=10;
a.tv_usec=0;

文件描述符集类型:fd_set rdset

//文件描述符操作函数

/*全部清空,所有标志位清空为0*/
void FD_ZERO(fd_set *set);

/*从集合中删除某一项 1->0*/
void FD_CLR(int fd,fd_set *set);
//FD_CLR(2,&reads);

/*将某个文件描述符添加到集合 0->1*/
void FD_SET(int fd,fd_set *set);

/*判断某个文件描述符是否在集合中*/
int FD_ISSET(int fd,fd_set *set);

在这里插入图片描述


1、select 工作过程分析

客户端A,B,C,D,E,F连接到服务器,分别对应文件描述符3,4,100,101,102,103

  • A,B,C发送了数据 【3->1 4->1 100->1 101->1 102->1 103->1】
  • 用户区的表拷贝到内核区
  • 内核检测完后,若无数据写入内核核缓冲区,则将无数据的标志位修改为0【101->0 102->0 103->0】
  • 然后,内核区的表拷贝回用户区,覆盖原先的表
 fd_set reads,temp;  //-用户空间
 FD_SET(3,&reads);....
 temp=reads;
 select(103+1,&temp,NULL,NULL,NULL);
 /*reads这张表,内核会拷贝一份(从用户区到内核区),
 传递的是地址,修改后原先的数据被覆盖了,
 因此需要传入一个临时的变量*/


2、select伪代码
int main()
{
	int lfd=socket();
	bind();
	listen();
	//创建一文件描述符
	fd_set reads,temp;
	//初始化
	FD_ZERO(&reads);
	//监听的lfd加入到读集合
	FD_SET(lfd,&reads);
	int maxfd=lfd;
	while(1)
	{
		//委托检测
		temp=reads;
		int ret=select(maxfd,&temp,NULL,NULL,NULL);

		//是不是监听的
		if(FD_ISSET(lfd,&temp))
		{
			//接受新连接
			int cfd=accept();
			//cfd加入到读集合
			FD_SET(cfd,&reads);
			//更新maxfd
			maxfd=maxfd<cfd?cfd:maxfd;
		}
		//客户端发送数据
		for(int i=lfd+1;i<maxfd+1;++i)
		{
			if(FD_ISSET(i,&temp))
			{
				int len=read();
				if(len==0)
				{
					//cfd从读集合中删除
					FD_CLR(i,&reads);
				}
				write();
			}
		}
	}
}

3、select代码实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/select.h>

int main(int argc,const char* argv[])
{
        if(argc<2)
        {
                printf("eg: ./a.out port\n");
                exit(1);
        }
        struct sockaddr_in serv_addr;
        socklen_t serv_len=sizeof(serv_addr);
        int port=atoi(argv[1]);

        //创建套接字
        int lfd=socket(AF_INET,SOCK_STREAM,0);

        //初始化服务器sockaddr_in
        memset(&serv_addr,0,serv_len);
        serv_addr.sin_family=AF_INET;//地址族
        serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//监听本机所有IP
        serv_addr.sin_port=htonl(port);

        //绑定IP和端口
        bind(lfd,(struct sockaddr*)&serv_addr,serv_len);

        //设置同时监听的最大个数
        listen(lfd,36);
        printf("Start accept.....\n");

        struct sockaddr_in client_addr;
        socklen_t cli_len=sizeof(client_addr);

        //最大的文件描述符
        int maxfd=lfd;
        //文件描述符读集合
        fd_set reads,temp;
        //初始化
        FD_ZERO(&reads);
        FD_SET(lfd,&reads);

        while(1)
        {
                //委托内核做IO检测
                temp=reads;
                int ret=select(maxfd+1,&temp,NULL,NULL,NULL);
                if(ret==-1)
                {
                        perror("select error");
                        exit(1);
                }

                //客户端发起了新连接
                if(FD_ISSET(lfd,&temp))
                {
                        //接受连接请求 - accept 不阻塞
                        int cfd=accept(lfd,(struct sockaddr*)&client_addr,&cli_len);
                        if(cfd==-1)
                        {
                                perror("accept error");
                                exit(1);
                        }
                        char ip[64];
                        printf("new client IP:%s,Port:%s\n",inet_ntop(AF_INET,
                                &client_addr.sin_addr.s_addr,ip,sizeof(ip)),
                                ntohs(client_addr.sin_port));
                        //将cfd加入到待检测的读集合中 - 下一次就可以检测到了
                        FD_SET(cfd,&reads);
                        //更新最大的文件描述符
                        maxfd=maxfd<cfd?cfd:maxfd;
                }

                //已经连接的客户端有数据到达
                int i;
                for(i=lfd+1;i<maxfd+1;++i)
                {
                        if(FD_ISSET(i,&temp))
                        {
                                char buf[1024]={0};
                                int len=recv(i,buf,sizeof(buf),0);
                                if(len==-1)
                                {
                                        perror("recv error");
                                        exit(1);
                                }
                                else if(len==0)
                                {
                                        printf("客户端已经断开了连接\n");
                                        close(i);
                                        //从读集合中删除
                                        FD_CLR(i,&reads);
                                }
                                else
                                {
                                        printf("recv buf:%s\n",buf);
                                        send(i,buf,strlen(buf)+1,0);
                                }
                        }
                }
        }
        close(lfd);
        return 0;
}


4、优缺点

优点:

  • 跨平台

缺点:

  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时会很大
  • select支持的文件描述符数量太小了,默认是1024【可扩展,修改配置文件】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值