网络通信学习笔记之————多路复用

一、IO模型介绍

1、阻塞IO,常用的scanf、printf、read、write、cout、cin

2、非阻塞IO,读取不到数据也立即返回

​ recv、send和flags设置为MSG_DONTWAIT

​ open打开文件时带O_NONBLOCK标志,read、write就是非阻塞的

3、信号驱动IO(Qt的信号和槽机制)

4、异步IO(不能立即得到结果,但可以先去做其他事情,能完成后再通知你)

5、多路复用IO

在不创建新的进程和线程的情况下监控多个文件描述符,多应用于网络编程时一个服务端程序为多个客户端程序提供服务,多用于在业务逻辑简单,客户端需要的服务时间短,响应时间无太高要求的场景。

二、使用select函数实现多路复用IO

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

功能:监控多个文件描述符的 读、写、异常 操作
nfds:最大文件描述符+1
readfds:监控读操作文件描述符集合
writefds:监控写操作文件描述符集合
exceptfds:监控异常操作文件描述符集合
timeout:设置超时时间
返回值:监控到文件描述符的个数,超时返回0,出错返回-1

void FD_CLR(int fd, fd_set *set); 
功能:从集合吕删除文件描述符
int  FD_ISSET(int fd, fd_set *set);
功能:测试集合中是否有文件描述符存在

void FD_SET(int fd, fd_set *set);
功能:向集合中添加文件描述符
void FD_ZERO(fd_set *set); 
功能:清空文件描述符集合
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    
    
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (0 > sockfd)
	{
    
    
		perror("socket");
		return -1;
	}

	struct sockaddr_in addr = {
    
    };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(5678);
	addr.sin_addr.s_addr = inet_addr("192.168.0.111");
	socklen_t addrlen = sizeof(addr);

	if (bind(sockfd, (struct sockaddr *)&addr, addrlen))
	{
    
    
		perror("bind");
		return -1;
	}

	if (listen(sockfd, 10))
	{
    
    
		perror("listen");
		return -1;
	}

	// 创建两个文件描述符集合
	fd_set readfd, readcp;
	// 清空两个文件描述符集合
	FD_ZERO(&readfd);
	FD_ZERO(&readcp);
	// 把sockfd加到readfd中
	FD_SET(sockfd, &readfd);

	int maxfd = sockfd + 1;
	struct timeval timeout = {
    
    5, 5000000};
	char buf[4096] = {
    
    };

	for (;;)
	{
    
    
		// 每次都要重新向select传递所监控的对象
		readcp = readfd;

		// 监控集合中的文件描述符
		int ret = select(maxfd, &readcp, NULL, NULL, &timeout);
		// 返回值小于零出错,等于零超时
		if (ret <= 0)
			continue;

		for (int i = 3; i < maxfd; i++)
		{
    
    
			// 检查sockfd 是否有新客户端连接
			if (i == sockfd && FD_ISSET(i, &readcp))
			{
    
    
				int clifd = accept(sockfd, (struct sockaddr *)&addr, &addrlen);
				if (clifd < 0)
					continue;

				FD_SET(clifd, &readfd);
				if (clifd + 1 >= maxfd)
				{
    
    
					maxfd = clifd + 1;
				}
			}
			else if (FD_ISSET(i, &readcp))
			{
    
    
				int ret = recv(i, buf, sizeof(buf), 0);
				if (ret <= 0)
				{
    
    
					FD_CLR(i, &readfd);
					continue;
				}
				printf("fd:%d recv:%s\n", i, buf);
				send(i, buf, strlen(buf) + 1, 1);
			}
		}
	}
}
select设计不合理的地方:

​ 1、所有被监视的文件描述符都需要检查(效率不高)。

​ 2、每次调用select都需要向它传递新的监视对象信息。

select的优点是:

​ 程序的兼容性高,所有操作系统都支持。

扫描二维码关注公众号,回复: 15401239 查看本文章

