假设客户端执行主动打开,已经经过第一次握手,即发送SYN包到服务器,状态变为SYN_SENT,服务器收到该包后,回复SYN+ACK包,客户端收到该包,进行主动打开端的第二次握手部分;流程中涉及到的函数和细节非常多,本篇只对主流程予以分析;

在ESTABLISHED和TIME_WAIT以外的状态时接收到包,会调用tcp_rcv_state_process函数来处理,处理部根据不同状态做对应处理,如果处于SYN_SENT状态,则会调用tcp_rcv_synsent_state_process函数进入和该状态的核心处理流程;

 /*
* This function implements the receiving procedure of RFC 793 for
* all states except ESTABLISHED and TIME_WAIT.
* It's called from both tcp_v4_rcv and tcp_v6_rcv and should be
* address independent.
*/ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
switch (sk->sk_state) {
case TCP_CLOSE:
goto discard; case TCP_LISTEN:
/* 省略部分代码 */ case TCP_SYN_SENT:
tp->rx_opt.saw_tstamp = ;
skb_mstamp_get(&tp->tcp_mstamp); /* syn_sent状态处理 */
queued = tcp_rcv_synsent_state_process(sk, skb, th); /* 下面返回值> 0,调用者需要发送rst */
if (queued >= )
return queued; /* Do step6 onward by hand. */
/* 处理紧急数据 */
tcp_urg(sk, skb, th);
__kfree_skb(skb); /* 检查是否有数据要发送 */
tcp_data_snd_check(sk);
return ;
} }

tcp_rcv_synsent_state_process函数为SYN_SENT状态输入数据包的核心处理函数,这里我们只关系服务器发送SYN+ACK的处理流程,其他部分暂且省略;函数主要完成以下几项工作:解析tcp选项;对序号,时间错,标志位等合法性做检查;调用函数tcp_ack对ack进行慢速路径处理;进行窗口,时间戳选项,MSS最大报文段,PMTU路径发现等初始化或更新;上述流程完毕后调用tcp_finish_connect完成连接建立,将连接状态更新为TCP_ESTABLISHED,若需要则开启保活定时器,以及对是否走快速路径做判断和标记;连接建立成功会进行一些唤醒操作;最后进行ACK模式是否切换的判断和操作,决定如何回复ACK;

 static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_fastopen_cookie foc = { .len = - };
