TCP连接关闭时不发FIN包的奇怪行为分析

转载:http://blog.chinaunix.net/uid-10106787-id-3172066.html


一般情况下,当TCP连接主动关闭时,会向对端发送一个FIN,对端会获得一个读事件,调用read时返回0,表示读到一个EOF,读结束。然而,在有的时候却不是这样的,接下来将讨论一下。
 
首先是一个简单的服务器程序,accept()后睡眠5s钟,然后关闭连接。

  1. int main(void)
  2. {
  3.     int fd;

  4.     fd = socket(AF_INET, SOCK_STREAM, 0);

  5.     struct sockaddr_in servaddr;
  6.     memset(&servaddr, 0, sizeof servaddr);
  7.     servaddr.sin_family = AF_INET;
  8.     servaddr.sin_port = htons(9001);
  9.     servaddr.sin_addr.s_addr = 0;

  10.     const int on = 1;
  11.     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);

  12.     bind(fd, (struct sockaddr *) &servaddr, sizeof servaddr);

  13.     listen(fd, 10);

  14.     int clifd = accept(fd, NULL, NULL);

  15.     sleep(5);

  16. //    char buf[1024];
  17. //    read(clifd, buf, sizeof buf); 

  18.     close(clifd);

  19.     close(fd);

  20.     return 0;
  21. }
下面是一个简单的客户端程序,连接成功后发送1024字节的数据,然后调用read()

  1. int main(void)
  2. {
  3.     int fd, ret;
  4.     
  5.     fd = socket(AF_INET, SOCK_STREAM, 0);

  6.     struct sockaddr_in servaddr;
  7.     memset(&servaddr, 0, sizeof servaddr);

  8.     servaddr.sin_family = AF_INET;
  9.     servaddr.sin_port = htons(9001);
  10.     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

  11.     connect(fd, (SA *) &servaddr, sizeof servaddr);

  12.     char buf[1024];

  13.     if (write(fd, buf, sizeof buf) != sizeof buf)
  14.         err_sys("write error");

  15.     if ((ret = read(fd, buf, sizeof buf)) < 0)
  16.         err_sys("read error");

  17.     fprintf(stderr, "Read %d Bytes\n", ret);

  18.     if (close(fd) < 0)
  19.         err_sys("close error");

  20.     return 0;
  21. }
运行结果如下:

  1. read error : Connection reset by peer
可见server在close时向client发送的不是FIN,而是RST,为什么会这样呢?我们从内核中找答案。
 
见 net/ipv4/tcp.c 中的 tcp_close() 函数,

  1. /* As outlined in RFC 2525, section 2.17, we send a RST here because
  2.      * data was lost. To witness the awful effects of the old behavior of
  3.      * always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk
  4.      * GET in an FTP client, suspend the process, wait for the client to
  5.      * advertise a zero window, then kill -9 the FTP client, wheee...
  6.      * Note: timeout is always zero in such a case.
  7.      */
  8.     if (data_was_unread) {
  9.         /* Unread data was tossed, zap the connection. */
  10.         NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
  11.         tcp_set_state(sk, TCP_CLOSE);
  12.         tcp_send_active_reset(sk, sk->sk_allocation);
  13.     }
代码里面写得很清楚,如果你的接收缓冲区中还有数据,协议栈就会发送RST而不是FIN。
 
我们再来验证一下,在server中先调用read()清空读缓冲区后再close(),此时发现client会收到FIN了。
 
可见,学习内核的协议栈是多么的重要啊!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值