前言:TCP是传输层协议,实现了一种可靠的通信。它从不同角度提供了多种可靠性保障措施来为网络传输提供确定性。连接性就是其中之一,不像UDP的无连接状态,TCP在数据传输之前会进行连接,只有双方都协调完成后,才会进行数据传输;同样的,在结束时,又会断开连接,通告传输的完成;在数据传输过程中,又会对每个传输进行确认。更多的可靠性措施在后面的系列中会仔细说明,这一篇,重点从连接这个角度看看TCP协议。

一. TCP状态机的运转

二. TCP的连接与断开

2.1 TCP连接处理

2.1.1 listen()调用

listen()系统调用是服务器侧编程的一个必要动作,主要是把创建的主动socket变成被动socket,那么这里主动和被动有什么区别呢?通过代码一探里面的操作:当调用socket()时,会调用对应的协议无关层的接口

sock->ops->listen(sock, backlog);

这里的ops->listen最终调用的就是inet_listen(),在这个函数中,我们看到

old_state = sk->sk_state;
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))goto out;/* Really, if the socket is already in listen state
* we can only allow the backlog to be adjusted.
*/
if (old_state != TCP_LISTEN) {
err = inet_csk_listen_start(sk, backlog);
if (err)goto out;
}sk->sk_max_ack_backlog = backlog;
err = 0;

在开始就是检查套接字的状态,如果已经是listen状态,则只要更改backlog的值;如果不是listen状态,就启动设置listen。看这个inet_csk_listen_start(sk, backlog);,里面把套接字状态设置为listen:

sk->sk_state = TCP_LISTEN;

所以,所说的把主动套接字变成被动套接字主要就是改变TCP的初始状态,从closed状态转为listen状态。从第一节的状态机上可以看出不同的起始状态后续的处理也不同。

2.1.2 TCP发起连接

TCP的连接过程主要是3次握手,如下图所示:

这是一张截取《TCP/IP 详解》中的握手图,其中的序列号注意一下。左端为客户端,右端为服务端。

一般来说是客户端主动发起连接,而服务端则接收并建立连接。服务端是在调用connect()时发起SYN分节,那接下来就来看看这个connect系统调用里面都做了哪些事:

err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,sock->file->f_flags);

一样的,这里的ops->connect在INET域就是inet_stream_connect(),在这个函数中,看到在检查了套接字还没有连接的前提下,就调用TCP的连接函数:

err = sk->sk_prot->connect(sk, uaddr, addr_len);
if (err < 0)goto out;

这里的sk_prot->connect就是tcp_v4_connect(),在这个函数中,查找路由,填充各种信息等,最后调用tcp_connect(),这个函数主要就是构建一个SYN报文发送出去。后面是tcp_transmit_skb(),填充一下消息头等各种操作,最后发送到队列中:

err = icsk->icsk_af_ops->queue_xmit(skb, 0);
if (likely(err <= 0))return err;

最后会调用icsk_af_ops->queue_xmit()将数据包往IP层发送,那么这个queue_xmit是什么呢?如果我们去搜这个icsk_af_ops注册的地方,就会发现在TCP操作集的初始化tcp_v4_init_sock()中,icsk_af_ops->queue_xmit()被设置为:

icsk->icsk_af_ops = &ipv4_specific;const struct inet_connection_sock_af_ops ipv4_specific = {.queue_xmit    = ip_queue_xmit,.send_check    = tcp_v4_send_check,.rebuild_header    = inet_sk_rebuild_header,.conn_request      = tcp_v4_conn_request,.syn_recv_sock     = tcp_v4_syn_recv_sock,.remember_stamp    = tcp_v4_remember_stamp,.net_header_len    = sizeof(struct iphdr),.setsockopt    = ip_setsockopt,.getsockopt    = ip_getsockopt,.addr2sockaddr     = inet_csk_addr2sockaddr,.sockaddr_len      = sizeof(struct sockaddr_in),.bind_conflict     = inet_csk_bind_conflict,
#ifdef CONFIG_COMPAT.compat_setsockopt = compat_ip_setsockopt,.compat_getsockopt = compat_ip_getsockopt,
#endif
};

可以看出来,最后的queue_xmit就是ip_queue_xmit()。然后就交给IP层处理。

那么这个tcp_v4_init_sock()是什么时候初始化的呢?我们看到这个函数是作为TCP操作集init的回调函数。它是在创建socket的时候,初始化的。系统调用socket会调用inet_init(),在这个函数中:

