void tcp_rcv_established(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len)

主要是处理已经连理连接的输入的tcp数据包。tcp_rcv_established实际上包含了两条路径用于处理不同目的的数据包。

  • 快速路径 使用快速路径只进行最少的处理,如处理数据段、发生ACK、存储时间戳等。
  • 慢速路径 使用慢速路径可以处理乱序数据段、PAWS、socket内存管理和紧急数据等。

主要是:TCP首部中第4个32位字除去保留的bit位和预测标志一致,比如:skb包的序列号和sock结构下一个要接收到序号相等,并且skb包中的确认序列号是有效的,除了head_len,只有为1的bit位对应的是ACK标志其余为0,snd_wnd则是本端发送窗口的大小。就走快速路径;毕竟是预期的报文!!

但是如果设置了FIN标志的话,则检查预测标志时失败,所以会在慢速路径中处理FIN包。

其流程大约是:

if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
tcp_fin(sk);
/*
* Process the FIN bit. This now behaves as it is supposed to work
* and the FIN takes effect when it is validly part of sequence
* space. Not before when we get holes.
*
* If we are ESTABLISHED, a received fin moves us to CLOSE-WAIT
* (and thence onto LAST-ACK and finally, CLOSE, we never enter
* TIME-WAIT)
*
* If we are in FINWAIT-1, a received FIN indicates simultaneous
* close and we go into CLOSING (and later onto TIME-WAIT)
*
* If we are in FINWAIT-2, a received FIN moves us to TIME-WAIT.
*/
static void tcp_fin(struct sock *sk)TCP_FIN_TIM
{
struct tcp_sock *tp = tcp_sk(sk); inet_csk_schedule_ack(sk);//表明需要发送ack sk->sk_shutdown |= RCV_SHUTDOWN; //表明rcv 关闭 关闭接收通道
sock_set_flag(sk, SOCK_DONE);//sock结构的标志,表示连接将要结束 switch (sk->sk_state) {
case TCP_SYN_RECV:
case TCP_ESTABLISHED:
/* Move to CLOSE_WAIT */
tcp_set_state(sk, TCP_CLOSE_WAIT);//设置为 close_wait 状态
inet_csk(sk)->icsk_ack.pingpong = 1;//设置延迟发送ACK的标志----也就是进入延迟确认模式 -----期望发送数据或FIN时携带ACK确认
/*tcp_fin()中虽然设置了发送ACK的相关标志,但是要有一个引发ACK发送的操作,或者是给内核发送ACK的一个提示。
这个操作是在上层函数tcp_rcv_established()函数中进行的,通过间接调用__tcp_ack_snd_check()中完成
*/
break; case TCP_CLOSE_WAIT:
case TCP_CLOSING:
/* Received a retransmission of the FIN, do
* nothing.
*/
break;
case TCP_LAST_ACK:
/* RFC793: Remain in the LAST-ACK state. */
break; case TCP_FIN_WAIT1:
/* This case occurs when a simultaneous close
* happens, we must ack the received FIN and
* enter the CLOSING state.
*/
tcp_send_ack(sk);
tcp_set_state(sk, TCP_CLOSING);
break;
case TCP_FIN_WAIT2:
/* Received a FIN -- send ACK and enter TIME_WAIT. */
tcp_send_ack(sk);
tcp_time_wait(sk, TCP_TIME_WAIT, 0);
break;
default:
/* Only TCP_LISTEN and TCP_CLOSE are left, in these
* cases we should never reach this piece of code.
*/
pr_err("%s: Impossible, sk->sk_state=%d\n",
__func__, sk->sk_state);
break;
} /* It _is_ possible, that we have something out-of-order _after_ FIN.
* Probably, we should reset in this case. For now drop them.
清理乱序队列、状态更改时可能要唤醒相关进程 乱序队列数据直接干掉哦!!! 之前正常的序列还在 ----
*/
__skb_queue_purge(&tp->out_of_order_queue);
if (tcp_is_sack(tp))//开启了SACK选项
tcp_sack_reset(&tp->rx_opt);//清除SACK选项信息,因为对端已经不会再发送数据了
sk_mem_reclaim(sk); if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk);//状态更改时可能要唤醒相关进程 ---sock_def_wakeup /* Do not send POLL_HUP for half duplex close. */
if (sk->sk_shutdown == SHUTDOWN_MASK ||
sk->sk_state == TCP_CLOSE)
sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_HUP);
else
sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
}
}

  如果收包队列中只有无数据的FIN包,则收包系统会调用返回0,这时应用进程就知道收到了FIN,对端关闭了连接。如果队列中有数据则系统调用会返回大于0的值 ;

