select函数

多路IO转接服务器

image

select函数参数

image

select相关函数

image

select实现多路IO转接服务器

/*************************************************************************
	> File Name: server.c
	> Author: shaozheming
	> Mail: 957510530@qq.com
	> Created Time: 2022年03月05日 星期六 10时48分07秒
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include <arpa/inet.h>
#include "wrap.h"
#define SERV_PORT 6666
int main(int argc, char* argv[])
{
	int i, j, n, nready;
	int maxfd = 0; //最大的文件描述符
	int listenfd, connfd;
	char buf[BUFSIZ];   //数据缓冲区
	struct sockaddr_in clie_addr, serv_addr;  //服务端和客户端socket
	socklen_t clie_addr_len;
	listenfd = Socket(AF_INET, SOCK_STREAM, 0); //设置tcp
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //设置端口复用
	bzero(&serv_addr, sizeof(serv_addr)); //清0
	serv_addr.sin_family = 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)); //bind绑定
	Listen(listenfd, 128); //设置监听数量
	fd_set rset, allset;  /* 读事件文件描述符集合,allset用来暂时存放 */
	maxfd = listenfd;//当socket之后,返回的是当前可用的文件描述符最小的值比如3, 3之后的就没人用了,最大就是3
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);   //将监听的文件描述符放到读文件集合中
	while(1) {
		rset = allset; //通过修改allset这个临时变量来修改rset
		//并且rset是传入传出,参数,每次出来都会被修改,所以不能再下面直接修改rset

		nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //阻塞监听
		if(nready < 0)
			perr_exit("select error!\r\n");
		if(FD_ISSET(listenfd, &rset)) { //说明有新的客户端连接请求
			/* 建立客户端连接 */
			clie_addr_len = sizeof(clie_addr);
			connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); //connfd通信
			FD_SET(connfd, &allset);    //因为新建立客户端了,需要再次添加到监听集合里
			if(maxfd < connfd) maxfd = connfd;
			if(0 == --nready) 
				continue; //当减少到0了,也就是nready是1,说明只有listenfd有响应,无客户端响应,直接回到while
		}
		for(i = listenfd + 1; i <= maxfd; ++i) {
			/* 检测哪个客户端有数据 */
			if(FD_ISSET(i, &rset)) {
				if((n = Read(i, buf, sizeof(buf))) == 0) {
				//当服务器关闭链接时
					Close(i);   
					FD_CLR(i, &allset); //解除监控
				} else if(n > 0) {
					for(j = 0; j < n; ++j) {
						buf[j] = toupper(buf[j]);
					}
					Write(i, buf, n);
				}
			}
		}
	}
	Close(listenfd);
    return 0;
}

select 优缺点

缺点: 监听上限受文件描述符限制,最大1024
检测满足条件的fd,自己添加业务逻辑提高小,提高了编码难度
优点:唯一一个可以跨平台

自定义数组提升效率

/*************************************************************************
	> File Name: server.c
	> Author: shaozheming
	> Mail: 957510530@qq.com
	> Created Time: 2022年03月05日 星期六 10时48分07秒
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include <arpa/inet.h>
#include "wrap.h"

#define INET_ADDRSTRLEN 16
#define SERV_PORT 6666

int main(int argc, char* argv[])
{
	int i, j, n, nready;
	int maxi, client[FD_SETSIZE];   //自定义数组,防止遍历1024个文件描述符

	int maxfd = 0; //最大的文件描述符
	int listenfd, connfd, sockfd;

	char buf[BUFSIZ], str[INET_ADDRSTRLEN];   //数据缓冲区

	struct sockaddr_in clie_addr, serv_addr;  //服务端和客户端socket
	socklen_t clie_addr_len;
	listenfd = Socket(AF_INET, SOCK_STREAM, 0); //设置tcp

	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //设置端口复用

	bzero(&serv_addr, sizeof(serv_addr)); //清0
	serv_addr.sin_family = 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)); //bind绑定

	Listen(listenfd, 128); //设置监听数量
	fd_set rset, allset;  /* 读事件文件描述符集合,allset用来暂时存放 */

	maxfd = listenfd;//当socket之后,返回的是当前可用的文件描述符最小的值比如3, 3之后的就没人用了,最大就是3

	maxi = -1;
	for(i = 0; i < FD_SETSIZE; ++i)
		client[i] = -1;

	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);   //将监听的文件描述符放到读文件集合中
	while(1) {
		rset = allset; //通过修改allset这个临时变量来修改rset
		//并且rset是传入传出,参数,每次出来都会被修改,所以不能再下面直接修改rset

		nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //阻塞监听
		if(nready < 0)
			perr_exit("select error!\r\n");
		if(FD_ISSET(listenfd, &rset)) { //说明有新的客户端连接请求
			/* 建立客户端连接 */
			clie_addr_len = sizeof(clie_addr);
			connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); //connfd通信

			printf("received from %s at PORT %d \n", 
					inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)), 
					ntohs(clie_addr.sin_port));
			
			/* 找client中没有使用的位置 */
			for(i = 0; i < FD_SETSIZE; ++i) { 
				if (client[i] < 0) {
					client[i] = connfd;
					break;
				}
			}
			
			/* 如果达到了监听上限 */
			if(i == FD_SETSIZE) {
				fputs("too many clients\n", stderr);
				exit(1);
			}

			FD_SET(connfd, &allset);    //因为新建立客户端了,需要再次添加到监听集合里

			if(maxfd < connfd) maxfd = connfd;
			/* 保证maxi存的是client[]最后一个下标 */
			if(i > maxi) maxi = i;
			if(0 == --nready) 
				continue; //当减少到0了,也就是nready是1,说明只有listenfd有响应,无客户端响应,直接回到while
		}
		for(i = 0; i <= maxi; ++i) {

			if((sockfd = client[i]) < 0)
				continue;
			/* 检测哪个客户端有数据 */
			if(FD_ISSET(sockfd, &rset)) {

				if((n = Read(sockfd, buf, sizeof(buf))) == 0) {
				//当服务器关闭链接时
					Close(sockfd);   
					FD_CLR(sockfd, &allset); //解除监控
					client[i] = -1;
				} else if(n > 0) {
					for(j = 0; j < n; ++j) {
						buf[j] = toupper(buf[j]);
					}
					Write(sockfd, buf, n);
					Write(STDOUT_FILENO, buf, n);
				}
				if(--nready == 0) break;
			}
		}
	}
	Close(listenfd);
    return 0;
}


posted @   蘑菇王国大聪明  阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示