Linux开发 I/O复用 Select模型 Epoll模型

一、介绍

1、多进程服务器的缺点:

1、需要大量的运算

2、大量的内存空间

所需的线程数 n(n-1)/2,计算复杂度O(n^2)。

2、select模型:

所需的线程数 n,计算复杂度O(n)。

二、Select模型具体步骤:

步骤

设置文件描述符

        select函数监视多个(不超过1024个)文件描述符

        fd_set结构体

FD_ZERO(fd set *fdset)∶将fd set变量的所有位初始化为0

FD_SET(int fd,fd set*fdset)∶在参数dset指向的变量中注册文件描述符fd的信息。

FD_CLR(int fd,fd set*fdset)∶从参数fdset指向的变量中清除文件描述符fd的信息。

FD_ISSET(int fd,fd_set*fdset)∶若参数fdset指向的变量中包含文件描述符fd的信息,则返回"真"。

Select函数

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

int select(int maxfd, fd_set*readset, fd_set* writeset, fd_set*exceptset, const struct

timeval * timeout);

→成功时返回大于0的值,失败时返回-1。

maxtfd :监视对象文件描述符数量。

readset:用于检查可读性,

writeset:用于检查可写性,

exceptset:用于检查带外数据

timeout:一个指向timeval结构的指针,用于决定select等待I/O的最长时间。如果为空将一直等待。

Select实现I/O复用服务端

代码:

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <pthread.h>
#include <semaphore.h>

