select完成服务器并发

服务器:

cpp 复制代码
#include <myhead.h>
 
#define PORT 4399 	//端口号
#define IP "192.168.0.191"//IP地址
 
//键盘输入事件
int keybord_events(fd_set readfds);
//客户端交互事件
int cliRcvSnd_events(int , struct sockaddr_in*, fd_set *, int *);
//客户端连接事件
int cliConnect_events(int , struct sockaddr_in*, fd_set *, int *);
 
int main(int argc, const char *argv[])
{
	//创建流式套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket create success sfd=%d\n", sfd);
 
	//允许端口被快速复用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
	{
		ERR_MSG("setsockopt");
		return -1;
	}
	//填充地址信息结构体
	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
 
	//绑定服务器的地址信息
	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("bind success\n");
	//将套接字设置为被动监听状态
	if(listen(sfd, 128) < 0)
	{
		ERR_MSG("listen");
		return -1;
	}
	printf("listen success\n");
	//创建一个读集合,一个操作集合
	fd_set readfds, tempfds;
 
	//清空集合
	FD_ZERO(&readfds);
 
	//将需要监测的文件描述符加入读集合
	FD_SET(0, &readfds);
	FD_SET(sfd, &readfds);
 
	int maxfd = sfd; 	//存储最大的文件描述符
	int s_res = -1;
	ssize_t res = -1;
	char buf[128] = "";
	struct sockaddr_in saveCin[1024]; 	//备份连接成功的客户端的地址信息,且用下标对应文件描述符
 
	while(1)
	{
		tempfds = readfds;
		//执行多路复用函数
		s_res = select(maxfd+1, &tempfds, NULL, NULL, NULL);
		if(s_res < 0)
		{
			ERR_MSG("select");
			return -1;
		}
		else if(0 == s_res)
		{
			printf("time out...\n");
			break;
		}
 
 
		//此时代表select函数解除阻塞,集合中有文件描述符存在
		for(int i=0;i<=maxfd;i++)
		{
			if(FD_ISSET(i, &tempfds) == 0)
 
				continue;
 
			//此时代表i所对应的文件描述符在集合中
			if(0 == i)
			{
				keybord_events(readfds);
			}
			else if(sfd == i)//代表sfd在集合中
			{
				//	printf("触发客户端连接事件\n");
				cliConnect_events(sfd, saveCin, &readfds, &maxfd);
			}
			else
			{
				//	printf("触发客户端交互事件\n");
				cliRcvSnd_events(i, saveCin, &readfds, &maxfd);
			}
		}
 
	}
 
	if(close(sfd) < 0)
	{
		ERR_MSG("close");
		return -1;
	}
 
 
 
	return 0;
}
 
//键盘输入事件
int keybord_events(fd_set readfds)
{
	char buf[128] = "";
	int sndfd = -1;
	bzero(buf, sizeof(buf));
 
	int res = scanf("%d %s", &sndfd, buf);
	while(getchar() != 10);
	if(res != 2)
	{
		printf("输入数据的格式错误 :fd string\n");
		return -1;
	}
 
	if(sndfd<=2 || FD_ISSET(sndfd, &readfds) == 0)
	{
		printf("非法的文件描述符:sndfd=%d\n", sndfd);
		return -1;
	}
 
	if(send(sndfd, buf, sizeof(buf), 0) < 0)
	{
		ERR_MSG("send");
		return -1;
	}
 
	printf("send success\n");
 
	return 0;
}
 
//客户端连接事件
int cliConnect_events(int sfd, struct sockaddr_in saveCin[], fd_set *preadfds, int *pmaxfd)
{
	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]客户端连接成功 newfd=%d\n",\
			inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);
	saveCin[newfd] = cin; 	//将cin另存到newfd对应的下表位置
	FD_SET(newfd, preadfds); //将newfd添加到集合中
	*pmaxfd = *pmaxfd>newfd ? *pmaxfd:newfd; 	//更新maxfd
 
	return 0;
}
 
