《UNIX网络编程 卷1》 笔记: I/O复用 select函数

上节我们实现的TCP回显客户程序有缺陷,代码如下:

 

void str_cli(FILE *fp, int sockfd)  
{  
    char sendline[MAXLINE], recvline[MAXLINE];  
  
    while (Fgets(sendline, MAXLINE, fp) != NULL) { /*从标准输入读取一行文本*/  
        Writen(sockfd, sendline, strlen(sendline)); /*发送到服务器*/  
        if (Readline(sockfd, recvline, MAXLINE) == 0) /*从服务器读取一行文本*/  
            err_quit("str_cli: server terminated prematurely");  
        Fputs(recvline, stdout); /*输出到标准输出*/  
    }  
}  

缺陷在于它仅仅阻塞在标准输入上,只有先处理完标准输入之后才能处理套接字的输入,而不能同时处理两者的输入。有了select函数,我们就能同时监听两个描述符解决这个问题。参考select函数的用法,我们修改了str_cli函数,代码如下:

 

 

void str_cli(FILE *fp, int sockfd)
{
	int maxfdp1;
	fd_set rset;
	char sendline[MAXLINE], recvline[MAXLINE];

	FD_ZERO(&rset);
	for ( ; ; ) {
		FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL);
		if (FD_ISSET(sockfd, &rset)) { /*套接字输入*/
			/*!!!!*/
			if (Readline(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
			Fputs(recvline, stdout);
		}
		if (FD_ISSET(fileno(fp), &rset)) { /*标准输入*/
			/*!!!!*/
			if (Fgets(sendline, MAXLINE, fp) == NULL)
				return;
			Writen(sockfd, sendline, strlen(sendline));
		}
	}
}

很不幸的是,修改之后的代码是不正确的。理由如下:

    1. readline函数使用的是自己的缓冲区,fgets函数使用的是标准I/O(stdio)缓冲区。而select函数判读描述符是否可读是从read系统调用的角度来看的,它并不知道readline函数和fgets函数使用了缓冲区。这样就会出现当数据已经在缓冲区时,而select函数并不知道的情况。所以使用标准I/O库的函数(fgets等)不能和select函数混合使用!!!

   2. 在批量方式下(将标准输入重定向到一个输入文件,将标准输出重定向到一个输出文件),我们会发现输出文件总是小于输入文件。原因在于在标准输入中遇到EOF时,str_cli函数就会返回,客户进程调用exit函数退出,随后关闭套接字描述符时会同时关闭接收端和发送端,这样在管道中等待接收的数据就被丢弃了。

 

为了解决第一个问题,我们取消以文本行为中心的操作,不使用标准I/O缓冲区,而使用read函数,改为单个字节的操作方式。

为了解决第二个问题,我们使用shutdown函数,执行TCP的半关闭操作,使得TCP连接的发送端被关闭的同时接收端仍能接收数据。

修改后的代码如下:

 

void str_cli(FILE *fp, int sockfd)
{
	int maxfdp1, stdineof;
	fd_set rset;
	char buf[MAXLINE];
	int n;

	stdineof = 0; /*标准输入遇到EOF时置1*/
	FD_ZERO(&rset);
	for ( ; ; ) {
		if (stdineof == 0)
			FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL);
		if (FD_ISSET(sockfd, &rset)) { /*套接字可读*/
			/*当我们在套接字遇到EOF时,如果我们已在标准输入遇到EOF,
			那么服务器就是正常的终止,函数返回。否则服务器就是异常的终止。*/
			if ((n = Read(sockfd, buf, MAXLINE)) == 0) {
				if (stdineof == 1)
					return;
				else
					err_quit("str_cli: server terminated prematurely");
			}
			Write(fileno(stdout), buf, n);
		}

		if (FD_ISSET(fileno(fp), &rset)) { /*标准输入可读*/
			if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) { /*标准输入遇到EOF*/
				stdineof = 1;
				Shutdown(sockfd, SHUT_WR); /*关闭连接的发送端*/
				FD_CLR(fileno(fp), &rset);
				continue;
			}
			Writen(sockfd, buf, n);
		}
	}
}

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值