Notas de estudio de comunicación de red: multiplexación

1. Introducción al modelo IO

1. Bloqueo de IO, scanf, printf, read, write, cout, cin de uso común

2. IO sin bloqueo, regrese inmediatamente si los datos no se pueden leer

​ recv, send y flags están establecidos en MSG_DONTWAIT

abrir con el indicador O_NONBLOCK al abrir el archivo, leer y escribir no bloquean

3. E/S impulsada por señal (mecanismo de ranura y señal de Qt)

4. E/S asíncrona (no puede obtener el resultado de inmediato, pero puede hacer otras cosas primero y se le notificará una vez que se complete)

5. E/S de multiplexación

Supervise múltiples descriptores de archivos sin crear nuevos procesos y subprocesos. Se usa principalmente en la programación de redes cuando un programa de servidor proporciona servicios para múltiples programas de clientes. Se usa principalmente en la lógica comercial simple y el tiempo de servicio requerido por el cliente. Escenarios donde el tiempo de respuesta es corto y el tiempo de respuesta no es demasiado alto.

2. Use la función de selección para realizar la multiplexación 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);
			}
		}
	}
}
Donde el diseño seleccionado no es razonable:

1. Todos los descriptores de archivos monitoreados deben verificarse (no es eficiente).

2. Cada vez que llama a select, necesita pasarle nueva información del objeto de monitoreo.

Las ventajas de seleccionar son:

El programa tiene una alta compatibilidad y es compatible con todos los sistemas operativos.

3. Use la función pselect para realizar la multiplexación 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);
			}
		}
	}
}

En cuarto lugar, utilice la función de encuesta para realizar la multiplexación de E/S.

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);
			}
		}
	}
}

5. Use la función epoll para realizar IO multiplexado

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);
			}
		}
	}
}

Supongo que te gusta

Origin blog.csdn.net/m0_62480610/article/details/127640659
Recomendado
Clasificación