TCP select 轮询服务器

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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;
}


你可能感兴趣的:(linux,网络,tcp/ip)