Linux——高性能服务器编程——select&poll&epoll

I/O复用:
多进程、 多线程、 进程池、 线程池每一个执行序列在同一时刻只能处理一个
socket(监
听、 链接)。 以线程池为例: 如果创建
N个线程, 同一时刻只能处理N的客户连接。I/O
用: 在一个进程或者一个线程中, 同时监听多个
socket。 当有socket上有事件发生时, 程序
才会接受数据。

select:

int n = select(int nfds, fd_set *read, fd_set *write,fd_set *except,struct timeval *timeout);

nfds:监听的最大文件描述符值 + 1;

read write except:select监听的文件描述符上的可读,可写,异常事件。

struct fd_set

{

      long int fd_sets[32];

}


struct timeval

{

      long tv_sec;  秒数

      long tv_usec;  微秒

}

返回值: 0:超时
                -1 :出错
               >0:就绪文件描述符的个数


poll:
int poll(struct pollfd *fds, int nfds, int timeout);
fds:数组,数组中记录所有监听的文件描述符和关注的事件类型
struct pollfd
{
     int fd;       /*文件描述符*/
     short events;     /*注册的事件*/
     short revent;       /*实际发生的事件,由内核填充 */
};
nfds:数组元素个数
timeout:超时时间,单位毫秒,-1表示永远阻塞,0表示立即返回
返回值: 0:超时
                -1 :出错
               >0:就绪文件描述符的个数



epoll:
#include
int epoll_create(int size);//创建一个内核事件表,返回内核事件表的标志符id
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:内核事件表的id,epoll_create的返回值
op:操作,EPOLL_CTL_ADD,EPOLL_CTL_MOD ,EPOLL_CTL_DEL
event:事件类型

struct epoll_event
{
      int events;
      epoll_data_t  data;
};
struct epoll_data_t
{
      void *ptr;
       int fd;
       int u32;
       int u64;
};
int epoll_wait(int epfd, struct epoll_event *revent, int maxevents, int timeout);
revents是一个用于记录内核就绪事件的数组。

三组I/O复用的函数比较:

1)事件集:

Select的参数类型fd_set没有将文描和事件绑定,所以需要三个这种类型的参数来分别传入和输出可读,可写和异常事件;poll则修改pollfd结构体中的revents事件,event事件保持不变,这两者时间复杂度都为O(n);但是epoll在内核中维护一个事件表,提供epoll_ctl来添加,删除,修改事件。Epoll_waitevents返回就绪的事件,则只返回就绪的文描个数

Poll, epollselest最大文件描述符个数受到限制,时间复杂度O1

2)最大支持文件描述符数:

Pollepoll分别用nfdsmaxevents来指定最多监听多少个文描个数和事件,两个都能达到系统最大允许的65535select允许的最大文描个数有限制,一般不可修改。

3)工作模式

Selectpoll工作在低效的LT模式,epoll工作在高效的ET模式,epoll支持EPOLLONESHOT事件。

4)具体实现

Selectpoll采用轮询的方式,每次调用都要扫描整个注册文件描述符的集合;epoll_wait采用回调的方式。

Linux——高性能服务器编程——select&poll&epoll_第1张图片

select.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void main()
{
	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("192.168.1.120");

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

	listen(sockfd, 5);

	int nfds = sockfd + 1;
	fd_set read;//
	FD_ZERO(&read);

	int fds[128];//记录所有要监听的文描
	//memset(fds, -1, 128);
	int i = 0;
	for(;i < 128;++i)
	{
		fds[i] = -1;
	}
	fds[0] = sockfd;

	int max = 0;

	while(1)
	{
		FD_ZERO(&read);//清空
		int j = 0;
		for(; j < 128; j++)
		{
			if(fds[j] != -1)
			{
				if(max < fds[j])
					max = fds[j];

				FD_SET(fds[j], &read);
			}
		}
		int n = select(max+1, &read, NULL, NULL, NULL);
		//将fds数组中的文描设置到read,write,except中
		if(n < 0)
		{
			printf("error\n");
			exit(0);
		}
		if(n == 0) //  超时
		{
			printf("time out\n");
			continue;
		}
		int i = 0;

		//循环判断fd中每个文描是否是就绪事件
		for(; i < 128; ++i)
		{
			if(fds[i] == -1)
				continue;

			if(FD_ISSET(fds[i], &read))
			{
				if(fds[i] == sockfd)
				{
					int len = sizeof(cli);
					int c = accept(sockfd, (struct sockaddr*)&cli, &len);
					int i = 0;
					for(; i < 128; ++i)
					{
						if(fds[i] == -1)
						{
							fds[i] = c;
							break;
						}
					}
				}
				else
				{
					char buff[128] = {0};
					int n = recv(fds[i], buff, 128, 0);
					if(n <= 0)
					{
						close(fds[i]);
						fds[i] = -1;
						continue;
					}

					printf("%s\n", buff);
					send(fds[i], "ok", 2, 0);
				}
			}
		}

	}
}

cli.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void main()
{
	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("192.168.1.120");

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

	while(1)
	{
		printf("please input: ");
		fflush(stdout);
		char buff[128] = {0};
		fgets(buff, 128, stdin);
		if(strncmp(buff, "end", 3) == 0)
		{
			close(sockfd);
			break;
		}

		send(sockfd, buff, strlen(buff) - 1, 0);
		memset(buff, 0, 128);
		recv(sockfd, buff, 127, 0);
		printf("%s\n", buff);
	}
}
结果:

Linux——高性能服务器编程——select&poll&epoll_第2张图片

poll.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX 128

void Init(struct pollfd *fds, int len)
{
	int i = 0;
	for(; i < len; ++i)
	{
		fds[i].fd = -1;
		fds[i].events = 0;
	}
}

void AddFd(struct pollfd *fds, int len, int fd)
{
	int i = 0;
	for(; i < len; ++i)
	{
		if(fds[i].fd == -1)
		{
			fds[i].fd = fd;
			fds[i].events = POLLIN;
			break;
		}
	}
}

void main()
{
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(listenfd != -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("192.168.1.120");

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

	listen(listenfd, 5);

	struct pollfd fds[MAX];
	Init(fds, MAX);
	AddFd(fds, MAX, listenfd);

	while(1)
	{
		int n = poll(fds, MAX, -1);
		assert(n != -1);
		if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		int i = 0;
		for(; i < MAX; ++i)
		{
			if(fds[i].fd == -1)
			{
				continue;
			}
			if(fds[i].revents & POLLIN)
			{
				int fd = fds[i].fd;
				if(fd == listenfd)
				{
					int len = sizeof(cli);
					int c = accept(fd, (struct sockaddr *)&cli, &len);
					assert(c != -1);

					printf("one client link\n");

					AddFd(fds, MAX, c);
				}
				else
				{
					char  buff[128] = {0};
					int n = recv(fd, buff, 127, 0);
					if(n <= 0)
					{
						printf("client unlink\n");
						close(fd);
						fds[i].fd = -1;
						fds[i].events = 0;
						continue;
					}

					printf("%d :  %s\n", fd, buff);
					send(fd, "OK", 2, 0);
				}
			}
		}
	}
}

结果:
Linux——高性能服务器编程——select&poll&epoll_第3张图片















你可能感兴趣的:(Linux——高性能服务器编程——select&poll&epoll)