使用select编写服务器

io多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般都是读就绪或者写就绪),就能通知应用程序进行相应的读写操作。select函数作为io多路复用的机制,第一个参数nfds是fd_set集合中最大描述符值+1,fdset是一个位数组,每一位代表其对应的描述符是否需要被检查。第二三四参数表示需要关注读、写、错误时间的文件描述符位数组,这些参数既是输入型参数也是输出型参数,可能会被内核修改用于标识哪些描述符上发生了关注的事件,所以每次调用select前都需要重新初始化fdset。timeout参数为超时时间,该结构会被内核修改,其值为超时剩余时间。

select函数返回值有三种,返回-1时表示select失败;返回0表示超时;其他返回值表示select成功。

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>

int rfds[128];

void usage(const char* proc)
{
	assert(proc);
	printf("usage:%s: [ip] [port]\n",proc);
}

int start_up(const char* ip,int port)
{
	assert(ip);
	assert(port > 0);

	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0)
	{
		perror("socket");
		exit(2);
	}

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = inet_addr(ip);
	
	if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
	{
		perror("bind");
		exit(3);
	}

	if(listen(sock,5) < 0)
	{
		perror("listen");
		exit(4);
	}
	return sock;
}

int main(int argc,char* argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		return 1;
	}

	int listen_sock = start_up(argv[1],atoi(argv[2]));

	int i = 0;
	for(;i < 128; i++)
	{
		rfds[i] = -1;
	}

	fd_set rset;
	int max_fd = 0;

	while(1)
	{
		struct timeval timeout = {0,0};
		FD_ZERO(&rset);

		rfds[0] = listen_sock;
		max_fd = listen_sock;

		for(i = 0;i < 128; i++)
		{
			if(rfds[i] >= 0)
			{
				FD_SET(rfds[i],&rset);

				if(max_fd < rfds[i])
					max_fd = rfds[i];
			}
		}

		switch(select(max_fd + 1,&rset,NULL,NULL,NULL))
		{
			case -1:
				perror("select");
				break;
			case 0:
				printf("timeout");
				break;
			default:
				{
					int j = 0;
					for(;j < 128;j++)
					{
						if(rfds[j] < 0)
							continue;

						if(j == 0&&FD_ISSET(rfds[j],&rset))
						{
							struct sockaddr_in client;
							socklen_t len = sizeof(client);
							int new_fd = accept(listen_sock,(struct sockaddr*)&client,&len);
							if(new_fd < 0)
							{
								perror("accept");
							}
							else
							{
								printf("get a client:socket :%s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
								int k = 0;
								for(;k < 128;k++)
								{
									if(rfds[k] == -1)
									{
										rfds[k] = new_fd;
										break;
									}
								}
								if( k == 128)
								{
									close(new_fd);
								}
							}
						}
						else if(FD_ISSET(rfds[j],&rset))
						{
							char buf[1024];
							ssize_t s = read(rfds[j],buf,sizeof(buf)-1);

							if(s > 0)
							{
								buf[s] = 0;
								printf("client#%s\n",buf);
							}
							else if(s == 0)
							{
								printf("client close...\n");
								close(rfds[j]);
								rfds[j] = -1;
							}
							else
							{
								perror("read");
							}
						}
					}
				}
				break;
		}
	}
}
使用telnet为一个客户端访问:

当把服务器终止后,再次打开服务器会出现这种情况:

这是因为,虽然server的应用程序终止了,但tcp协议层的连接没有完全断开,因此不能再次监听同样的server端口。

client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后 就可以再次启动server了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值