三、使用pselect函数实现多路复用IO

int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask);

功能:与select的功能大致类似
区别:
	1、select函数用的timeout参数,是一个timeval的结构体(包含秒和微秒),然而pselect用的是一个timespec结构体(包含秒和纳秒)
	2、select函数可能会为了指示还剩多长时间而更新timeout参数,然而pselect不会改变timeout参数
	3、select函数没有sigmask参数,当pselect的sigmask参数为null时,两者行为时一致的。有sigmask的时候,pselect相当于如下的select()函数,在进入select()函数之前手动将信号的掩码改变,并保存之前的掩码值;select()函数执行之后,再恢复为之前的信号掩码值。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    
    
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (0 > sockfd)
	{
    
    
		perror("socket");
		return -1;
	}

	struct sockaddr_in addr = {
    
    };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(5678);
	addr.sin_addr.s_addr = inet_addr("192.168.0.111");
	socklen_t addrlen = sizeof(addr);

	if (bind(sockfd, (struct sockaddr *)&addr, addrlen))
	{
    
    
		perror("bind");
		return -1;
	}

	if (listen(sockfd, 10))
	{
    
    
		perror("listen");
		return -1;
	}

	// 创建两个文件描述符集合
	fd_set readfd, readcp;
	// 清空两个文件描述符集合
	FD_ZERO(&readfd);
	FD_ZERO(&readcp);
	// 把sockfd加到readfd中
	FD_SET(sockfd, &readfd);

	int maxfd = sockfd + 1;
	// struct timeval timeout = {5,5000000};
	struct timespec timeout = {
    
    5, 500000000};
	char buf[4096] = {
    
    };

	for (;;)
	{
    
    
		// 每次都要重新向select传递所监控的对象
		readcp = readfd;

		// 监控集合中的文件描述符
		// pselect 不会屏蔽信号,需要把已经屏蔽的信号集经pselect函数,pselect会把屏蔽的信号取消,等执行结束后再还原屏蔽的信号
		int ret = pselect(maxfd, &readcp, NULL, NULL, &timeout, NULL);
		// 返回值小于零出错,等于零超时
		if (ret <= 0)
			continue;

		for (int i = 3; i < maxfd; i++)
		{
    
    
			// 检查sockfd 是否有新客户端连接
			if (i == sockfd && FD_ISSET(i, &readcp))
			{
    
    
				int clifd = accept(sockfd, (struct sockaddr *)&addr, &addrlen);
				if (clifd < 0)
					continue;

				FD_SET(clifd, &readfd);
				if (clifd + 1 >= maxfd)
				{
    
    
					maxfd = clifd + 1;
				}
			}
			else if (FD_ISSET(i, &readcp))
			{
    
    
				int ret = recv(i, buf, sizeof(buf), 0);
				if (ret <= 0)
				{
    
    
					FD_CLR(i, &readfd);
					continue;
				}
				printf("fd:%d recv:%s\n", i, buf);
				send(i, buf, strlen(buf) + 1, 1);
			}
		}
	}
}

四、使用poll函数实现多路复用IO

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:所有被监控的文件描述符结构体数组
nfds:数组的长度
timeout:超时时间,毫秒单位

struct pollfd
{
    
    
	int fd;   //被监控文件描述符
    short events; // 等待的需要监控事件
	short revents; // 实际发生了的事件,也就是返回结果
};

events:
	POLLIN    普通或优先级带数据可读
    POLLPRI   高优先级数据可读
	POLLOUT   普通数据可写
	POLLRDHUP  对方socket关闭
	POLLERR   发生错误
	POLLHUP   发生挂起
	POLLNVAL   描述字不是一个打开的文件
        
poll特点:
	1.文件描述符没有最大限制 -数据结构:链表
	2.每次调用都需要将fd集合从用户态拷贝到内核态
	3.内核需要遍历所有fd,效率低
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>

