前面我们虽然解决了困惑我们很久客服端的问题, 但是虽然解决了但是该客服端还有更加大的隐患存在, 接下来我们就来看一下问题所在和问题解决办法.
使用前面IO复用之select 的客服端和服务端都改写成的完整代码做实验.
完整代码 : select_server_client.c
服务端运行:
./a.out 1 8080 127.0.0.1
客服端运行:
./a.out 2 8080 127.0.0.1 < select_service_client.c
客服端输入一段代码进行回显即可, 实验结果如下:
可以看到, 客服端并没有完整的接收到所有的数据就关闭了, 难道丢失数据了? 服务端为什么也关闭?
怎么回事, 怎么越改越错, 完全不如之前的IO分离了. 不急不急, 找到bug问题所在就容易解决了.
通过抓包我们来分析, 抓包结果如下 :
可以看到服务端很认真的将数据发送给客服端, 但是客服端发送完数据之后就直接关闭了, 而服务端并没有完整的将数据发送完; 当客服端关闭后服务端再发送数据就收到RET
. 所以后面服务端的数据就丢失了, 客服端并没有收到所有的数据.
问题所在就是客服端关闭的太早了, 并没有正常的四次挥手就关闭了. 看来客服端不能直接close
关闭套接字, 而是应该等待把服务端发送过来的所有数据全部接收完了后调用 close
才行, 但是 close
没有办法判断什么时候读完啊.
接下来介绍shutdown
函数来解决 close
的问题. 因为TCP是全双工通信, 所以可以关闭套接字一端( 读或者写端 ).
TCP是全双工通信, 所以我们可以选择性的关闭读端或者写端实现半关闭状态 [1] .
#include
int shutdown(int sockfd, int howto);
参数 :
close
.我们可以使用shutdown
修改客服端, 在发送完数据之后调用 shutdown(SHUT_WR)
, 等服务端把数据发送之后在调用shutdown(SHUT_RD)
关闭读端.
while(1)
{
testrfds = rfds;
select(sockfd + 1, &testrfds, NULL, NULL, NULL);
if(FD_ISSET(STDIN_FILENO, &testrfds))
{
n = read(STDIN_FILENO, buf, sizeof(buf));
if(n == 0){
shutdown(sockfd, SHUT_WR);
stat = 1;
FD_CLR(sockfd, &rfds);
continue;
}
send(sockfd, &buf, n, 0);
}
if(FD_ISSET(sockfd, &testrfds))
{
n = recv(sockfd, buf, sizeof(buf), 0);
if(n == 0 && stat == 1){
return 0;
}
else
exit(-1);
write(STDOUT_FILENO, buf, n);
}
}
实验完整代码 : shutdown.c
服务端 :
./a.out 1 8080 127.0.0.1
客服端
./a.out 2 8080 127.0.0.1
可以看到在客服端发送完所有数据时进入了半关闭状态, 一直等待服务端传输完数据后退出. 终端也可以看到输出了所有的数据出来.
做到这里我们终于使用IO复用写出了基本没有错误的C/S, 代码更加的复杂, 但是整体带来的好处也很明显, 以单进程实现了多进程的功能.