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;
}
相关推荐
Hrain-AI4 小时前
多 Agent 并行不打架:worktree 隔离与反馈回流落地(附脚本)
网络·数据库·人工智能·架构
云运维笔记4 小时前
IP地址从入门到精通:网络通信的核心基石
运维·网络·计算机网络
Koi慢热4 小时前
DayDayMap学术社区体验:卫星网络测绘专题用下来怎么样
网络·人工智能·windows·web安全·网络安全
万联WANFLOW5 小时前
从“连接网络”到“编排业务”:企业网络架构正在发生什么变化?
网络·人工智能
bosins5 小时前
WSL2 启动即崩溃?调整超时参数解决 Docker 阻塞问题
linux·docker·wsl
web守墓人5 小时前
【goed/ui】自定义组件设计思想篇
linux·windows·ui·golang
kuroomi6 小时前
Ingress-Nginx与kubernetes 网络
linux·运维·网络·kubernetes
IpdataCloud6 小时前
AI智能体调用工具怎么核验来源IP?归属地、网络类型与代理风险识别(含Python代码)
数据库·python·tcp/ip
daemon.qiang6 小时前
国内虚拟机对接 Freedesktop:Fork xserver、自建 Runner 与提交 MR 实战
linux·ubuntu·centos·gitlab·开源软件
Yang96116 小时前
鼎讯101C测向天线200MHz至500MHz比幅测向解读
网络