if (sk->sk_prot->init) {err = sk->sk_prot->init(sk);
if (err)sk_common_release(sk);

当创建的是流式套接字时,对应于INET族的就是TCP协议,就会调用tcp_v4_init_sock()

这就是客户端主动发起连接的过程,当然里面还有复杂的多种其他任务的处理,自行根据需要分析。

2.1.3 TCP接收连接

对于tcp接收,首先要确认它的处理函数—tcp_v4_rcv(),这是根据接收的报文头层层传递上来的,具体的过程,在讲设备无关层是具体说明。忽略其他的处理,然后就到了tcp_v4_do_rcv(),在这里进行报文的实际处理,主要分为三条线:

  1. 对于已经建立的tcp连接,此时接收的就是数据报文,进入到内层处理:

    if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */TCP_CHECK_TIMER(sk);if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}TCP_CHECK_TIMER(sk);return 0;
    }

    对于内层的tcp_rcv_established()就不进行详细说明了,对于它的工作,很显然的是拷贝报文到用户态应用程序,是在里面的tcp_copy_to_iovec()中做的,就是拷贝报文。

  2. 对于是tcp listen状态的,则进入建立连接的处理。最终进入到tcp_rcv_state_process()状态机处理。

  3. 对于不是以上两种状态的,则直接进入到状态机处理。

从以上可以看出,对于接收,最重要的处理过程就是tcp状态机。理解了状态机的转换过程,也就明白了代码处理的逻辑。

2.2 TCP断开处理

tcp的断开过程形象的说是4次挥手的过程,如下图所示:

这里一共有4次交互过程,关于为什么中间的ack M+1和FIN N不合并成一条呢?在《unix 网络编程》中作者曾提到过,其中一个原因就是另一方暂时不想断开,也就是说tcp是双工的,允许一个方向断开,而另个方向暂时不断开的情形,就是所说的半关闭状态。

其中主动关闭的一方可以在调用close()时发送FIN报文,开始关闭过程。如果调用shutdown()会触发单向关闭,可以去查看源代码:在用户调用close()时,最终根据套接字类型,会找到tcp_close()函数。在其中最后调用了tcp_send_fin()发送FIN报文。然后就是接收报文进入状态机进行处理。

对于断开连接有一个问题:TIME_WAIT状态会保持2MSL时间,其中的解释如下(取自网络)

主动发起关闭连接的操作的一方将达到TIME_WAIT状态,而且这个状态要保持Maximum Segment Lifetime的两倍时间。为什么要这样做而不是直接进入CLOSED状态?原因有二:一、保证TCP协议的全双工连接能够可靠关闭二、保证这次连接的重复数据段从网络中消失先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。

2.3 异常处理

  1. 超时

    由于tcp是可靠的协议,因此,在一方发送数据后,期望能够确认数据发送成功或者失败。在linux中提供了重传定时器,用于在数据超时的时候进行重传。比如在tcp_connect()中,发送SYN连接时,设置的超时定时器:

    inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,inet_csk(sk)->icsk_rto, TCP_RTO_MAX);

三. TCP连接的特殊状态

3.1 半打开状态

半打开状态是指连接的一方由于异常关闭,而另一端对此并不知情的场景。常见的触发原因有:主机掉电等。在这个时候,另一端如果发送数据,必然是不会通的,因为此时掉电重启的主机已经没有了连接的信息,会回复一个RST报文复位连接。对于检测半打开状态,可以使用keepalive保活定时器,默认是120分钟。也可以自己实现心跳来保持。

3.2 半关闭状态

由于tcp是全双工的,因此双方都可以关闭自己的连接。而有一种特殊的情况就是:主动发起关闭的一方发送FIN后,被动一方对其进行了确认,然而并没有接着发送自己的FIN,此时,表示被动一方仍然想要传输数据,当然,主动的一方虽然关闭了发送通道,但是仍然可以接收被动方的数据。

转载于:https://www.cnblogs.com/yhp-smarthome/p/7102488.html

