Linux网络编程——多路IO转接服务器设计


select多路IO转接:

  • 原理:借助内核,select来监听,客户端连接、数据通信事件。

     void FD_ZERO(fd_set *set);  ---清空一个文件描述符集合
     		fd_set rset;
     		FD_ZERO(&rset);
     void FD_SET(int fd, fd_set *set);    ---将待监听的文件描述符,添加到监听集合中
     		FD_SET(3,&rset);
     		FD_SET(5,&rset) ;
     		FD_SET(6,&rset);
     void FD_CLR(int fd, fd_set *set);    --将一个文件描述符从监听集合中移除。
     		FD_CLR(4,&rset); 
     int FD_ISSET(int fd, fd_set *set);    ---判断一个文件描述符是否在监听集合中。
     		返回值:在:1;不在。0;
     		FD_ISSET (4,&rset) ;
    
  • int select(int nfds,fd_set *readfds,fd_set *writefds, fd_set *exceptfds,struct timeva1 *timeout);

    nfds:监听的所有文件描述符中,最大文件描述符+1
    readfds: 读文件描述符监听集合。传入、传出参数
    writefds: 写文件描述符监听集合。传入、传出参数 NULL
    exceptfds: 异常文件描述符监听集合。传入、传出参数NULL
    timeout: >0:设置监听超时时长。NULL:阻塞监听。0:非阻塞监听,轮询
    返回值:>0:所有监听集合(3个)中,满足对应事件的总数。0:没有满足监听条件的文件描述符。-1:errnol

思路分析

lfd = socket() ;			创建套接字
bind() ;					绑定地址结构
listen();					设置监听上限
fd_set rset,allset;		创建r监听集合
FD_ZERO(&allset);			将r监听集合清空
FD_SET(lfd, &allset);		将lfd 添加至读集合中。
while (1){
	rset = a11set;			保存监听集合
	ret = select(lfd+1,&rset,NULL,NULL,NULL);		监听文件描述符集合对应事件。
	if (ret > 0){				有监听的描述符满足对应事件
		if (FD_ISSET(lfd,&rset)){			// 1在。0不在。
			cfd = accept () ﹔					建立连接,返回用于通信的文件描述符
			FD_SET(cfd,,&allset);			添加到监听通信描述符集合中。
		}
		for (i = lfd+1;i <=最大文件描述符; i++){
			FD_ISSET(i,&allset)				有read、write事件
			read ()
			小-大
			write();
		}
	}
}

代码实现

服务端:

#define SERV_PORT 6666
int main(int argc,char *argv[]){
    
    
	int i, j,n,nready;
	int maxfd = 0;
	int listenfd,connfd;
	char buf[BUFSIZ];		/*#define INET_ADDRSTRLEN 16*/
	struct sockaddr_in clie_addr , serv_addr;
	socklen_t clie_addr_len;
	listenfd = Socket(AF_INET,SOCK_STREAM,0);int opt = 1;
	setsockopt(listenfd,soL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	bzero(&serv_addr , sizeof(serv_addr));
	serv_addr.sin_fanily= AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port= htons(SERV_PORT);
	Bind(listenfd,(struct sockaddr * )&serv_addr , sizeof(serv_addr));
	Listen(listenfd,128);
	fd_set rset,allset; 		/*set 读事件文件描述符集合allset用来暂存*/
	maxfd = listenfd;
	FD_ZERO(&allset);
	FD_SET(listenfd,&allset);	/*构造select监控文件描述符集*/
	while (1){
    
    
		rset = allset;			/*每次循环时都从新设置select监控信号集*/
		nready = select(naxfd+1&rset,NULL,NULL,NULL);
		if (nready < 0)
			perr_exit( "select error");
		if (FD_ISSET(listenfd,&rset)) {
    
    		/*说明有新的客户端链接请求*/
			clie_addr_len = sizeof(clie_addr);
			connfd = Accept(listenfd,(struct sockaddr * )&clie_addr,&clie_addr_len);			/* Accept不会阻塞*/
			FD_SET(connfd,&allset);		/*向监控文件描述符集合allset添加新的文件描述符connfd*/
			if (naxfd < connfd)
				maxfd = connfd;
			if (0 == --nready)			/*只有listenfd有事件,后续的for不需执行*/
				continue;
		}
		for (i = listenfd+1; i <= naxfd; i++) {
    
    		/*检测哪个clients有数据就绪*/
			if (FD_ISSET(i,&rset)) {
    
    
				if ((n = Read(i,buf,sizeof(buf))) == 0)		/*当client关闭链接时,服务器端也关闭对应链接*/
				close(i);
				FD_CLR(i, &allset);					/*解除select对此文件描述符的监控*/
			}else if (n > 0) {
    
    
				for (j = 0; j <n; j++)
					buf[j] = toupper(buf[j]);write(i, buf, n);
				}
			}
		}
	}
	return 0;
}	

客户端:

int main(int argc,char *argv[]){
    
    
	struct sockaddr_in servaddr;char buf[MAXLINE];
	int sockfd,n;
	sockfd = Socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr ,sizeof(servaddr ));
	servaddr.sin_fanily = AF_INET;
	inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr.s_addr);
	servaddr.sin_port = htons(SERV_PORT);
	Connect(sockfd,(struct sockaddr *)&servaddr ,sizeof(servaddr));
	printf("---------- --connect ok----------------\n");
	while (fgets(buf,MAXLINE,stdin) != NULL){
    
    
		write(sockfd,buf, strlen(buf));
		n = Read(sockfd,buf,MAXLINE);
		if (n == 0) {
    
    
			printf( "the other side has been closed.\n");
			break;
		}
		else
			write(STDOUT_FILENO,buf,n);
	}
	close(sockfd);
	return 0;
}

select优缺点:

缺点:
监听上限受文件描述符限制。最大1024.
检测满足条件的fd,自己添加业务逻辑提高小。提高了编码难度。

优点:
跨平台。win、linux、macOS、Unix、类Unix、mips

猜你喜欢

转载自blog.csdn.net/Strive_LiJiaLe/article/details/128715487