#include <sys/select.h> 
#include <sys/times.h>  
void server101()
{
	//套接字
	//创建变量
	int serv_sock{}, clnt_sock{};
	struct sockaddr_in serv_adr {}, clnt_adr{};
	socklen_t clnt_sz = sizeof(clnt_adr);
	//赋值
	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(9527);

	//bind
	if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
	{
		//绑定失败
		printf("bind error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
		close(serv_sock);
		return;
	}

	//listen
	if (listen(serv_sock, 5) == -1)
	{
		printf("listen error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
		close(serv_sock);
		return;
	}

	//!!!!!!!单线程程序 accept()   卡在此处
	//!
	fd_set reads{}, copy_reads{};  //设置文件描述符
	FD_ZERO(&reads);  //清空
	FD_SET(serv_sock, &reads);  //监控serv_sock上的读
	timeval timeout{ 5,5000 };   //
	int max_sock = serv_sock;

	int fd_num{};
	while (true)
	{
		copy_reads = reads;       //!!!!!max_sock+1不能超过1024!!!!!!!
		fd_num = select(max_sock + 1, &copy_reads, NULL, NULL, &timeout);
		if (fd_num == -1)
		{
			printf("select error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
			close(serv_sock);
			return;
		}
		if (fd_num == 0)	continue;
		for (int i = 0; i < max_sock + 1; i++)   //!!!!CPU一直空转,资源浪费
		{
			if (FD_ISSET(i, &copy_reads))
			{
				if (i == serv_sock)  //服务器上发生的读事件 只可能是有连接进来
				{
					clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_sz);
					FD_SET(clnt_sock, &reads);
					if (max_sock < clnt_sock)
					{
						max_sock = clnt_sock;
					}
					printf("client is connected:%d\n", clnt_sock);
				}
				else  //客户端上发生事件  回声服务器
				{
					char buffer[256] = "";
					ssize_t str_len = read(i, buffer, sizeof(buffer));
					if (str_len == 0) //read的返回值是0  客户端已经结束
					{
						FD_CLR(i, &reads);
						close(i);
						printf("client is disconnected:%d\n", i);
					}
					else
					{
						write(i, buffer, str_len);
					}
				}
			}
		}
	}


}

void client101()
{
	int cln_sock = socket(PF_INET, SOCK_STREAM, 0);
	sockaddr_in serv_adr;
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr("127.0.0.1");
	serv_adr.sin_port = htons(9527);

	if (connect(cln_sock, (sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
	{
		printf("connect error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
		close(cln_sock);
		return;
	}

	char message[256] = "";
	while (true)
	{
		printf("Input message(q/Q to quit):");
		fgets(message, sizeof(message), stdin);
		if (!strcmp(message, "q\n") || !strcmp(message, "q\n"))	break;
		write(cln_sock, message, strlen(message));
		memset(message, 0, sizeof(message));
		read(cln_sock, message, sizeof(message));
		printf("server:%s\n", message);
	}
	close(cln_sock);
}

void lession101(const char* arg)
{
	if (strcmp(arg, "s") == 0)  //如果用户输入的是s  就启动服务端
	{
		server101();
	}
	else
	{
		client101();
	}
}

int main(int argc, char* argv[])
{
	lession101(argv[1]);
	return 0;
}

三、Epoll模型

Epoll的三大函数:epoll_create,epoll_wait, epoll_ctl

epoll_create

#include<sys/epoll.h>

int epoll_create(int size);

→成功时返回epoll文件描述符,失败时返回-1。

size:epoll实例的大小。

该函数从2.3.2版本的开始加入的,2.6版开始引入内核

Linux最新的内核稳定版本已经到了5.8.14,长期支持版本到了5.4.70

从2.6.8内核开始的Linux,会忽略这个参数,但是必须要大于0

这个是Linux独有的函数

 epoll_ctl

#include<sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

→成功时返回0,失败时返回-1。

●epfd 用于注册监视对象的epoll例程的文件描述符。

●op 用于指定监视对象的添加、删除或更改等操作。

EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL

● fd 需要注册的监视对象文件描述符。

● event 监视对象的事件类型。

EPOLLIN∶需要读取数据的情况。

EPOLLOUT∶输出缓冲为空,可以立即发送数据的情况。

EPOLLPRI∶收到OOB数据的情况。

EPOLLRDHUP∶断开连接或半关闭的情况,这在边缘触发方式下非常有用。

EPOLLERR∶发生错误的情况。

EPOLLET∶以边缘触发的方式得到事件通知。

EPOLLONESHOT∶发生一次事件后,相应文件描述符不再收到事件通知。因此需要向

epoll_ctl函数的第二个参数传递

EPOLLCTL_MOD,再次设置事件。

epoll_wait

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event*events,int maxevents,int timeout);

→成功时返回发生事件的文件描述符数,失败时返回-1。

epfd 表示事件发生监视范围的epol例程的文件描述符

events 保存发生事件的文件描述符集合的结构体地址值。

maxevents 第二个参数中可以保存的最大事件数目。

Timeout:以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件。

Epoll实现I/O复用服务端

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <pthread.h>
#include <semaphore.h>

#include <sys/select.h> 
#include <sys/times.h>  

#include <sys/epoll.h>
void server102()
{
	//套接字socket
	//创建变量
	int serv_sock{}, clnt_sock{};
	struct sockaddr_in serv_adr {}, clnt_adr{};
	socklen_t clnt_sz = sizeof(clnt_adr);
	char buf[1024] = "";  //收发缓冲区
	//赋值
	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(9527);

	//连接bind
	if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
	{
		//绑定失败
		printf("bind error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
		close(serv_sock);
		return;
	}

	//listen
	if (listen(serv_sock, 5) == -1)
	{
		printf("listen error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
		close(serv_sock);
		return;
	}

	//!!!!!!!epoll
	epoll_event event;
	int epfd, event_cnt;
	epfd = epoll_create(1);  //参数大于0即可
	if (epfd == -1)
	{
		printf("epoll_create error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
		close(serv_sock);
		return;
	}
	epoll_event* all_events = new epoll_event[100];

	event.events = EPOLLIN;
	event.data.fd = serv_sock;
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
	while (true)
	{
		event_cnt = epoll_wait(epfd, all_events, 100, 1000);//最后1000表示等待1s -1则无限等
		if (event_cnt==-1)
		{
			printf("epoll_wait error %d %s\n", errno, strerror(errno));
			break;
		}
		if(event_cnt == 0)  continue;
		for (int i = 0; i < event_cnt; i++)
		{
			if (all_events[i].data.fd == serv_sock)
			{
				clnt_sz = sizeof(clnt_adr);
				clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_sz);
				event.events = EPOLLIN;
				event.data.fd = clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("client is connected! %d\n", clnt_sock);
			}
			else
			{
				ssize_t len = read(all_events[i].data.fd, buf, sizeof(buf));
				if (len <= 0)
				{
					epoll_ctl(epfd, EPOLL_CTL_DEL, all_events[i].data.fd, NULL);
					close(all_events[i].data.fd);
					printf("client is closed! %d\n", all_events[i].data.fd);
				}
				else
				{
					write(all_events[i].data.fd, buf, len);
				}
			}
		}
		{
		}
	}

	delete[] all_events;
	close(serv_sock);
	close(epfd);
}

void client102()
{
	int cln_sock = socket(PF_INET, SOCK_STREAM, 0);
	sockaddr_in serv_adr;
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr("127.0.0.1");
	serv_adr.sin_port = htons(9527);

	if (connect(cln_sock, (sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
	{
		printf("connect error %d %s\n", errno, strerror(errno));  //打印出错误编号和含义
		close(cln_sock);
		return;
	}

	char message[256] = "";
	while (true)
	{
		printf("Input message(q/Q to quit):");
		fgets(message, sizeof(message), stdin);
		if (!strcmp(message, "q\n") || !strcmp(message, "q\n"))	break;
		write(cln_sock, message, strlen(message));
		memset(message, 0, sizeof(message));
		read(cln_sock, message, sizeof(message));
		printf("server:%s\n", message);
	}
	close(cln_sock);
}

//epoll
void lession102(const char* arg)
{
	if (strcmp(arg, "s") == 0)  //如果用户输入的是s  就启动服务端
	{
		server102();
	}
	else
	{
		client102();
	}
}

int main(int argc, char* argv[])
{
	lession102(argv[1]);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yy_xzz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值