//客户端交互事件
int cliRcvSnd_events(int fd, struct sockaddr_in* saveCin, fd_set *preadfds, int *pmaxfd)
{
	char buf[128] = "";
	//清空字符串
	bzero(buf, sizeof(buf));
 
	//接收
	ssize_t res = recv(fd, buf, sizeof(buf), 0);
	if(res < 0)
	{
		ERR_MSG("recv");
		return -1;
	}
	else if(0 == res)
	{
		printf("[%s:%d]客户端下线 newfd=%d\n",\
				inet_ntoa(saveCin[fd].sin_addr), ntohs(saveCin[fd].sin_port), fd);
		close(fd);//关闭文件描述符
		FD_CLR(fd, preadfds); //将文件描述符从集合中删除
 
		//由于删除的文件描述符可能是最大的文件描述符,所以要更新maxfd
		while(FD_ISSET(*pmaxfd, preadfds) == 0 && (*pmaxfd)-- >= 0);
		return 0;
	}
	printf("[%s:%d] newfd=%d:%s\n",\
			inet_ntoa(saveCin[fd].sin_addr), ntohs(saveCin[fd].sin_port), fd, buf);
 
	//发送信息
	strcat(buf, "*_*");
	if(send(fd, buf, sizeof(buf), 0) < 0)
	{
		ERR_MSG("send");
		return -1;
	}
	printf("send success\n");
	return 0;
}

客户端:

cpp 复制代码
#include<myhead.h>
 
#define PORT 4399             //服务器绑定的端口号
#define IP  "192.168.0.191"   //服务器绑定的IP
 
 
int main(int argc, const char *argv[])
{
	//创建流式套接字 socket
	int cfd = socket(AF_INET, SOCK_STREAM, 0);
	if(cfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket create success cfd=%d\n", cfd);
 
	//绑定客户端的地址信息---》非必须绑定
	//当不手动绑定的时候,操作系统会自动给客户端绑定本机IP和随机端口。   
 
 
	//填充服务器的地址信息结构体给connect函数连接,
	//想连接哪个服务器,就填哪个服务器绑定的地址信息
	//真实的地址信息结构体根据地址族指定 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
 
	//连接指定服务器 connect
	if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("connect");
		return -1;
	}
	printf("connect success\n");
 
 
	//定义要监测的集合
	fd_set readfds,tempfds;
 
	FD_ZERO(&readfds);
 
	//将需要的文件描述符加入集合
	FD_SET(0,&readfds);
	FD_SET(cfd,&readfds);
 
	int s_res = -1;
 
	char buf[128] = "";
	ssize_t res = 0;
 
	while(1)
	{
		tempfds=readfds;
		s_res = select(cfd+1, &tempfds, NULL, NULL, NULL);
		if(s_res < 0)
		{
			ERR_MSG("selsct");	
			return -1;
		}
		else if(0 == s_res)
		{
			printf("time out....\n");
			break;
		}
		//运行到此,则代表集合中有文件描述符准备就绪
 
		if(FD_ISSET(0, &tempfds))
		{
			//清空字符串
			bzero(buf, sizeof(buf));    
 
			fgets(buf, sizeof(buf), stdin);
			buf[strlen(buf)-1] = 0;
 
			//发送
			if(send(cfd, buf, sizeof(buf), 0) < 0)
			{
				ERR_MSG("send");
				return -1;
			}
			printf("send success\n");
		}
 
		if(FD_ISSET(cfd, &tempfds))
		{
			bzero(buf, sizeof(buf));    //memset
			//接收
			res = recv(cfd, buf, sizeof(buf), 0);
			if(res < 0)
			{
				ERR_MSG("recv");
				return -1;
			}
			else if(0 == res)
			{
				printf("服务器下线 cfd=%d\n", cfd);
				break;
			}
			printf("cfd=%d : %s\n", cfd, buf);
 
		}
	}
 
 
	//关闭文件名描述符
	if(close(cfd) < 0)
	{
		ERR_MSG("close");
		return -1;
	}
 
	return 0;
}

效果:

相关推荐
YuMiao12 小时前
gstatic连接问题导致Google Gemini / Studio页面乱码或图标缺失问题
服务器·网络协议
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
Sinclair3 天前
简单几步,安卓手机秒变服务器,安装 CMS 程序
android·服务器
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
Rockbean4 天前
用40行代码搭建自己的无服务器OCR
服务器·python·deepseek
茶杯梦轩4 天前
CompletableFuture 在 项目实战 中 创建异步任务 的核心优势及使用场景
服务器·后端·面试