Linux高性能服务器编程——I/O复用 select

提出背景

    不管是多线程,或者多进程,以及线程池,进程池。他们都存在一定的效率问题。

1.每个进程或线程只能为一个客户端进行服务,知道该客户端结束。(如果客户端在同一时间的访问数量特别大呢?)

2.当客户端发送来数据后,分配线程或进程为其服务完后,就要等待该客户端的下一次数据。(如果该客户端连接成功仅仅发了一次数据呢?)

    他们虽然能够进行与客户的交互服务,但是当在规定的时间内,系统(或者池)分配不出足够的进程或线程,达不到及时响应的要求。

I/O复用

    1.决解的问题:一个进程或线程能够同时对多个文件描述符(socket)进行服务。

    2.服务器上的进程或线程 如何将多个文件描述符进行同一监听,当任意文件描述符上有事件发生,其都能够及时处理。

列举几种我们要学习的I/O复用技术:1.select ; 2. poll ; 3. epoll(Linux独有的)。

select函数

1.函数原型:

#include<sys/select.h>
​int select (int nfds , fd_set *readfds , fd_set *writefds , fd_set *excefds , struct timeval*timeout);​

2.fd_set结构体

#include<typesizes.h>
#define _FD_SETSIZE 1024 //决定了fd_set能容纳的文件描述符数量

#include<sys/select.h>
#define FD_SETSIZE _FD_SETSIZE
typedef long_int _fd_mask;
#undef _NFDBITS
#define _NFDBITS { 8 *(int)sizeof(_fd_mask) ) // 8 * 4 = 32

typedef struct
{
#ifdef _USE_XOPEN
    _fd_mask fds_bits[_FD_SETSIZE / _NFDBITS]; //位数组 1024 / 32 = 32
#define _FDS_BITS(set) ((set) -> fds_bits)
#else
    fd_mask _fds_bits[_FD_SETSIZE / _NFDBITS]; //位数组 1024 / 32 = 32
#define _FDS_BITS(set) ((set) -> fds_bits)
#endif
}fd_set;

其实呢,简而言之就是

typedef struct
{
    int fds_bits[32];
}fd_set;

fd_set结构体仅仅包含一个整型数组,该数组的每一个元素的每一位(bit)标记一个文件描述符。int类型,总共32个元素,那么总共可以标记32 * 32 = 1024个文件描述符。

由于位运算操作过于繁琐,提供了一系列的宏来访问fd_set结构体中的位:

头文件: #include<sys/select.h>
清除fdset的所有位 FD_ZERO(fd_set *fdset);
设置fdset的位fd FD_SET(int fd, fd_set *fdset);
清除fdset的位fd FD_CLR( int fd , fd_set *fdset);
测试fdset的位fd是否被设置 int FD_ISSET(int fd , fd_set *fdset);

3.参数介绍

1.nfd: 被监听文件描述符总数  + 1
2.readfds: 用户感兴趣的可读事件的文件描述符集合

3.writefds:

可写事件的文件描述符的集合
4.exceptfds: 异常事件的文件描述符集合
5.timeout 设置超时时间,如果timeout设置为NULL,则select一直阻塞,直到某个文件就绪。

4.如何将文件描述符分别设置到readfds, writefds , exceptfds里?

    通过位运算宏函数FD_SET()。

5.select返回后,如何知道哪些文件描述符已经就绪?

    循环探测;通过宏函数int FD_ISSET(int fd , fd_set *fdset);

6.每次调用select之前,需要做什么准备工作?

    将所有的readfds , writefds , exceptfds全部都置为空。

逻辑理念

测试代码

#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>

void Init_fds(int* fds,int len)
{
		int i = 0;
		for(;i<len;++i)
		{
				fds[i] = -1;//都是无效的
		}
}
void Delete_fds(int* fds,int fd,int len)
{
		for(int i = 0;i<len;++i)
		{
				if(fds[i] == fd)
				{
						fds[i] = -1;
						break;
				}

		}
}
void Insert_fds(int* fds,int fd,int len)
{
		int i =0;
		for(;i<len;++i)
		{
				if(fds[i] == -1)
				{
					fds[i] = fd;
					break;
				}
		}
}
int main()
{		//完成TCP连接服务
		int sockfd = socket(AF_INET,SOCK_STREAM,0);
		assert(sockfd != -1);

		struct sockaddr_in ser,cli;
		memset(&ser,0,sizeof(ser));

		ser.sin_family = AF_INET;
		ser.sin_port = htons(6000);
		ser.sin_addr.s_addr = inet_addr("127.0.0.1");

		int  res = bind(sockfd, (struct sockaddr*)&ser,sizeof(ser));
		assert(res != -1);

		listen(sockfd,5);
		//fd_set,并将sockfd插入进去
		fd_set readfds;
		int fds[100];//数组
		Init_fds(fds,100);//初始化全部设置为-1,均为无效的文件描述符。
		Insert_fds(fds,sockfd,100);//将sockfd添加到fds中

		while(1)
		{
				int maxfd = -1;
				FD_ZERO(&readfds);//清空
				for(int i = 0;i<100;++i)//fds[100]的循环探测
				{
						if(fds[i] != -1)//说明数组有了就绪的事件
						{
								if((fds[i] > maxfd))//对于maxfd的处理
								{
										maxfd = fds[i];
								}
								FD_SET(fds[i],&readfds);//设置fd位。
						}
				}

				int n = select(maxfd + 1,&readfds,NULL,NULL,NULL);//读取
				if(n<=0)
				{
						printf("select is fail:\n");
						continue;
				}
				//n>0有文件描述符就绪,如何进行探测呢?
				for(int i = 0;i < 100;++i)//循环探测
				{
						if(fds[i] != -1 && FD_ISSET(fds[i],&readfds))//探测fdset的位fd是否被设置
								//就绪
						{
								if(fds[i] == sockfd)//连接请求
								{
										int len = sizeof(cli);
										int c = accept(sockfd,(struct sockaddr*)&cli,(socklen_t*)&len);
										if(c<0)
										{
												printf("accept is fail:\n");
												continue;
										}
										Insert_fds(fds,c,100);//对这个c加入到 readfds中fds[100]
								}
						
								else
								{
									int fd = fds[i];
									char buff[128]={0};//有数据可读取
									int n= recv(fd,buff,127,0);//只读取一次
									if(n<=0)
									{
											close(fd);
											Delete_fds(fds,fd,100);
											continue;
									}
									else
									{
											printf("%d: %s\n",fd,buff);
											send(fd,"OK",2,0);
									}
								}
						
						}
				}
		}
		close(sockfd);
}

猜你喜欢

转载自blog.csdn.net/genzld/article/details/84778041