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;
}
相关推荐
草莓熊Lotso1 分钟前
Linux 线程同步与互斥(一):彻底搞懂线程互斥原理、互斥量底层实现与 RAII 封装
linux·运维·服务器·开发语言·数据库·c++
feng_you_ying_li3 分钟前
linux之进程概念
linux
j_xxx404_4 分钟前
深入理解Linux底层存储:从物理磁盘架构到文件系统(inode/Block)原理
linux·运维·服务器·后端
嵌入式×边缘AI:打怪升级日志10 分钟前
深度剖析Linux按键驱动四种访问方式:从查询到异步通知
linux·运维·服务器
凉、介13 分钟前
从设备树到驱动源码:揭秘嵌入式 Linux 中 MMC 子系统的统一与差异
linux·驱动开发·笔记·学习·嵌入式·sd·emmc
小江的记录本14 分钟前
【网络安全】《网络安全与数据安全核心知识体系》(包括数据脱敏、数据加密、隐私合规、等保2.0)
java·网络·后端·python·算法·安全·web安全
@insist12315 分钟前
网络工程师-动态路由协议(二):BGP 协议与路由引入技术详解
运维·网络·网络工程师·软考·软件水平考试
今天又在写代码18 分钟前
计算机网络v2
网络·计算机网络
Full Stack Developme19 分钟前
Linux 软连接与硬连接比较
linux·运维·服务器
云边有个稻草人22 分钟前
【Linux系统】第九节—进程状态续集+进程优先级+进程切换
linux·进程状态·进程优先级·linux进程调度算法·linux进程切换·死循环进程如何运行·pri and ni