然后 应用进程如果调用事件监听函数(如epoll_wait /poll)等待数据到来,这时会调用tcp_poll函数:

其实现如下:

/*
* Wait for a TCP event.
*
* Note that we don't need to lock the socket, as the upper poll layers
* take care of normal races (between the test and the event) and we don't
* go look at any of the socket buffers directly.
*/
unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
unsigned int mask;
struct sock *sk = sock->sk;
const struct tcp_sock *tp = tcp_sk(sk);
int state; sock_rps_record_flow(sk); sock_poll_wait(file, sk_sleep(sk), wait); state = sk_state_load(sk);
if (state == TCP_LISTEN)
return inet_csk_listen_poll(sk); /* Socket is not locked. We are protected from async events
* by poll logic and correct handling of state changes
* made by other threads is impossible in any case.
*/ mask = 0; /*
* POLLHUP is certainly not done right. But poll() doesn't
* have a notion of HUP in just one direction, and for a
* socket the read side is more interesting.
*
* Some poll() documentation says that POLLHUP is incompatible
* with the POLLOUT/POLLWR flags, so somebody should check this
* all. But careful, it tends to be safer to return too many
* bits than too few, and you can easily break real applications
* if you don't tell them that something has hung up!
*
* Check-me.
*
* Check number 1. POLLHUP is _UNMASKABLE_ event (see UNIX98 and
* our fs/select.c). It means that after we received EOF,
* poll always returns immediately, making impossible poll() on write()
* in state CLOSE_WAIT. One solution is evident --- to set POLLHUP
* if and only if shutdown has been made in both directions.
* Actually, it is interesting to look how Solaris and DUX
* solve this dilemma. I would prefer, if POLLHUP were maskable,
* then we could set it on SND_SHUTDOWN. BTW examples given
* in Stevens' books assume exactly this behaviour, it explains
* why POLLHUP is incompatible with POLLOUT. --ANK
*
* NOTE. Check for TCP_CLOSE is added. The goal is to prevent
* blocking on fresh not-connected or disconnected socket. --ANK
*/
if (sk->sk_shutdown == SHUTDOWN_MASK || state == TCP_CLOSE)
mask |= POLLHUP;
if (sk->sk_shutdown & RCV_SHUTDOWN)
mask |= POLLIN | POLLRDNORM | POLLRDHUP; /* Connected or passive Fast Open socket? */
if (state != TCP_SYN_SENT &&
(state != TCP_SYN_RECV || tp->fastopen_rsk)) {
int target = sock_rcvlowat(sk, 0, INT_MAX); if (tp->urg_seq == tp->copied_seq &&
!sock_flag(sk, SOCK_URGINLINE) &&
tp->urg_data)
target++; if (tp->rcv_nxt - tp->copied_seq >= target)
mask |= POLLIN | POLLRDNORM; if (!(sk->sk_shutdown & SEND_SHUTDOWN)) {
if (sk_stream_is_writeable(sk)) {
mask |= POLLOUT | POLLWRNORM;
} else { /* send SIGIO later */
sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); /* Race breaker. If space is freed after
* wspace test but before the flags are set,
* IO signal will be lost. Memory barrier
* pairs with the input side.
*/
smp_mb__after_atomic();
if (sk_stream_is_writeable(sk))
mask |= POLLOUT | POLLWRNORM;
}
} else
mask |= POLLOUT | POLLWRNORM; if (tp->urg_data & TCP_URG_VALID)
mask |= POLLPRI;
}
/* This barrier is coupled with smp_wmb() in tcp_reset() */
smp_rmb();
if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
mask |= POLLERR; return mask;
}

if (sk->sk_shutdown & RCV_SHUTDOWN)
mask |= POLLIN | POLLRDNORM | POLLRDHUP;

其代码中会判断 其 RCV_SHUTDOWN SEND_SHUTDOWN等状态 然后返回对应的掩码!!!

所以POLLRDHUP可以检测到文件是否挂起!!!-----也就是fd 是否被close

