hwk2:select实现服务器并发

selectTcpSer.c:

#include 

#define ERR_MSG(msg) do{\
	fprintf(stderr,"__%d__\n",__LINE__);\
	perror(msg);\
}while(0)

#define PORT 8888

int keybroad_event(fd_set readfds)
{
	char buf[128] = "";
	int sendfd = -1;
	bzero(buf,sizeof(buf));

	int res = scanf("%d %s",&sendfd,buf);
	while(getchar()!=10);
	if(res != 2)
	{
		printf("input error,usage:sendfd string\n");
		return -1;
	}

	if(sendfd <=2 || FD_ISSET(sendfd,&readfds) == 0)
	{
		printf("非法的文件描述符\n");
		return -1;
	}

	if(send(sendfd,buf,sizeof(buf),0) < 0)
	{
		ERR_MSG("send");
		return -1;
	}
	printf("send success\n");

	return 0;
}
int cliconnect_event(int sfd,struct sockaddr_in cliaddrs[],fd_set *preadfds,int *pmaxfd)
{
	int newfd = -1; 	//客户端文件描述符
	struct sockaddr_in cin; 	//用来存储客户端地址信息
	socklen_t addrlen = sizeof(cin);

	newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
	if(newfd < 0)
	{
		ERR_MSG("accept");
		return -1;
	}
	printf("[%s:%d]客户端连接成功,newfd = %d\n",\
			inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);

	cliaddrs[newfd]=cin; 	//将客户端地址信息保存到数组中

	FD_SET(newfd,preadfds); 	//将文件描述符添加到集合中

	*pmaxfd = *pmaxfd > newfd ? *pmaxfd:newfd; 	//更新maxfd

	return 0;
}
int clichange_event(int fd,struct sockaddr_in cliaddrs[],fd_set *preadfds,int *pmaxfd)
{
	char buf[128] = "";
	bzero(buf,sizeof(buf));

	ssize_t ret = recv(fd,buf,sizeof(buf),0);
	if(ret < 0)
	{
		ERR_MSG("recv");
		return -1;
	}
	else if(0 == ret)
	{
		printf("[%s:%d]客户端下线 fd = %d",\
				inet_ntoa(cliaddrs[fd].sin_addr),ntohs(cliaddrs[fd].sin_port),fd);

		close(fd);
		FD_CLR(fd,preadfds);

		while(FD_ISSET(*pmaxfd,preadfds) == 0 && *pmaxfd-->=0);

		return 0;
	}

	printf("[%s:%d] newfd = %d:%s\n",\
			inet_ntoa(cliaddrs[fd].sin_addr),ntohs(ntohs(cliaddrs[fd].sin_port)),fd,buf);

	//发送
	strcat(buf,">-<");
	if(send(fd,buf,sizeof(buf),0) < 0)
	{
		ERR_MSG("send");
		return -1;
	}

	printf("send success\n");

	return 0;
}
int main(int argc, const char *argv[])
{
	//判断输入合法性
	if(argc < 2)
	{
		printf("input error,usage:./a.out ip\n");
		return -1;
	}

	//创建流式套接字
	int sfd;
	if((sfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket success sfd = %d\n",sfd);

	//允许端口快速复用
	int reuse = 1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0)
	{
		ERR_MSG("setsockopt");
		return -1;
	}

	//填写地址信息结构体给bind使用
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	
	//bind绑定地址信息,必须绑定
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) < 0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("bind success\n");

	//listen 将套接字转换成监听模式
	if(listen(sfd,128) < 0)
	{
		ERR_MSG("listen");
		return -1;
	}
	printf("listen success\n");

	//创建一个读集合
	fd_set readfds,tempfds;

	//清零
	FD_ZERO(&readfds);

	//将文件描述符添加到集合中
	FD_SET(0,&readfds);
	FD_SET(sfd,&readfds);

	int s_res = -1;
	ssize_t res = -1;
	int maxfd = sfd; 	//存储最大文件描述符
	
	struct sockaddr_in cliaddrs[1024]; 	//记录客户端信息

	while(1)
	{
		//用来额外备份,保证原先的集合不变
		tempfds = readfds;
		//实行IO多路复用
		s_res = select(maxfd+1,&tempfds,NULL,NULL,NULL);
		if(s_res < 0)
		{
			ERR_MSG("select");
			return -1;
		}
		else if(s_res == 0)
		{
			printf("timeout\n");
			return -1;
		}
		
		//当有文件描述符准备就绪时,该集合中就会只剩下已经准备就绪的文件描述符
		for(int i=0;i<=maxfd;i++)
		{
			if(FD_ISSET(i,&tempfds) == 0)
				continue;

			//运行到这说明集合内有这个文件描述符
			if(0 == i)
			{
				//键盘输入事件
				keybroad_event(readfds);
			}else if(sfd == i)
			{
				//客户端连接事件
				cliconnect_event(sfd,cliaddrs,&readfds,&maxfd);
			}else{
				//客户端交互事件
				clichange_event(i,cliaddrs,&readfds,&maxfd);
			}
		}
	}

	//关闭套接字
	if(close(sfd) < 0)
	{
		ERR_MSG("close");
		return -1;
	}

	return 0;
}

效果图:

hwk2:select实现服务器并发_第1张图片

你可能感兴趣的:(c语言)