第十八篇:批量处理情况下的回射客户端
前言
批量处理是指将原先的输入重定向到一个输入文件,这样客户端将连续向服务器发送该文件中的数据,然后接收到服务器的回射数据后,再将其写入到另一个文件中。在这样的情况下,原来的客户端程序不能够再正确运行了。那么会发生什么问题?我们又该如何修改客户端程序才能使之正确工作呢?
且看下文。
发现问题一 传输线路中数据的丢失
先来看看原客户端中的数据处理函数的一段:
1 if (FD_ISSET(sockfd, &rset)) { 2 if (Readline(sockfd, recvline, MAXLINE) == 0) 3 err_quit("str_cli: server terminated prematurely"); 4 Fputs(recvline, stdout); 5 } 6 7 // 处理用户输入 8 if (FD_ISSET(fileno(fp), &rset)) { 9 if (Fgets(sendline, MAXLINE, fp) == NULL) 10 return; 11 Writen(sockfd, sendline, strlen(sendline)); 12 }
可以发现,当用户输入EOF后,该函数会立刻返回到主程序并执行 close() 关闭连接。但现在问题出现了:在批量处理的情况下,此时传输线路中的一部分数据还没有到达客户端。而客户端已经关闭了连接,这部分数据只能被丢弃了。
解决思路
在当用户输入EOF后,我们不立刻断开连接,而是只断开连接的写入部分( 使用shutdown函数实现 )。只有当服务器也关闭了服务进程之后,才让客户端关闭连接( 为此需要设置一个标记符 stdioeof )。
发现问题二 IO异常
请再看上述代码的第 9 行,在批量情况下,这个输入语句只会从输入文件读取一行数据就返回,而不理会输入缓冲区中剩下的数据。上面第2行代码也是的。而当回到select以后,即使studio缓冲区仍然有数据没被取出。它假定是不知道studio使用了缓冲区,故不会返回文件描述符就绪。
解决思路
废弃以文本行为中心的代码,改为针对缓冲区操作。
客户端str_cli () 函数再修订版
1 #include "unp.h" 2 3 void 4 str_cli(FILE *fp, int sockfd) 5 { 6 int maxfdp1, stdineof; 7 fd_set rset; 8 char buf[MAXLINE]; 9 int n; 10 11 stdineof = 0; 12 FD_ZERO(&rset); 13 for ( ; ; ) { 14 // 当客户端输入并没有结束 15 if (stdineof == 0) 16 FD_SET(fileno(fp), &rset); 17 FD_SET(sockfd, &rset); 18 maxfdp1 = max(fileno(fp), sockfd) + 1; 19 Select(maxfdp1, &rset, NULL, NULL, NULL); 20 if (FD_ISSET(sockfd, &rset)) { 21 // 注意已经换成了以缓冲区为处理单位的函数,后面的也是。 22 if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { 23 // 当客户端输入结束并且服务器也关闭了服务就正常退出 24 if (stdineof == 1) 25 return; 26 // 当客户端输入还没结束但服务器已经关闭服务就正常退出 27 else 28 err_quit("str_cli: server terminated prematurely"); 29 } 30 31 Write(fileno(stdout), buf, n); 32 } 33 34 if (FD_ISSET(fileno(fp), &rset)) { 35 if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) { 36 stdineof = 1; 37 // 输入结束的话关闭连接的写部分且清空文件监听描述符,停止对该描述符的监听。 38 Shutdown(sockfd, SHUT_WR); 39 FD_CLR(fileno(fp), &rset); 40 continue; 41 } 42 43 Writen(sockfd, buf, n); 44 } 45 } 46 }