深入理解 TCP IP 协议 -TCP 建立与终止连接

本贴最后更新于 2185 天前,其中的信息可能已经时移世易

深入理解 TCP/IP 协议-TCP 建立与终止连接

一、引言

  TCP 是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。连接创建与终止的状态变化图如下:

二、三次握手建立连接

过程如下:

  • 客户端发送一个 SYN 数据包指明客户端打算连接服务器的端口,初始化序号(ISN)为 m。
  • 服务器发回包含服务器的 ISN 作为应答(值为 n)。同时,将确认序号设置成客户端 ISN+1(m+1)来作为对客户端 SYN 的确认。
  • 客户端发送一个 ACK 数据包,ack=n+1, 作为对服务器的 SYN 的确认。

1. 为什么是三次握手,而不是两次

  网络是不可靠的,数据包是可能丢失的。假设没有第三次确认,客户端向服务端发送了 SYN,请求建立连接。由于延迟,服务端没有及时收到这个包。于是客户端重新发送一个 SYN 包。回忆一下介绍 TCP 首部时提到的序列号,这两个包的序列号显然是相同的。假设服务端接收到了第二个 SYN 包,建立了通信,一段时间后通信结束,连接被关闭。这时候最初被发送的 SYN 包刚刚抵达服务端,服务端又会发送一次 ACK 确认。由于两次握手就建立了连接,此时的服务端就会建立一个新的连接,然而客户端觉得自己并没有请求建立连接,所以就不会向服务端发送数据。从而导致服务端建立了一个空的连接,白白浪费资源。

  TCP 是双通道,需要双向确定。只有两次握手,客户端知道了服务器收到了,服务器不知道客户端收到了,联想打电话。通讯系统中的占拜庭将军问题。

2. 最大报文段长度

  最大报文段长度(MSS)表示 TCP 传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的 MSS。在三次握手的时候 SYN 的 TCP 首部中的可选字段确定。以太网的默认长度为 1460。

三、四次握手关闭连接(正常状态)

  建立一个连接需要三次握手,而终止一个连接要经过 4 次握手。这由 TCP 的半关闭 (half-close) 造成的。一个 TCP 连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。

  • 主动方想要关闭连接,发送 FIN 包给被动方,序号为 m
  • 被动方接收到主动方发送的 FIN 包,知道了对方要关闭连接,发送 ACK 确认包,序号 m+1。主动方连接关闭。
  • 等待片刻(处于半关闭状态),在此期间(finwait2,closewait)。被动方发送最后的数据,主动方接收最后的数据。
  • 被动方确认要关闭连接,发送 FIN 包。序号 n。
  • 主动方等待片刻(接收网络中,还未到达的数据包),发送 ACK 确认包。序号 n+1。到此连接关闭。

1.TCP 的半关闭状态

  TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。如主动方处于 fin_wait2 状态。

2.TIME_WAIT 状态

  TIMEWAIT 状态也称为 2MSL 等待状态。每个具体 TCP 实现必须选择一个报文段最大生存时间 MSL( Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。因为 TCP 报文段以 IP 数据报在网络内传输,而 IP 数据报则有限制其生存时间的 TTL 字段。在实际应用中,对 I P 数据报 TTL 的限制是基于跳数,而不是定时器。   在处于 2MSL 等待状态的 socket(客户端 IP 与端口,服务器 IP 与端口) 不能再被使用。但在实际的使用中,允许一个新的连接请求到达仍处于 timewait 状态的连接,只要新的序号大于该连接的前一个连接的最后序号。

四、正常状态抓包

下面是一次完整的 tcp 建立连接,发送数据,关闭连接过程

该过程为,3 次握手建立连接,一次数据发送,4 次握手关闭连接

五、异常情况

出现异常的时候,服务器通常通过复位报文来通告,复位报文为 tcp 数据包类型设置为 rst。

1. 连接超时或到达不存在的端口 / 服务器

当服务器端没有开或网络问题,会出现连接超时的情况。抓包如下:

客户端尝试 3 三次来连接,有时候服务器端会发送 rst 数据包。

2. 异常终止一个连接

  在 TCP 通讯中。如果通讯双方应为某种原因(如突然断电等)关闭连接时候一方(如 A)没有发送 fin 数据包。另一端 (如 B) 不知道对方已经关闭了连接。再次发送数据的时候,异常关闭的一方,可能会返回一个 rst 数据包。通知异常关闭。如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的 TCP 连接称为半打开 (Half Open) 的。

3. 同时打开

  两个应用程序同时彼此执行主动打开的情况是可能的。每一方必须发送一个 SYN,且这些 SYN 必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。同时打开的状态迁移图不同于正常状态的三次握手,该情况下需要进行 4 次握手。如图:

4. 同时关闭

  我们在以前讨论过一方(通常但不总是客户方)发送第一个 FIN 执行主动关闭。双方都执行主动关闭也是可能的,TCP 协议也允许这样的同时关闭(simultaneous close)。在同时关闭的时候,双方都进入 time_wait 状态,如图:

六. TCP 服务器设计

  大多数的 TCP 服务器进程是并发的。当一个新的连接请求到达服务器时,服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。

1. 接入连接请求队列

  一个并发服务器调用一个新的进程来处理每个客户请求,因此处于被动连接请求的服务器应该始终准备处理下一个呼入的连接请求。那正是使用并发服务器的根本原因。但仍有可能出现当服务器在创建一个新的进程时,或操作系统正忙于处理优先级更高的进程时,到达多个连接请求。当服务器正处于忙时,TCP 是如何处理这些呼入的连接请求?TCP 有这样一个队列来临时存放这些连接 - 接入连接请求队列。处理方式如下:

  • 正等待连接请求的一端有一个固定长度的连接队列,该队列中的连接已被 TCP 接受(即三次握手已经完成),但还没有被应用层所接受。注意区分 TCP 接受一个连接是将其放入这个队列,而应用层接受连接是将其从该队列中移出。
  • 应用层将指明该队列的最大长度,这个值通常称为积压值 (backlog)。
  • 当一个连接请求(SYN)到达时, TCP 使用一个算法,根据当前连接队列中的连接数来确定是否接收这个连接。积压值说明的是 TCP 监听的端口已被 TCP 接受而等待应用层接受的最大连接数。
  • 如果对于新的连接请求,该 TCP 监听的端口的连接队列中还有空间,TCP 模块将对 SYN 进行确认并完成连接的建立。此时,应用层不一定知道该新的连接,如果对方发送数据,这些数据将放入缓冲队列中。
  • 如果对于新的连接请求,连接队列中已没有空间,TCP 将不理会收到的 SYN。也不发回任何报文段(即不发回 RST)。如果应用层不能及时接受已被 TCP 接受的连接,这些连接可能占满整个连接队列,客户的主动打开最终将超时。
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3453 回帖 • 203 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖
  • tcp-ip
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 58 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖 • 67 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 134 关注
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    53 引用 • 37 回帖
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    8 引用 • 30 回帖 • 407 关注
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 94 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 51 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    179 引用 • 995 回帖
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 62 关注
  • OnlyOffice
    4 引用 • 3 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    25 引用 • 191 回帖 • 16 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    125 引用 • 169 回帖 • 1 关注
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 31 关注
  • 安全

    安全永远都不是一个小问题。

    199 引用 • 816 回帖 • 1 关注
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    90 引用 • 59 回帖 • 1 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1434 引用 • 10054 回帖 • 490 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    497 引用 • 1387 回帖 • 283 关注
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 664 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 362 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 3 关注
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 463 关注