继续跟着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);
}