网络-IO复用篇

前言

IO多路复用的基本用法优势在于:可以等待多个描述符就绪。在网络服务器编程中,IO多路复用目前是最常用的手段。

1 select模型

1.1 基本语法

select是一种典型的bit map管理模式,利用bit位来判断当前集合中IO是否就绪。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);

参数描述
1 timeout三种情况:

  1. 设置为空指针NULL:永远等待下去,直到有I/O准备是才返回;
  2. 设置时间设置为0:立即返回;
  3. 设置为具体时间:超时返回;(这种情况一种用法:用来作为一个粗略的定时器)

2 中间三个参数比较好理解:

readset:读fd集合
writeset:写fd集合
exceptset:异常fd集合

3 第一个参数maxfd

最大描述符加1 ,这个主要是用来进行描述符集合的遍历;

另外就是进行集合操作函数,用于集合的增删改查。

void FD_SET(fd_set *fdset);//监听集合某一位bit置一
void FD_ZERO(int fd, fd_set *fdset);//清空集合,所有bit位置零
void FD_CLR(int fd, fd_set *fdset);//集合某一位bit置零
int FD_ISSET(int fd, fd_set *fdset);//检查集合某一位bit是否置一

1.2 实战用法

	fd_set rfds, rset, wfds, wset;

	FD_SET(listenfd, &rfds);
	FD_ZERO(&rfds);
	FD_ZERO(&wfds);

	int max_fd = listenfd;

	while (1) {

		rset = rfds;
		wset = wfds;

		int nready = select(max_fd+1, &rset, &wset, NULL, NULL);//select阻塞等待

		if (FD_ISSET(listenfd, &rset)) { //服务器监听服务器socket事件

			struct sockaddr_in client;
		    socklen_t len = sizeof(client);
		    //接收客户连接
		    if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
		        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
		        return 0;
		    }
			//将客户连接加入读集合
			FD_SET(connfd, &rfds);
			
			if (connfd > max_fd) max_fd = connfd;

			if (--nready == 0) continue;

		}

		int i = 0;
		for (i = listenfd+1;i <= max_fd;i ++) {
			//客户端读集合触发,读取去客户数据
			if (FD_ISSET(i, &rset)) { // 
	
				n = recv(i, buff, MAXLNE, 0);
		        if (n > 0) {
		            buff[n] = '\0';
		            printf("recv msg from client: %s\n", buff);
					//客户fd加入写集合
					FD_SET(i, &wfds);
		        } else if (n == 0) { 
					FD_CLR(i, &rfds);//当客户端断开需要移除集合
		            close(i);	
		        }
				if (--nready == 0) break;
			} else if (FD_ISSET(i, &wset)) {//触发写集合向客户写数据
				
				send(i, buff, n, 0);
				FD_SET(i, &rfds);
			}
		}
	}

2 poll模型

poll模型和select模型实现原理基本一致,效率上也相差不大。

1.1 基本语法

#include <sys/poll.h>
 
struct pollfd {
	int fd; /* file descriptor */
	short events; /* requested events to watch */
	short revents; /* returned events witnessed */
};

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

第一个参数fds为一个pollfd结构数组,用来保存文件描述符

第二个参数nfds为pollfd结构体数组+1

第三个参数timeout为poll等待时间

1.2 实战用法

#define POLL_SIZE	1024
	//初始化poll集合
	struct pollfd fds[POLL_SIZE] = {0};
	fds[0].fd = listenfd;
	fds[0].events = POLLIN;

	int max_fd = listenfd;
	int i = 0;
	for (i = 1;i < POLL_SIZE;i ++) {
		fds[i].fd = -1;//fd为-1表示为空闲
	}

	while (1) {

		int nready = poll(fds, max_fd+1, -1);//poll

		if (fds[0].revents & POLLIN) {//接收客户端连接

			struct sockaddr_in client;
		    socklen_t len = sizeof(client);
		    if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
		        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
		        return 0;
		    }

			//加入poll队列
			fds[connfd].fd = connfd;
			fds[connfd].events = POLLIN;

			if (connfd > max_fd) max_fd = connfd;

			if (--nready == 0) continue;
		}

		//int i = 0;
		for (i = listenfd+1;i <= max_fd;i ++)  {

			if (fds[i].revents & POLLIN) {//客户端事件检查
				//接收回写
				n = recv(i, buff, MAXLNE, 0);
		        if (n > 0) {
		            buff[n] = '\0';
		            printf("recv msg from client: %s\n", buff);

					send(i, buff, n, 0);
		        } else if (n == 0) { //客户端退出

					fds[i].fd = -1;
		            close(i);
					
		        }
				if (--nready == 0) break;
			}
		}
	}

3 epoll模型

epoll模型可以说是神一样的存在,从效率和速度都是远超过其他io复用模型,目前大部分的linux网络组件都是基于epoll模型构建,使得服务突破百万连接的瓶颈。

1.1 基本语法

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

1 epoll_create

size没有实际意义大于零即可,主要是兼容旧版本
建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);

2 epoll_ctl

实现套接字的增删改
EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD

3 epoll_wait

收集发生事件的连接。

1.2 实战用法

#define EPOLL_SIZE	1024
	//创建epoll模型
	int epfd = epoll_create(1); //int size
	
	//初始化epoll事件
	struct epoll_event events[EPOLL_SIZE] = {0};
	struct epoll_event ev;

	ev.events = EPOLLIN;
	ev.data.fd = listenfd;
	//服务器socket加入epoll
	epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

	while (1) {
		//epoll等待io事件触发
		int nready = epoll_wait(epfd, events, EPOLL_SIZE, 50);//size对性能影响不大,主要是影响单次通知的数量
		if (nready == -1) {
			continue;
		}

		int i = 0;
		for (i = 0;i < nready;i ++) {
			//遍历所有io事件
			int clientfd =  events[i].data.fd;
			if (clientfd == listenfd) {//服务器接收客户端

				struct sockaddr_in client;
			    socklen_t len = sizeof(client);
			    if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
			        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
			        return 0;
			    }

				printf("accept\n");
				ev.events = EPOLLIN;
				ev.data.fd = connfd;
				epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

			} else if (events[i].events & EPOLLIN) {//客户端读事件
				//接收和回写
				printf("recv\n");
				n = recv(clientfd, buff, MAXLNE, 0);
		        if (n > 0) {
		            buff[n] = '\0';
		            printf("recv msg from client: %s\n", buff);

					send(clientfd, buff, n, 0);
		        } else if (n == 0) { //关闭客户端连接

					ev.events = EPOLLIN;
					ev.data.fd = clientfd;
					epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
		            close(clientfd);
		        }
			}
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值