TCP select 轮询服务器

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>

#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s);


/*
   函数功能:创建一个服务端与客户端通信的套接字
   函数参数:下一个空余的文件描述符
   函数返回值:成功:用于与新客户端通信的套接字  失败:-1
   */
int server_handler(int server, struct sockaddr_in saveCaddr[])
{
	struct sockaddr_in addr = {0};
	socklen_t asize = sizeof(addr);
	int ret = -1;

	if((ret = accept(server, (struct sockaddr*)&addr, &asize)) == -1)
	{
		LOG("accept error");
		perror("accept error");
		return -1;
	}
	printf("[%s/%d] client%d 已连接\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), ret);

	saveCaddr[ret] = addr;

	return ret;
}

/*
   函数功能:接收客户端发送的数据
   函数参数:客户端的文件描述符
   函数返回值:成功:返回成功读取到的字节数  失败:-1
   */
int client_handler(int client, struct sockaddr_in* saveCaddr)
{
	char buf[128] = {0};
	bzero(buf, sizeof(buf));
	//sizeof(buf)-1为了给字符串补'\0'需要留一个位置
	int ret = read(client, buf, sizeof(buf)-1);

	if(ret == 0)
	{
		printf("[%s/%d] client%d 已下线\n", inet_ntoa(saveCaddr[client].sin_addr), ntohs(saveCaddr[client].sin_port), client);

		ret = -1;
	}
	else if(ret > 0)
	{
		//read不会自动补0,需要在结尾处给字符串补'\0'
		buf[ret] = 0;

		printf("[%s/%d] client%d:%s\n", inet_ntoa(saveCaddr[client].sin_addr), ntohs(saveCaddr[client].sin_port), client, buf);
		//客户端输入quit则断开与服务器连接
		if(strcmp(buf, "quit") != 0)
		{
			//输出读取到的数据
			ret = write(client, buf, ret);
		}
		else
		{
			printf("[%s/%d] client%d 已下线\n", inet_ntoa(saveCaddr[client].sin_addr), ntohs(saveCaddr[client].sin_port), client);
			ret = -1;
		}
	}

	return ret;
}

int main(char argc, char* argv[])
{
	//申请服务器资源
	int server = 0;
	struct sockaddr_in saddr = {0};
	//记录文件描述符的范围
	int max = 0;
	//接收select函数返回值
	int num = 0;
	//标记感兴趣的文件描述符
	fd_set reads = {0};
	fd_set temps = {0};

	//申请服务端套接字
	server = socket(PF_INET, SOCK_STREAM, 0);
	if(server == -1)
	{
		puts("server socket error");
		return -1;
	}

	int reuse = 1;
	if(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
	{
		LOG("setsockopt");
		return -1;
	}
	//使用IPv4连接,需要与套接字的第一个参数对应
	saddr.sin_family = AF_INET;
	//INADDR_ANY->"0.0.0.0"接受所有IP地址连接
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	//监听端口号
	saddr.sin_port = htons(8899);

	//绑定端口
	if(bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1)
	{
		puts("server bind error");
		return -1;
	}

	//监听端口,队列为5
	if(listen(server, 5) == -1)
	{
		puts("server listen error");
		return -1;
	}

	puts("server start success");

	//将reads的所有位置置0
	FD_ZERO(&reads);
	//监听reads[0,max]号位置的事件
	FD_SET(server, &reads);
	FD_SET(0, &reads);

	//更新max,将最新一个空余的文件符记录
	max = server;

	//备份连接成功的客户端地址信息
	struct sockaddr_in saveCaddr[1024];

	while(1)
	{
		//selet会修改参数内容,所以需要拷贝reads的内容到temps去使用
		temps = reads;

		//对[0,max]个文件描述符的读事件进行轮询
		num = select(max+1, &temps, NULL, NULL, NULL);

		if(num > 0)
		{
			for(int i = 0; i <= max; i++)
			{
				if(FD_ISSET(i, &temps) == 0)
					continue;
				//判断是否有新的客户端连接
				if(0 == i)
				{
					puts("键盘事件");
					char buf[128] = "";
					bzero(buf, sizeof(buf));
					fgets(buf, sizeof(buf), stdin);
					buf[strlen(buf) - 1] = 0;
					printf("input: %s\n",buf);

				}
				else if(server == i)
				{
					puts("连接事件");
					//服务端创建一个用于与客户端通信的套接字
					int client = server_handler(server, saveCaddr);
					if(client > -1)
					{
						//将该套接字放入监听队列
						FD_SET(client, &reads);
						//判断最大文件描述符是否更新
						max = (client > max)? client : max;

					}
				}
				//判断是否存在文件描述符
				else
				{
					puts("通信事件");
					int r = client_handler(i, saveCaddr);
					if(r == -1)
					{
						FD_CLR(i, &reads);
						close(i);
						while(FD_ISSET(max, &reads) == 0 && max-- >= 0);
					}
				}
			}
		}
	}

	close(server);

	return 0;
}
相关推荐
ZZZCY200311 分钟前
路由策略与路由控制实验
前端·网络
爱上语文25 分钟前
Http 请求协议
网络·后端·网络协议·http
夏沫の浅雨30 分钟前
imx-6ULL uboot 移植
linux·uboot·nxp·6ull
C++忠实粉丝38 分钟前
计算机网络之应用层协议HTTP
linux·网络·c++·网络协议·tcp/ip·计算机网络·http
Lin_Aries_042140 分钟前
CentOS 7 安装部署 KVM
linux·运维·centos
JosieBook1 小时前
【面试题】2025年百度校招Java后端面试题
java·开发语言·网络·百度
北京搜维尔科技有限公司1 小时前
搜维尔科技:通过Touch力反馈主手实时通讯机械臂,进行远程操作
网络·科技
身如柳絮随风扬无论云泥意贯一1 小时前
计算机网络 实验七 NAT配置实验
服务器·网络·计算机网络·智能路由器
深思慎考1 小时前
计算机操作系统——进程控制(Linux)
linux·服务器·c++·c
海阔天空_20131 小时前
浏览器自动化库playwright简介
网络·python·自动化