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;
}
相关推荐
LaoZhangGong1231 分钟前
学习TCP/IP的第7步:设计TCPIP程序要注意的事项
网络协议·学习·tcp/ip·以太网
板面华仔1 分钟前
Linux基础(下)——工作中常用命令总结
linux·运维·服务器
刃神太酷啦3 分钟前
Linux 基础 IO 收官:库的构建与使用、进程地址空间及核心知识点全解----《Hello Linux!》(11)
java·linux·c语言·数据库·c++·算法·php
RisunJan4 分钟前
Linux命令-let(执行算术运算)
linux·服务器
开开心心就好13 分钟前
视频伪装软件,.vsec格式批量伪装播放专用
java·linux·开发语言·网络·python·电脑·php
极安代理18 分钟前
HTTP代理,什么是HTTP代理,HTTP代理的用途有哪些?
网络·网络协议·http
咕咕嘎嘎102422 分钟前
传输层协议UDP和TCP
网络协议·tcp/ip·udp
济61723 分钟前
linux 系统移植(第十九期)---- BusyBox 构建根文件系统---- Ubuntu20.04
linux·运维·服务器
阿钱真强道25 分钟前
基于openssl的sm4加密,加密数据,验证OK
linux·网络协议·网络安全
无名修道院26 分钟前
AI大模型应用开发-Linux 入门
linux·运维·人工智能·ai大模型应用开发