linux TCP协议(1)---连接管理与状态机相关推荐

  1. 4-5:TCP协议之连接管理机制(三次握手、四次挥手详解)

    文章目录 一:TCP三次握手过程和状态变迁 (1)三次握手过程和状态变迁过程详解 (2)为什么必须要三次握手? A:只有三次握手才可以阻止重复历史连接的初始化(主要原因) B:同步双方初始序列号 C: ...

  2. TCP协议-长连接和短连接

    一 前言 TCP在真正开始进行数据传输之前,Server 和 Client 之间必须建立一个连接.当数据传输完成后,双方不再需要这个连接时,就可以释放这个连接. TCP连接的建立是通过三次握手,而连接 ...

  3. TCP系列04—连接管理—3、TCP连接的半打开和半关闭

    ====================================================|| 欢迎讨论技术的可以相互加微信:windgs (请备注csdn+xx职业) ======== ...

  4. 深入理解TCP协议的连接状态与可靠机制

    TCP是一个巨复杂的协议,因为他要解决很多问题,而这些问题又带出了很多子问题和阴暗面.所以学习TCP本身是个比较痛苦的过程,但对于学习的过程却能让人有很多收获. 一.TCP协议的定义 TCP在网络OS ...

  5. Linux——TCP协议与相关套接字编程

    一.TCP协议概念 与UDP协议相同,TCP协议也是应用在传输层的协议.虽然都是应用在传输层,但是使用方式和应用场景上大不一样.TCP协议具有:有连接(可靠).面向字节流的特点. (一).有连接 所谓 ...

  6. TCP的运输连接管理——TCP的连接建立

    TCP的连接建立 TCP运输连接有以下三个阶段: 建立TCP连接 数据传送 释放TCP连接 三报文握手 一开始,两端的TCP进程都处于关闭状态. 然后,TCP服务器首先创建传输控制块,用来存储控制块的 ...

  7. TCP系列05—连接管理—4、TCP连接的ISN、连接建立超时及TCP的长短连接

    一.TCP连接的ISN         之前我们说过初始建立TCP连接的时候的系列号(ISN)是随机选择的,那么这个系列号为什么不采用一个固定的值呢?主要有两方面的原因 防止同一个连接的不同实例(di ...

  8. linux tcp 异常断开连接,TCP 异常断开

    首先感谢你这么详细的提问! 如果业务每个请求处理时间都足够快,支持8万设备在线是没问题的. 但是如果业务有一点慢,比如数据库操作一次需要0.05秒,假设8万设备每秒产生5000个操作数据库的请求,那么 ...

  9. 【计算机网络微课堂】5.8 TCP的运输连接管理

    目录 TCP连接建立 TCP使用"三报文"建立连接 "三报文"是否多余? TCP连接释放 TCP通过"四报文"挥手释放连接 客户进程有必要时 ...

  10. 计算机网络-TCP的运输连接管理(三次握手,四次挥手)补充一下为什么不能将四次挥手改为三次挥手

    hello,朋友们.今天咱们分享一下TCP连接建立与释放问题(三次握手与四次挥手问题) 1.简单介绍 基础知识了解(仅代表个人简单理解) SYN        同步(一个信号   代表自己的状态) F ...

最新文章

  1. python——元素列表基础
  2. 第三章JavaScript 内置对象
  3. 微脉java面试,微脉医疗开放平台
  4. java 锁旗标_Java多线程
  5. 阿里重磅开源首款自研科学计算引擎Mars,揭秘超大规模科学计算
  6. 小甲鱼Python第二十二讲课后习题
  7. 告别并不遥远的儿时,抬眼期待未来
  8. Unreal Engine 4添加自定义Settings到项目设置
  9. 三星Galaxy Note 10最新消息:将取消所有实体按键
  10. 怎么找服务器物理地址吗,服务器怎么知道物理地址是多少
  11. VIM空格和TAB转换
  12. java数组验证哥德巴赫猜想_java 验证哥德巴赫猜想
  13. 水晶球 crystal ball
  14. java ts流,Windows 合并多个*.ts文件
  15. html星星连线特效代码,js实现飞入星星特效代码
  16. FAT文件系统文件存储与删除原理分析
  17. 社群运营普遍存在的五个问题
  18. 利用ipconfig命令查看IP及释放和重获IP
  19. python可视化的优势_「数据可视化」数据可视化的优势有哪些?
  20. BeanUtils之populate的用法

热门文章

  1. 苹果mac视频编辑和制作软件:Premiere Pro
  2. 如何在Mac上创建和使用符号链接?
  3. 18 Strings for Mac(Xcode文件翻译工具)
  4. CAD迷你画图 for mac
  5. 手动实现一个vue的mvvm,思路解析
  6. 【SDOI2014】数表
  7. 我是如何在自学编程9个月后找到工作的
  8. Python基础之赋值运算符
  9. C++中类中常规变量、const、static、static const(const static)成员变量的声明和初始化...
  10. Easy Building Redis-cluster (轻松搭建reids集群)