UNP读书笔记第16章 非阻塞IO

继续跟着steven大佬修改 回射程序

接着第六章的程序

那个程序的大致流程是

 先读输入,读完输入再发给服务器,接着等服务器的输入,并输出到屏幕上, 总共四个步骤

但是,假如我们一次性输入的数据非常多,大概有10000行,那么就会出现一种情况:

当我们正在输入第5000行时, 第一个发出去的行,已经被服务器回射回来了。按理说,这个行应该马上输出

但是,他却会卡在中断输入的IO那里(Fgets) ,等Fgets结束了,才输出

同理,在输出到终端时, 本也可以立马输入,但是我还是阻塞住了,一直等Fputs结束了我才继续输入。

显然这里的阻塞产生了影响。


所以要想办法进行分离。

第一种方法: 非阻塞IO

将每个描述符设置为非阻塞, 然后设置2个缓冲区, 终端输入->发出 缓冲区 , 和接收->终端输出 缓冲区

每当1个缓冲区可读,或可写时,从select返回,执行对应的IO操作, 并用相应的指针去判断 是否有数据可发出

是否有数据可终端输出


第二种方法: 客户子进程

即客户fork一个子进程

父进程用来接收终端输入,并发送到服务器上

子进程用来接收服务器上的数据,并输出到终端上

当子进程结束后,kill掉父进程,避免服务器进程提前结束,但父进程不知道的情况。


第一种方法的例程如下,我自己照着敲了一遍, 加了注释, 顺便试着和第六章的程序实验对比了一下

果然对于输入大文件,其速度加快了一倍之多。

#include "unp.h"
#include <time.h>


void str_cli(FILE *fp, int sockfd){
	int maxfdp1, val, stdineof;
	ssize_t n, nwritten;
	fd_set rset, wset;
	char to[MAXLINE], fr[MAXLINE];  //to是从标准输入到服务器的缓冲区,fr是服务器到标准输出的缓冲区
	char *toiptr, *tooptr, *friptr, *froptr; //opt是输出,ipt是输入。
	
	val = Fcntl(sockfd, F_GETFL, 0);  //暂存套接字描述符的原值
	Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);  //用|的形式去添加非阻塞位
	
	val = Fcntl(STDIN_FILENO, F_GETFL, 0);  //暂存标准输入字描述符的原值
	Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);  //设置为非阻塞
	
	val = Fcntl(STDOUT_FILENO, F_GETFL, 0);  //暂存标准输出的原值
	Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);  //设置为非阻塞	
	
	toiptr = tooptr = to;  //缓冲区指针初始化,一开始都是相同的,在数组开头
	friptr = froptr = fr;
	stdineof = 0; //这个标志用来判断标准输入是否结束
	maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
	for( ; ; ){
		FD_ZERO(&rset);   //读写集合的初始化清零
		FD_ZERO(&wset);   
		//如果可读,且标准输入缓冲区还够,则重置该描述符到reset,接下来select能检测到它	
		if (stdineof == 0 && toiptr < &to[MAXLINE]) 
			FD_SET(STDIN_FILENO, &rset);
		if (friptr < &fr[MAXLINE]) //从服务器接收数据缓冲区还够,则绑定 
			FD_SET(sockfd, &rset);
		if (tooptr != toiptr) //输入数据中还有一些数据没有发往服务器,则绑定
			FD_SET(sockfd, &wset);
		if(froptr != friptr) //接收的数据中,还有一些没发往屏幕,则绑定
			FD_SET(STDOUT_FILENO, &wset);
		Select(maxfdp1, &rset, &wset, NULL, NULL); //等待哪个描述符有可读或可写状态发生
		/* ………等待……,好!Select返回了,我们检查一下是哪些描述符有反应了 */
		if (FD_ISSET(STDIN_FILENO, &rset)){ //如果是输入可读,即有标准输入的时候
			if( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0){
				if (errno != EWOULDBLOCK)  //我们忽略阻塞错误
					err_sys("read error on stdin");
			}else if (n == 0){  //n=0说明是输入了EOF
				fprintf(stderr, "%s:EOF on stdin\n", gf_time()); //告知某时刻结束了
				stdineof = 1;
				if (tooptr == toiptr)  //当发往服务器的缓冲区用完,说明结束啦,发完了,告知服务器一个FIN
					Shutdown(sockfd, SHUT_WR);  //也可能没用完,那么这个FIN交给第4个大if来做
			}else{
				fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);
				toiptr += n; //又输入了一些!指针右移
				FD_SET(sockfd, &wset);  //既然有输入,那么又可以给服务器发数据了,set这个描述符集合
			}
		}
		if (FD_ISSET(sockfd, &rset)){ //收到服务器发来的数据啦
			if( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0){
				if (errno != EWOULDBLOCK)  //我们忽略阻塞错误
					err_sys("read error on stdin");
			}else if (n == 0){  //n=0说明是服务器发给我的数据发完了
				fprintf(stderr, "%s:EOF on socket\n", gf_time()); //告知某时刻结束了
				if (stdineof)  //刚好我的输入也结束了的,说明,整个回射任务完成辣
					return ;
				else  //whatfuck! 输入还没结束,你就告诉我回射完了?你肯定把服务器关了!!
					err_quit("str_cli: server terminated prematurely");
			}else{
				fprintf(stderr, "%s: read %d bytes from socket\n", gf_time(), n);
				friptr += n; //收到了一些数据!指针右移
				FD_SET(STDOUT_FILENO, &wset);  //那么接下来可以让标准输出预备啦
			}
		}
		
		//想输出到屏幕??先看看给你存的那些服务器发来的数据够不够
		//服务器的数据都给你发完了?说明那边堵住了,你别急着写,先歇着!
		if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)){ 
			if( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0){
				if (errno != EWOULDBLOCK)  //我们忽略阻塞错误
					err_sys("write error on stdin");
			}else{ 
				fprintf(stderr, "%s: wrote %d bytes to stdout\n", gf_time(), nwritten);
				froptr += nwritten; //往屏幕发了一下数据,缓冲区里的内容减少啦
				if(froptr == friptr)  //哇发完了?那我们重头开始(免得数组不够了- -)
					froptr = friptr = fr;
			}
		}
		
		//我要往服务器发数据!先看看内容够不够。。。
		if (FD_ISSET(sockfd, &wset) && ((n = toiptr - tooptr) > 0)){ 
			if( (nwritten = write(sockfd, tooptr, n)) < 0){ //可能一次没写完
				if (errno != EWOULDBLOCK)  //我们忽略阻塞错误
					err_sys("write error on stdin");
			}else{ 
				fprintf(stderr, "%s: wrote %d bytes to socket\n", gf_time(), nwritten);
				tooptr += nwritten; //往屏幕发了一下数据,缓冲区里的内容减少啦
				if(tooptr == toiptr){  //哇发完了?那我们重头开始(免得数组不够了- -)
					tooptr = toiptr = to;
					if (stdineof)
						Shutdown(sockfd, SHUT_WR);
				}
			}
		}
	}	
	
}

int main(int argc, char **argv){
	int sockfd,i;
	struct sockaddr_in servaddr;
	if (argc != 2){
		err_quit("usage: tcpli<IPaddress>");
	}
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	Inet_pton(AF_INET, argv[1],  &servaddr.sin_addr);
	Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
	str_cli(stdin, sockfd);
	exit(0);
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值