14. TCP套接字之shutdown函数


前面我们虽然解决了困惑我们很久客服端的问题, 但是虽然解决了但是该客服端还有更加大的隐患存在, 接下来我们就来看一下问题所在和问题解决办法.


问题浮现

使用前面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 

客服端输入一段代码进行回显即可, 实验结果如下:

14. TCP套接字之shutdown函数_第1张图片

可以看到, 客服端并没有完整的接收到所有的数据就关闭了, 难道丢失数据了? 服务端为什么也关闭?

怎么回事, 怎么越改越错, 完全不如之前的IO分离了. 不急不急, 找到bug问题所在就容易解决了.


问题分析

通过抓包我们来分析, 抓包结果如下 :

14. TCP套接字之shutdown函数_第2张图片

可以看到服务端很认真的将数据发送给客服端, 但是客服端发送完数据之后就直接关闭了, 而服务端并没有完整的将数据发送完; 当客服端关闭后服务端再发送数据就收到RET. 所以后面服务端的数据就丢失了, 客服端并没有收到所有的数据.

问题所在就是客服端关闭的太早了, 并没有正常的四次挥手就关闭了. 看来客服端不能直接close关闭套接字, 而是应该等待把服务端发送过来的所有数据全部接收完了后调用 close才行, 但是 close没有办法判断什么时候读完啊.

接下来介绍shutdown函数来解决 close的问题. 因为TCP是全双工通信, 所以可以关闭套接字一端( 读或者写端 ).


shutdown函数

TCP是全双工通信, 所以我们可以选择性的关闭读端或者写端实现半关闭状态 [1] .

#include 

int shutdown(int sockfd, int howto);

参数 :

  • sockfd : 套接字
  • howto参数 :
    • SHUT_RD : 关闭套接字读端. 此时还可以接收数据.
    • SHUT_WR : 关闭套接字写端. 此时还可以写数据.
    • SHUT_RDWR : 关闭套接字读写端. 类似与调用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

14. TCP套接字之shutdown函数_第3张图片

可以看到在客服端发送完所有数据时进入了半关闭状态, 一直等待服务端传输完数据后退出. 终端也可以看到输出了所有的数据出来.


总结

做到这里我们终于使用IO复用写出了基本没有错误的C/S, 代码更加的复杂, 但是整体带来的好处也很明显, 以单进程实现了多进程的功能.

  • 掌握shutdown
  • 半关闭状态

你可能感兴趣的:(网络编程,网络编程学习)