int main()
{
    
    
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (0 > sockfd)
	{
    
    
		perror("socket");
		return -1;
	}

	struct sockaddr_in addr = {
    
    };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(5678);
	addr.sin_addr.s_addr = inet_addr("192.168.0.111");
	socklen_t addrlen = sizeof(addr);

	if (bind(sockfd, (struct sockaddr *)&addr, addrlen))
	{
    
    
		perror("bind");
		return -1;
	}

	if (listen(sockfd, 10))
	{
    
    
		perror("listen");
		return -1;
	}

	nfds_t nfds = 10, maxi = 1;
	struct pollfd *fds = calloc(sizeof(struct pollfd), 10);
	fds[0].fd = sockfd;
	fds[0].events = POLLIN;

	for (;;)
	{
    
    
		int ret = poll(fds, nfds, 100000);
		if (fds[0].revents & POLLIN)
		{
    
    
			int clifd = accept(sockfd, (struct sockaddr *)&addr, &addrlen);
			if (clifd < 0)
				continue;
			for (int i = 0; i < nfds; i++)
			{
    
    
				if (0 == fds[i].fd)
				{
    
    
					fds[i].fd = clifd;
					fds[i].events = POLLIN;
					maxi = i + 1;
					break;
				}
			}
		}
		for (int i = 1; i < maxi; i++)
		{
    
    
			if (fds[i].revents & (POLLIN))
			{
    
    
				char buf[4096] = {
    
    };
				int ret = recv(fds[i].fd, buf, sizeof(buf), 0);
				if (ret <= 0)
				{
    
    
					fds[i].fd = 0;
					continue;
				}
				printf("clifd:%d recv:%s\n", fds[i].fd, buf);
				send(fds[i].fd, buf, strlen(buf) + 1, 1);
			}
		}
	}
}

五、使用epoll函数实现多路复IO

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.文件描述符没有最大限制 --数据结构:红黑树
    2.只需拷贝一次fd到内核态
	3.内核只需判断就绪链表是否为空,不需要遍历所有fd,效率高,并把就绪fd拷贝到用户空间
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

int main()
{
    
    
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (0 > sockfd)
	{
    
    
		perror("socket");
		return -1;
	}

	struct sockaddr_in addr = {
    
    };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(6666);
	addr.sin_addr.s_addr = inet_addr("192.168.0.111");
	socklen_t addrlen = sizeof(addr);

	if (bind(sockfd, (struct sockaddr *)&addr, addrlen))
	{
    
    
		perror("bind");
		return -1;
	}

	if (listen(sockfd, 10))
	{
    
    
		perror("listen");
		return -1;
	}
	int epfd = epoll_create(10);
	struct epoll_event event;
	event.data.fd = sockfd;
	event.events = EPOLLIN;
	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
	struct epoll_event *events = calloc(sizeof(struct epoll_event), 10);

	for (;;)
	{
    
    
		int cnt = epoll_wait(epfd, events, 10, 100000);
		if (cnt <= 0)
			continue;
		for (int i = 0; i < cnt; i++)
		{
    
    
			printf("cnt=%d fd=%d sockfd:%d\n", cnt, events[0].data.fd, sockfd);
			if (events[i].data.fd == sockfd)
			{
    
    
				int clifd = accept(sockfd, (struct sockaddr *)&addr, &addrlen);
				if (0 > clifd)
					continue;
				event.data.fd = clifd;
				event.events = EPOLLIN;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clifd, &event);
			}
			else
			{
    
    
				char buf[4096] = {
    
    };
				int ret = recv(events[i].data.fd, buf, sizeof(buf), 0);
				if (ret <= 0)
				{
    
    
					epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, events + i);
					continue;
				}

				printf("clifd:%d recv:%s\n", events[i].data.fd, buf);
				send(events[i].data.fd, buf, strlen(buf) + 1, 1);
			}
		}
	}
}

猜你喜欢

转载自blog.csdn.net/m0_62480610/article/details/127640659