Linux网络编程--单进程服务器处理多客户请求(I/O复用)

采用I/O复用可以使用单进程的服务器去处理多个客户的连接请求,而不需要为每个客户分配单独的进程或线程去专门的处理客户请求;

I/O复用的模型:

进程阻塞于select(),而select检测相应的描述符集,如果有就绪的描述符,则退出阻塞,然后进程判断就绪的描述符并作相应的处理;

下面的例子根据前面的文章:http://blog.csdn.net/hjj414/article/details/21642183中的程序修改而来;

功能:

服务器仅仅读取客服发送的数据,并显示到屏幕上;

客户端仅仅发送数据到服务器;

服务器代码:

int main(int argc, char **argv)
{
	int listenfd;
	struct sockaddr_in servaddr;
	
	/*socket*/
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == listenfd) {
		perror("socket error.");
		return -1;
	}
	/*init IP address*/
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(1234);
	/*set prot reuse*/
	const int onflg = 1;
	if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &onflg, sizeof(onflg))) {
		perror("set socket prot reuse error.");
		return -1;
	}
	/*bind*/
	if (-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
		perror("bind error.");
		return -1;
	}
	/*listen*/
	if (-1 == listen(listenfd, 10)) {
		perror("listen error.");
		return -1;
	}//调用listen后建立了一个监听socket,如果没有客户连接,此时监听socket的‘已完成连接队列’为空;
	 //accept函数将读取监听socket的已完成连接队列,如果队列为空accept将阻塞,直到队列不为空,即当
	 //监听socket可读的时候,accept才从退出阻塞状态;所以使用I/O复用模型,使用select函数来测试
	 //监听socket,当其可读时,调用accept;
	serv_io_multiplex(listenfd);
	
	return 0;
}
serv_io_multiplex:

struct cli_array_t {//记录已连接的soket描述符
	int clifd[FD_SETSIZE];	//已连接的socket
	struct sockaddr_in *cliad[FD_SETSIZE];//客户IP地址
	int maxi; //记录clifd数组中有记录的最大下标
	int maxfd; //最大的描述符
};
#define BSIZE 100
int serv_io_multiplex(int listenfd)
{
	int connfd;
	struct sockaddr_in cliaddr;
	socklen_t clilen;

	fd_set allset, rset; //allset为所有待检测的读描述符集,rset为当前检测的读描述符集
	int nready, i; //nready记录当前描述符集中有多少个准备就绪
	char buf[BSIZE];
	ssize_t n;
	struct cli_array_t cliarr;
	
	for (i = 0; i < FD_SETSIZE; i ++) {
		cliarr.clifd[i] = -1;
		cliarr.cliad[i] = NULL;
	}
	cliarr.maxi = -1;
	cliarr.maxfd = listenfd;

	FD_ZERO(&allset);
	FD_SET(listenfd, &allset); //将监听socket加入allset中

	while (1) {
		rset = allset; //更新当前检测的读描述符集
		nready = select(cliarr.maxfd+1, &rset, NULL, NULL, NULL);
		if (-1 == nready) {
			perror("select error.");
			return -1;
		}
		/*client connected*/
		if (FD_ISSET(listenfd, &rset)) {//监听socket准备就绪
			clilen = sizeof(cliaddr);
			connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
			if (-1 == connfd) {
				perror("accept error.");
				return -1;
			}
			for (i = 0; i < FD_SETSIZE; i ++) { //记录已连接socket,并记录客户IP
				if (cliarr.clifd[i] < 0) {
					cliarr.clifd[i] = connfd;
					cliarr.cliad[i] = malloc(sizeof(cliaddr));
					if (NULL == cliarr.cliad[i]) {
						printf("malloc error.\n");
						return -1;
					}
					cliarr.cliad[i]->sin_addr = cliaddr.sin_addr;
					cliarr.cliad[i]->sin_port = cliaddr.sin_port;
					break;
				}
			}
			FD_SET(connfd, &allset); //将已连接的socket描述符加入allset描述符集中
			if (i > cliarr.maxi)
				cliarr.maxi = i;
			if (connfd > cliarr.maxfd)
				cliarr.maxfd = connfd;

			if (--nready <= 0) {	//如果没有其他的可读就绪,进入下一次检测
				continue;
			}
		}
		/*read clients*/
		for (i = 0; i <= cliarr.maxi; i ++) {
			if (cliarr.clifd[i] < 0){ 
				continue;
			}
			if (FD_ISSET(cliarr.clifd[i], &rset)) {// 已连接的socket读准备就绪
				if (0 == (n=read(cliarr.clifd[i], buf, BSIZE))) { //客户输入ctrl+D或者ctrl+C,即客户关闭
					/*client close the socket*/
					close(cliarr.clifd[i]);
					FD_CLR(cliarr.clifd[i], &allset);
					cliarr.clifd[i] = -1;
					printf("<IP:%s @PORT:%d exit>\n", inet_ntoa(cliarr.cliad[i]->sin_addr), \
							ntohs(cliarr.cliad[i]->sin_port), buf);
					free(cliarr.cliad[i]);
					cliarr.cliad[i] = NULL;
				} else { //显示读取到的信息
					buf[n] = '\0';
					printf("IP:%s @PORT:%d# %s", inet_ntoa(cliarr.cliad[i]->sin_addr), \
							ntohs(cliarr.cliad[i]->sin_port), buf);
				}
				if (--nready <= 0) //如果没有已连接的socket可读,则进入下一次检测
					break;
			}
		}
		while (cliarr.clifd[cliarr.maxi]<0 && cliarr.maxi>=0)
			cliarr.maxi --;
	}
}

客户端的代码:

int main(int argc, char **argv)
{
	int sockfd;
	struct sockaddr_in servaddr;
	
	if (2 != argc) {
		printf("usage: ./client 127.0.0.1");
		return -1;
	}
	/*socket*/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd) {
		perror("socket error.");
		return -1;
	}
	/*connect*/
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(1234);
	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
	if (-1 == connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
		perror("connect error.");
		return -1;
	}

	str_cli(stdin, sockfd);

	return 0;
}

#define LSIZE 100
void str_cli(FILE *fp, int sockfd) //仅仅发送数据到服务器
{
	char sendline[LSIZE], recvline[LSIZE];

	while (NULL != fgets(sendline, LSIZE, fp)) {
		n_write(sockfd, sendline, strlen(sendline));
	}
}

运行结果:


……


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值