int saved_clamp = tp->rx_opt.mss_clamp;
bool fastopen_fail; /* 解析tcp选项 */
tcp_parse_options(skb, &tp->rx_opt, , &foc); /* 有时间戳,有回显,记录回显 */
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
tp->rx_opt.rcv_tsecr -= tp->tsoffset; if (th->ack) {
/* rfc793:
* "If the state is SYN-SENT then
* first check the ACK bit
* If the ACK bit is set
* If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
* a reset (unless the RST bit is set, if so drop
* the segment and return)"
*/
/* ack <= una || ack > nxt,ack确认序号错误 */
if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
goto reset_and_undo; /* 对端时间戳不合法 */
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
!between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp,
tcp_time_stamp)) {
NET_INC_STATS(sock_net(sk),
LINUX_MIB_PAWSACTIVEREJECTED);
goto reset_and_undo;
} /* Now ACK is acceptable.
*
* "If the RST bit is set
* If the ACK was acceptable then signal the user "error:
* connection reset", drop the segment, enter CLOSED state,
* delete TCB, and return."
*/ /* 设置了rst标志 */
if (th->rst) {
tcp_reset(sk);
goto discard;
} /* rfc793:
* "fifth, if neither of the SYN or RST bits is set then
* drop the segment and return."
*
* See note below!
* --ANK(990513)
*/
/* 未设置syn标志 */
if (!th->syn)
goto discard_and_undo; /* rfc793:
* "If the SYN bit is on ...
* are acceptable then ...
* (our SYN has been ACKed), change the connection
* state to ESTABLISHED..."
*/
/* ecn标记 */
tcp_ecn_rcv_synack(tp, th); /* 记录窗口更新时数据包序号 */
tcp_init_wl(tp, TCP_SKB_CB(skb)->seq); //输入ack处理,使用慢速路径,可能会更新发送窗口
tcp_ack(sk, skb, FLAG_SLOWPATH); /* Ok.. it's good. Set up sequence numbers and
* move to established.
*/ /* 更新下一个要接收的序号 */
tp->rcv_nxt = TCP_SKB_CB(skb)->seq + ;
/* 更新窗口左边,即窗口中最小的序号 */
tp->rcv_wup = TCP_SKB_CB(skb)->seq + ; /* RFC1323: The window in SYN & SYN/ACK segments is
* never scaled.
*/
/* 获取发送窗口 */
tp->snd_wnd = ntohs(th->window); /* 没有窗口扩大因子 */
if (!tp->rx_opt.wscale_ok) {
/* 设置为0 */
tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = ;
/* 设置最大值 */
tp->window_clamp = min(tp->window_clamp, 65535U);
} /* 有时间戳选项 */
if (tp->rx_opt.saw_tstamp) {
/* 在syn中有时间戳选项 */
tp->rx_opt.tstamp_ok = ; /* tcp首部需要增加时间戳长度 */
tp->tcp_header_len =
sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED; /* mss需要减去时间戳长度 */
tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; /* 设置回显时间戳 */
tcp_store_ts_recent(tp);
} else {
/* 记录tcp首部长度 */
tp->tcp_header_len = sizeof(struct tcphdr);
} /* 有sack选项,开启了fack算法,则打标记 */
if (tcp_is_sack(tp) && sysctl_tcp_fack)
tcp_enable_fack(tp); /* MTU探测相关初始化 */
tcp_mtup_init(sk);
/* 计算mss */
tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
/* 初始化rcv_mss */
tcp_initialize_rcv_mss(sk); /* Remember, tcp_poll() does not lock socket!
* Change state from SYN-SENT only after copied_seq
* is initialized. */ /* 记录用户空间待读取的序号 */
tp->copied_seq = tp->rcv_nxt; smp_mb(); /* 连接建立完成的状态改变和相关初始化 */
tcp_finish_connect(sk, skb); /* fastopen处理 */
fastopen_fail = (tp->syn_fastopen || tp->syn_data) &&
tcp_rcv_fastopen_synack(sk, skb, &foc); /* 连接未在关闭态,则进行一些唤醒操作 */
if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk);
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
}
if (fastopen_fail)
return -; /* 有写数据请求|| 收包accept || 延迟ack */
if (sk->sk_write_pending ||
icsk->icsk_accept_queue.rskq_defer_accept ||
icsk->icsk_ack.pingpong) {
/* Save one ACK. Data will be ready after
* several ticks, if write_pending is set.
*
* It may be deleted, but with this feature tcpdumps
* look so _wonderfully_ clever, that I was not able
* to stand against the temptation 8) --ANK
*/
/* 标志ack调度 */
inet_csk_schedule_ack(sk);
/* 进入快速ack模式 */
tcp_enter_quickack_mode(sk);
/* 设置延迟ack定时器 */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
TCP_DELACK_MAX, TCP_RTO_MAX); discard:
tcp_drop(sk, skb);
return ;
} else {
/* 回复ack */
tcp_send_ack(sk);
}
return -;
} /* 省略了rst以及syn状态的代码 */
}

以上流程中比较重要的两个函数为tcp_ack与tcp_finish_connect;

tcp_ack函数因为涉及内容较多,此处不做分析,后续单独分析后在补充链接;

tcp_finish_connect函数进行连接建立完成的处理,函数将连接状态更改为TCP_ESTABLISHED,检查并重建路由项,初始化拥塞控制,若需要则开启保活定时器,以及对是否走快速路径做判断和标记

 /* tcp连接建立完成 */
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk); /* 设置为已连接状态 */
tcp_set_state(sk, TCP_ESTABLISHED);
/* 记录最后一次收到包的时间戳 */
icsk->icsk_ack.lrcvtime = tcp_time_stamp; if (skb) {
/* 设置接收路由缓存 */
icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
security_inet_conn_established(sk, skb);
} /* Make sure socket is routed, for correct metrics. */
/* 检查或重建路由 */
icsk->icsk_af_ops->rebuild_header(sk); /* 初始化度量值 */
tcp_init_metrics(sk); /* 初始化拥塞控制 */
tcp_init_congestion_control(sk); /* Prevent spurious tcp_cwnd_restart() on first data
* packet.
*/
/* 记录最后一次发送数据包的时间 */
tp->lsndtime = tcp_time_stamp; tcp_init_buffer_space(sk); /* 开启了保活,则打开保活定时器 */
if (sock_flag(sk, SOCK_KEEPOPEN))
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp)); /* 设置预测标志,判断快慢路径的条件之一 */
if (!tp->rx_opt.snd_wscale)
__tcp_fast_path_on(tp, tp->snd_wnd);
else
tp->pred_flags = ; }

