多路IO转接服务器-select

有帮助记得点个赞+关注

一.多路IO转接服务器

多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主要思想,不在由应用程序自己监视客户端连接,取而代之的是由内核替应用程序监视文件

主要实现凡是有三种,今天来介绍一下第一种方式SELECT【轮询机制】

二.SELECT【轮询机制】 

 1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,这是由历史原因造成的,因为以前网络不是这么发达并发量不高,大佬设计这个select的时候觉得1024已经够用了】。单纯的改变进程打开文件描述符并不能改变select监听的文件个数。如图(我是已经通过配置文件改过了,默认是1024)

大家看open files 字段,这就是进程能打开的 文件描述符上限

2.解决1024个数以下的客户端时,使用select是比较合适的,但连接客户多了【但只有一两个客户发送消息】,select采用轮询机制检测谁发送了消息,会大大降低服务器响应的效率,不应该在select上花太多的精力【但是我还是要给大家介绍一下】

三.select相关的操作函数

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

  • nfds【会变化的】:   监控文件描述符集里最大文件描述符+1, 因为此参数会告诉内核检测前多少个文件描述符的状态
  • readfds: 监控有读数据到文件描述符集合,传入传出参数。
  • writefds:监控写数据到达文件描述符集合,传入传出参数
  • exceptfds:  监控异常发生到达文件描述符集合,如外带数据到达异常,传入传出参数
  • timeout: 定时阻塞监控时间,3种情况,1.NULL永远等下去,2.设置timeval,等待固定时间,3.设置timeval里的时间均为0,检查描述字后立即返回,轮询
  • struct timeval{
  •     long tv_sec;   //  秒
  •     long tv_usec;  // 微秒
  • }

void FD_CLR(int fd, fd_set *set);   // 把文件描述符集合里的 fd清0

int   FD_ISSET(int fd,  fd_set* set);  // fd 是否在 文件描述符集合中

void  FD_SET(int fd, fd_set* set);   //  把 fd 添加到 文件描述符集合中

void FD_ZERO(fd_set * set);   // 把文件描述符集合全部清0

四.多路IO转接服务器-select【代码Demo】

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>



using namespace std;

#ifndef FD_SETSIZE
#define FD_SETSIZE 1024
#endif

#define SERVER_PORT 7777
#define LISTEN_NUM  	200
#define BUF_SIZE 1024


int
main(int argc, char*argv[])
{
	int i, j, n, maxi;
	int nready, client[FD_SETSIZE];
	int maxfd, listenfd, connfd, sockfd;
	struct sockaddr_in clie_addr, serv_addr;
	socklen_t clie_addr_len;
	fd_set r_set, all_set;
	char buf[BUF_SIZE], str[INET_ADDRSTRLEN];   // 16

	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	memset(&serv_addr, 0, sizeof(serv_addr));

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(SERVER_PORT);

	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));	
	bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));	
	listen(listenfd, LISTEN_NUM);

	maxfd = listenfd;  // 3
	maxi = -1;

	for(i=0; i<FD_SETSIZE; i++) client[i] = -1;  // 我们自己维护一个 client fd  数组
	FD_ZERO(&all_set);
	FD_SET(listenfd, &all_set);

	// printf("*******listen******\n");
	
	while(true){
		r_set = all_set; // 因为是传入传出参数所以要 赋值
		nready = select(maxfd+1, &r_set, NULL, NULL, NULL);
		if(nready < 0){
			perror("select err");
			exit(1);
		}

		if(FD_ISSET(listenfd, &r_set)){  // 有用户连接
			clie_addr_len = sizeof(clie_addr);
			connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
			printf("received from %s at port %d\n",
				inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
				ntohs(clie_addr.sin_port));

			for(i=0; i<FD_SETSIZE; i++){
				if(client[i] < 0){   // 找到下标最小的  没有被占用的
					client[i] = connfd;
					break;
				}
			}
			
			if(i == FD_SETSIZE){ // 用户已经达到 规定的上限了
				fputs("too many client\n", stderr);
				exit(1);
			}

			FD_SET(connfd, &all_set);  // 加入所有用户文件描述符集合的 
			if(connfd > maxfd)  // 设置好最大的 maxfd
				maxfd = connfd;
			
			if(i > maxi)   // 下标赋值
				maxi = i;

			if(--nready == 0)  // only has listen event
				continue;
	
		}

		for(i=0; i<=maxi; ++i){
			if((sockfd = client[i]) < 0)
				continue;

			if(FD_ISSET(sockfd, &r_set)){
				if((n = read(sockfd, buf, sizeof(buf) )) == 0){// 清理资源
					close(sockfd);
					FD_CLR(sockfd, &all_set);
					client[i] = -1;
				}else if(n > 0){
					for(j=0; j<n; j++)
						buf[j] = toupper(buf[j]);
					sleep(2);
					write(sockfd, buf, n);
				}

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

		}
	}
	
	close(listenfd);
	return 0;
}

效果:

SERVER

CLIENT

 原理图

猜你喜欢

转载自blog.csdn.net/qq_44065088/article/details/109237642
今日推荐