使用select实现服务器并发

select函数介绍:

select 函数是一个用于在一组文件描述符上进行异步I/O多路复用的系统调用。它可以同时监视多个文件描述符,等待其中任何一个文件描述符准备就绪,然后进行相应的操作。

以下是select函数的原型:

cs 复制代码
#include <sys/select.h>

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

参数:

nfds:待监视的最大文件描述符加1。

readfds:指向一个可读文件描述符集合的指针,用于指定要监视读事件的文件描述符。

writefds:指向一个可写文件描述符集合的指针,用于指定要监视写事件的文件描述符。

exceptfds:指向一个异常文件描述符集合的指针,用于指定要监视异常事件的文件描述符。

timeout:指向一个表示超时时间的结构体指针,用于设置select的超时时间。如果为NULL,select将一直阻塞,直到有事件发生。

返回值:

如果超时时间内有文件描述符就绪或有错误发生,select函数返回就绪文件描述符的总数。

如果超时时间到达而没有文件描述符就绪,select函数返回0。

如果出现错误,select函数返回-1,并设置errno来指示具体的错误类型。

select函数主要用于实现多路复用的I/O操作,它允许同时监视多个文件描述符,以避免使用阻塞式I/O时每个文件描述符都需要单独的线程。通过select函数,可以有效地管理并发连接、处理I/O事件和提高系统性能。

服务器代码:

cs 复制代码
#include <myhead.h>

#define PORT 9999               //端口号
#define IP "192.168.125.39"     //本机IP

int main(int argc, const char *argv[])
{


	//创建流式套接字 socket
	int sfd=socket(AF_INET,SOCK_STREAM,0);
	if(sfd<0){
		ERR_MSG("socket create");
		return -1;
	}


	//允许端口快速的被复用
	int reuse = 1;                                                            
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
	{
		ERR_MSG("setsockopt");
		return -1;
	}
	printf("允许端口快速的被复用成功\n");


	//填充地址信息结构体给bind函数绑定
	//真实的地址信息结构体根据地址族指定 AF_INET:man 7 ip
	struct sockaddr_in sin;
	sin.sin_family = AF_INET; //必须填AF_INET
	sin.sin_port = htons(PORT); //端口号的网络字节序
	sin.sin_addr.s_addr = inet_addr(IP);//本机ip


	//绑定服务器的地址信息 必须绑定bind
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0){
		ERR_MSG("bind");
		return -1;
	}

	//将套接字设置为被动监听状态listen
	if(listen(sfd,128)<0){
		ERR_MSG("listen");
		return -1;
	}

	//创建一个读集合
	fd_set readfds,tempfds;
	//fd_set 本质上是一个结构体,结构体中有一个整形数组
	//清空集合,存在随机值,可能会随机到有用的但是不需要监测的文件描述符
	//清空集合
	FD_ZERO(&readfds);

	//将要监测的文件描述符添加到集合中
	FD_SET(0,&readfds);
	FD_SET(sfd,&readfds);

	int maxfd = sfd;      //储存最大文件描述符

	char buff[128] = "";
	int s_res = -1;
	char buf[128] = "";
	ssize_t res = -1;
	struct sockaddr_in saveCin[1024];   //备份连接成功的客户端的地址信息,用下标对应文件描述符



	while(1){

		tempfds = readfds;
		//执行IO多路复用函数
		s_res = select(maxfd+1,&tempfds,NULL,NULL,NULL);
		if(s_res <0){

			ERR_MSG("select");
			return -1;
		}
		else if(s_res == 0){

			printf("time out,, \n");
			break;
		}
		//此时集合中会只剩下产生事件的文件描述符
		//只需要判断集合中剩下哪个文件描述符,走对应处理函数


		for(int i=0;i<=maxfd;i++ ){

			if(FD_ISSET(i,&tempfds)==0)
				continue;
			//能运行到这里,说明i代表的文件描述符在集合中

			if(0 == i){

				int sendfd = -1;
				printf("触发键盘事件\n");
				bzero(buff,sizeof(buff));
				int res=scanf("%d %s",&sendfd,buff);
				while(getchar()!=10);
				if(2!=res){
					printf("输入格式错误\n");
					return -1;
				}

				if(2>=sendfd || FD_ISSET(sendfd,&readfds)==0){
					printf("输入有误\n");
					return -1;
				}

				if(send(sendfd,buff,sizeof(buff),0)<0){
					ERR_MSG("send");
					return -1;
				}

			}

			else if(sfd == i){

				printf("触发客户端连接事件\n");
				//连接到客户端
				//获取一个已经完成的客户端信息,生成一个新的文件描述符

				int newfd = -1;
				struct sockaddr_in cin;//存储客户端的地址信息
				socklen_t addrlen = sizeof(cin);//真实的结构体信息地址大小

				newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
				if(newfd<0){
					ERR_MSG("newfd");
					return -1;
				}

				printf("[%s %d]:%d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
				saveCin[newfd] = cin;

				//将newfd添加到集合中
				FD_SET(newfd,&readfds);
				//更新最大文件描述符
				maxfd = maxfd>newfd?maxfd:newfd;
			}

			else{

				printf("触发客户端交互事件\n");
				bzero(buf,sizeof(buf));
				res = recv(i,buf,sizeof(buf),0);
				
				if(res<0){
					ERR_MSG("recv");
					return -1;
				}
				if(0 == res){
					printf("[%s %d]客户端下线:%d",inet_ntoa(saveCin[i].sin_addr),ntohs(saveCin[i].sin_port),i);

					//关闭文件描述符	
					close(i);
					//剔除文件描述符
					FD_CLR(i, &readfds);

					while(!FD_ISSET(maxfd, &readfds)&& (maxfd--) > 0);

					continue;
				}
				printf("[%s %d]%d:%s\n",inet_ntoa(saveCin[i].sin_addr),ntohs(saveCin[i].sin_port),i,buf);

			
			}
		}
	}

	//关闭
	if(close(sfd)<0){
		ERR_MSG("close");
		return -1;
	}
	return 0;
}
相关推荐
张国荣家的弟弟6 分钟前
为何在VMware中清理CentOS虚拟机后,本地磁盘空间未减少的问题解决
linux·运维·centos
甘北18 分钟前
docker commit除了提交容器成镜像,还能搞什么之修改cmd命令
linux·运维·docker
努力学习的小廉1 小时前
深入了解linux系统—— 操作系统的路径缓冲与链接机制
android·linux·服务器
蠢货爱好者1 小时前
Linux中的nfs网络文件系统
linux·服务器·网络
酥暮沐2 小时前
Linux的启动流程
linux·服务器·网络·启动流程
清寒敲代码3 小时前
LVS集群的基本原理和相关配置
运维·服务器·lvs
大大小小聪明3 小时前
Nginx中root与alias的区别及用法
服务器·网络·nginx
Tom Boom3 小时前
23. 装饰器应用之测试用例的依赖实现
服务器·网络·测试开发·测试用例·自动化测试框架开发·wraps
ivwdcwso4 小时前
构建安全与合规的Jenkins环境:全周期审计方案详解
运维·安全·ci/cd·jenkins·devops·审计
LitchiCheng4 小时前
RISC-V 开发板 MUSE Pi Pro USB 测试(3.0 U盘,2.0 UVC摄像头)
linux·运维·risc-v