TCP主动打开 之 第二次握手-接收SYN+ACK的更多相关文章

  1. TCP被动打开 之 第二次握手-发送SYN+ACK

    假定客户端执行主动打开,发送syn包到服务器,服务器执行完该包的第一次握手操作后,调用af_ops->send_synack向客户端发送syn+ack包,该回调实际调用tcp_v4_send_s ...

  2. TCP主动打开 之 第一次握手-发送SYN

    tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端: tcp_v4_connect为发起连接主流程,首先对必要参数进行检查 ...

  3. TCP主动打开 之 第三次握手-发送ACK

    假定客户端执行主动打开,并且已经收到服务器发送的第二次握手包SYN+ACK,在经过一系列处理之后,客户端发送第三次握手包ACK到服务器:其流程比较简单,主要是分配skb,初始化ack包并发送:需要注意 ...

  4. TCP被动打开 之 第一次握手-接收SYN

    假定客户端执行主动打开,服务器执行被动打开,客户端发送syn包到服务器,服务器接收该包,进行建立连接请求的相关处理,即第一次握手:本文主要分析第一次握手中被动打开端的处理流程,主动打开端的处理请查阅本 ...

  5. TCP被动打开 之 第三次握手-接收ACK

    假定客户端主动打开,发送syn包到服务器,服务器创建连接请求控制块加入到队列,进入TCP_NEW_SYN_RECV 状态,发送syn+ack给客户端,并启动定时器,等待客户端回复最后一个握手ack: ...

  6. 真的懂了:TCP协议中的三次握手和四次挥手(关闭连接时, 当收到对方的FIN报文时, 仅仅表示对方不在发送数据了, 但是还能接收数据, 己方也未必全部数据都发送对方了。相当于一开始还没接上话不要紧,后来接上话以后得让人把话讲完)

    一.TCP报文格式 下面是TCP报文格式图: (1) 序号, Seq(Sequence number), 占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记. (2) 确 ...

  7. TCP 三次握手(相当于寄信需要回执,第一次握手:我寄给你一封信。第二次握手:你回我一封信。第三次握手:我再给你一个回执,这样你才能确认我收到信了)

    TCP 连接是通过三次握手进行初始化的.三次握手的目的是同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.以下步骤概述了通常情况下客户端计算机联系服务器计算机的过程: 1. 客户端向服务器发送 ...

  8. TCP/IP具体解释--三次握手和四次握手 Dos攻击

    TCP连接的状态图 TCP建立连接的三次握手过程,以及关闭连接的四次握手过程 贴一个telnet建立连接,断开连接的使用wireshark捕获的packet截图. 1.建立连接协议(三次握手) (1) ...

  9. http协议tcp协议ip协议三次握手四次挥手,为什么三次握手,为什么四次挥手,sockete套接字理解

    1.1 TCP是什么? TCP是Tranfer Control Protocol的简称,TCP协议是一种面向连接的.可靠的.基于字节流的运输层通信协议.通过TCP协议传输,得到的是一个顺序的无差错的数 ...

随机推荐

  1. PHP之配置

    1) 错误日志 一.相关配置 需要将php.ini中的配置指令做如下修改: . error_reporting = E_ALL ;将会向PHP报告发生的每个错误 . display_errors = ...

  2. CSS3总结三:文字(text)/字体、文本、文本装饰、多列

    Text-Decoration text-shadow text-decoration Font font font-face Text 常用Text属性 Multi-column Multi-col ...

  3. element-ui中点击菜单,改变当前菜单背景颜色

    需求: vue项目中,点击左侧菜单,tags页显示当前打开的菜单,并且高亮显示当前菜单 实现效果: 实现代码:在vuex里面定义tags存放所有打开的菜单,和当前打开的索引curtagsIndex:, ...

  4. 退居三线iOS开发的自主开发历程

    忙前忙后,一切终将步入正轨,在忙也要抽出时间思考自己的事情 推荐一篇简书(https://www.jianshu.com/u/8367278ff6cf)讲解很官方 Metal体验 学习了一些基础的视频 ...

  5. MySQL的基本操作一

    本文主要涉及到的SQL知识点包括CREATE创建数据库和表.INSERT插入数据.SUM()求和.GROUP BY分组.DATE_FORMAT()格式化日期.ORDER BY排序.COUNT()统计行 ...

  6. centos7中的网卡名称相关知识

    转载自https://www.cnblogs.com/zyd112/p/8143464.html 一致性网络设备命名(Consistent Network Device Naming) 背景介绍: 在 ...

  7. 在values中添加colors.xml

    如何在values中添加colors.xml文件?按钮上的文字,背景色的动态变化的xml放在res/drawable里,现在我就说静态的xml文件吧. res/values/colors.xml< ...

  8. django_celery_results安装的坑

    前言  在Celery4.0之前的版本中,有一个专门供Django使用的Celery版本django-celery.但现在Celery已经统一为一个版本,所以直接安装原生的Celery即可.这里就暂时 ...

  9. The Tower HDU - 6559 (解析几何)

    The Tower HDU - 6559 The Tower shows a tall tower perched on the top of a rocky mountain. Lightning ...

  10. BZOJ 1006 完美消除序列&最大势算法&弦图

    K国是一个热衷三角形的国度,连人的交往也只喜欢三角原则.他们认为三角关系:即AB相互认识,BC相互认识,CA相互认识,是简洁高效的.为了巩固三角关系,K国禁止四边关系,五边关系等等的存在.所谓N边关系 ...