tcp 接收被动关闭 fin的更多相关文章

  1. TCP连接与关闭

    1.建立连接协议(三次握手) (1)客户端发送一个带SYN标志的TCP报文到服务器.这是三次握手过程中的报文1. (2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和S ...

  2. TCP连接建立和关闭中的疑难点

    TCP连接建立和关闭中的疑难点 作者:夏语岚    撰写日期:2011-10-29 近日在阅读<Unix网络编程>,以前在<计算机网络>课程中学到TCP,当时只是简单了解了TC ...

  3. Linux:TCP状态/半关闭/2MSL/端口复用

    TCP状态 CLOSED:表示初始状态. LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接. SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行 ...

  4. TCP连接建立与关闭

    http://hi.baidu.com/psorqkxcsfbbghd/item/70f3bd91943b9248f14215cd TCP连接建立与关闭 TCP 是一个面向连接的协议,无论哪一方向另一 ...

  5. TCP连接的关闭

    原文地址:http://lib.csdn.net/article/computernetworks/17264   TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们 ...

  6. 深入理解TCP建立和关闭连接

    建立连接: 理解:窗口和滑动窗口TCP的流量控制TCP使用窗口机制进行流量控制什么是窗口?连接建立时,各端分配一块缓冲区用来存储接收的数据,并将缓冲区的尺寸发送给另一端 接收方发送的确认信息中包含了自 ...

  7. TCP中异常关闭链接的意义 异常关闭的情况

    终止一个连接的正常方式是发送FIN. 在发送缓冲区中 所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失. 但我们有时也有可能发送一个RST报文段而不是F IN来中途关闭一个连接.这称为 ...

  8. TCP协议: SYN ACK FIN RST PSH URG 详解

    TCP的三次握手是怎么进行的了:发送端发送一个SYN=1,ACK=0标志的数据包给接收端,请求进行连接,这是第一次握手:接收端收到请求并且允许连接的话,就会发送一个SYN=1,ACK=1标志的数据包给 ...

  9. 五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

    55.1 TCP 连接和关闭过程 55.1.1 介绍 建立连接的过程就是三次握手的过程:客户端发送 SYN 报文给服务器,服务器回复 SYN+ACK 报文,客户机再发送 ACK 报文. 关闭连接的过程 ...

随机推荐

  1. BOOST库 消息队列

    直接贴实验代码: /******* boost 消息队列 **********/ #if 1 #include <boost/thread/thread.hpp> #include < ...

  2. socket php

    $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); socket_bind($socket,'0.0.0.0',6666); while(tru ...

  3. samesite-cookie详解(译文)

    Cookie是便于向网站添加持久化状态的方式之一.随着时间推移,它们的能力得到了扩展和进化,也造成了很多历史遗留问题.为了解决这个问题,浏览器产商(包括Chrome,Firefox,和Edge)改变了 ...

  4. sql分页 一条语句搞定

    select top 每页条数 * from ( SELECT ROW_NUMBER() OVER (ORDER BY id desc) AS RowNumber,* FROM Article  条件 ...

  5. Java中的5大队列,你知道几个?

    本文已收录至 https://github.com/vipstone/algorithm <算法图解>系列. 通过前面文章的学习<一文详解「队列」,手撸队列的3种方法!>我们知 ...

  6. 立即执行函数 - Js函数笔记

    立即执行函数 定义:此类函数没有声明,在执行一次后即释放,适合做初始化. 针对初始化功能的函数,同时遵循一句话,只有表达式才能被执行符号执行 1.(function() {...}()); - W3C ...

  7. docker compose 用法

    目录 docker compose的使用场景 一个基本的demo演示 找一个目录,在其中创建一个python文件app.py 在相同的文件夹下,创建requirements.txt文件 在相同的文件夹 ...

  8. redis限频

    做法 使用redis的lua脚本功能来限频 在redis中定时刷新系统时间来作为一个全局的时钟 限频脚本: /** * 获取令牌的lua脚本 */ public final static String ...

  9. Java学习的第四十四天

    1.例5.4将二维数组的行列互换 public class cjava { public static void main(String []args) { int [][]a=new int [][ ...

  10. Learn day10 锁

    1.锁 # ### 锁 from multiprocessing import Lock,Process import json,time """ # 创建一把锁 loc ...