很多人总觉得学习TCP/IP协议没什么用,觉得日常编程开发只需要知道socket接口怎么用就可以了。如果大家定位过线上问题就会知道,实际上并非如此。如果应用在局域网内,且设备一切正常的情况下可能确实如此,但如果一旦出现诸如中间交换机不稳定、物理服务器宕机或者其它异常情况时,此时引起的问题如果只停留在套接字接口的理解层面将无法解决。因此,深入理解TCP/IP协议,对我们分析异常问题有很大的帮助。

下图是网络通信中常见的架构,也就是CS架构。其中程序包括两部分,分别为客户端(Client)和服务端(Server)。当然,实际的环境还要复杂的多,在客户端和服务端之间可能有多种不同种类和数量的设备,这些设备都会增加网络通信的复杂性。自然,也会增加程序开发容错的复杂性。

图1 基本架构

TCP的基本流程

在分析异常情况之前,我们先回忆一下TCP协议的基本逻辑。在客户端和服务端能够收发数据之前首先必需建立连接。连接的建立在协议层面也是通过收发数据包完成,只不过在用户层面就是客户端调用了一个connect函数。连接的过程俗称“三次握手”,具体流程如图2所示。

图2 TCP的三次握手流程

TCP连接的断开也是比较复杂的,需要经过所谓的“四次挥手”的流程。其原因是因为TCP是双工通信,分别需要从客户端和服务端2侧断开连接。

图3 TCP的四次挥手

另外一个比较重要的内容是TCP协议的状态转换,理解了这个内容,我们才能清楚出现各种异常情况下数据包的内容。

图4 TCP状态转换图

本文只是简单回忆一下TCP的基本流程,详细的内容可以参考本号之前的文章《从TCP到Socket,彻底理解网络编程是怎么回事

异常情况分析

了解了TCP的基本流程之后,我们再看一下各种异常情况。这些异常情况才是我们在后续解决问题的时候的关键。了解了这些异常情况及原理,后面解决问题才能游刃有余。

1. 试图与一个不存在的端口建立连接(主机正常)

这里的不存在的端口是指在服务器端没有程序监听在该端口。我们的客户端就调用connect,试图与其建立连接。这时会发生什么呢?

这种情况下我们在客户端通常会收到如下异常内容:

[Errno 111] Connection refused(连接拒绝)

具体含义可以查一下Linux的相关手册,或者用搜索引擎搜索一下。试想一下,服务端本来就没有程序监听在这个接口,因此在服务端是无法完成连接的建立过程的。我们参考‘三次握手’的流程可以知道当客户端的SYNC包到达服务端时,TCP协议没有找到监听的套接字,就会向客户端发送一个错误的报文,告诉客户端产生了错误。而该错误报文就是一个包含RST的报文。这种异常情况也很容易模拟,我们只需要写一个小程序,连接服务器上没有监听的端口即可。如下是通过wireshark捕获的数据包,可以看到红色部分的RST报文。

图5 数据包截图

继续深入理解一下,在操作系统层面,TCP的服务端实际上就是从网卡的寄存器中读取数据,然后进行解析。对于TCP自然会解析出目的端口这个关键信息,然后根据这个信息查看有没有这样的套接字。这个套接字是什么呢?在用户层面是一个文件句柄,但在内核中实际是一个数据结构,里面记录了很多信息。这个数据结构存储在一个哈希表中,通过函数__inet_lookup_skb(net/inet_hashtables.h)可以实现对该数据结构的查找。对于上述情况,自然无法找到该套接字,因此TCP服务端会进行错误处理,处理的方式就是给客户端发送一个RST(通过函数tcp_v4_send_reset进行发送)。

2. 试图与一个某端口建立连接但该主机已经宕机(主机宕机)

这也是一种比较常见的情况,当某台服务器主机宕机了,而客户端并不知道,仍然尝试去与其建立连接。这种场景也是分为2种情况的,一种是刚刚宕机,另外一种是宕机了很长时间。为什么要分这2种情况?

这主要根ARP协议有关系,ARP会在本地缓存失效,TCP客户端就无法想目的服务端发送数据包了。

(192.168.1.100) 位于 08:00:27:1a:7a:0a [ether] 在 eth0

了解了上述情况,我们分析一下刚刚宕机的情况,此时客户端是可以向服务端发送数据包的。但是由于服务器宕机,因此不会给客户端发送任何回复。

图6 数据包截图

由于客户端并不知道服务端宕机,因此会重复发送SYNC数据包,如图6所示,可以看到客户端每隔几秒会向服务端发送一个SYNC数据包。这里面具体的时间是跟TCP协议相关的,具体时间不同的操作系统实现可能稍有不同。

3. 建立连接时,服务器应用被阻塞(或者僵死)

还有一种情况是在客户端建立连接的过程中服务端应用处于僵死状态,这种情况在实际中也会经常出现(我们假设仅仅应用程序僵死,而内核没有僵死)。此时会出现什么状态?TCP的三次是否可以完成?客户端是否可以收发数据?

在用户层面我们知道,服务端通过accept接口返回一个新的套接字,这时就可以和客户端进行数据往来了。也就是在用户层面来说,accept返回结果说明3次握手完成了,否则accept会被阻塞。在我们假设的情况下,其实就相当于应用程序无法进行accept操作了。

如果想彻底理解上面我们假设的问题,需要理解两点,一点是accept函数具体做了什么,另外一点是TCP三次握手的本质。

我们先试着理解第一点,accept会通过软中断陷入内核中,最终会调用tcp协议的inet_csk_accept函数,该函数会从队列中查找是否有处于ESTABLISHED状态的套接字。如果有则返回该套接字,否则阻塞当前进程。也就是说这里只是一个查询的过程,并不参与三次握手的任何逻辑。

三次握手的本质是什么呢?实际上就是客户端与服务端一个不断交流的过程,而这个交流过程就是通过3个数据包完成的。而这个数据包的发送和处理实际上都是在内核中完成的。对于TCP的服务端来说,当它收到SYNC数据包时,就会创建一个套接字的数据结构并给客户端回复ACK,再次收到客户端的ACK时会将套接字数据结构的状态转换为ESTABLISHED,并将其发送就绪队列中。而这整个过程跟应用程序没有半毛钱的关系。

当上面套接字加入就绪队列时,accept函数就被唤醒了,然后就可以获得新的套接字并返回。但我们回过头来看一下,在accept返回之前,其实三次握手已经完成,也就是连接已经建立了。

另外一个是如果accept没有返回,客户端是否可以发送数据?答案是可以的。因为数据的发送和接受都是在内核态进行的。客户端发送数据后,服务端的网卡会先接收,然后通过中断通知IP层,再上传到TCP层。TCP层根据目的端口和地址将数据存入关联的缓冲区。如果此时应用程序有读操作(例如read或recv),那么数据会从内核态的缓冲区拷贝到用户态的缓存。否则,数据会一直在内核态的缓冲区中。总的来说,TCP的客户端是否可以发送数据与服务端程序是否工作没有任何关系。

当然,如果是整个机器都卡死了,那就是另外一种情况了。这种情况就我们之前分析的第2种情况一直了。因为,由于机器完全卡死,TCP服务端无法接受任何消息,自然也无法给客户端发送任何应答报文。

总结

今天我们主要介绍了连接建立过程中的各种异常情况,还有另外一种情况是在数据的传输过程中。比如传输过程中服务器突然掉电,或者程序crash等,后续我们将详细这些异常情况下在协议层的表现。

理解了这些异常现象才敢说真正懂了TCP协议的更多相关文章

  1. JavaScript Date 对象的异常现象-new Date('0001-01-01 00:00:00')

    Date 对象 Date 对象用于处理日期和时间. new Date() :Date 对象会自动把当前日期和时间保存为其初始值. 打开chrome的开发者工具,在Console敲下new Date() ...

  2. TCP 协议中MSS的理解

    在介绍MSS之前我们必须要理解下面的几个重要的概念.MTU: Maxitum Transmission Unit 最大传输单元MSS: Maxitum Segment Size 最大分段大小PPPoE ...

  3. tcp协议中mss的理解

    在介绍MSS之前我们必须要理解下面的几个重要的概念.<blockquote>MTU: Maxitum Transmission Unit 最大传输单元MSS: Maxitum Segmen ...

  4. 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码

    首发于公众号:计算机视觉life 旗下知识星球「从零开始学习SLAM」 这可能是最清晰讲解g2o代码框架的文章 理解图优化,一步步带你看懂g2o框架 小白:师兄师兄,最近我在看SLAM的优化算法,有种 ...

  5. 对TCP协议握手的理解(转)

    reference:https://www.cnblogs.com/awkflf11/p/9191708.html 目录: 31.Tcp握手的一些问题? 21.Tcp三次握手及SYN攻击: 四次握手? ...

  6. 基于tcp协议下粘包现象和解决方案,socketserver

    一.缓冲区 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区.write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送 ...

  7. python 全栈开发,Day35(TCP协议 粘包现象 和解决方案)

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

  8. 通俗大白话来理解TCP协议的三次握手和四次断开

    from : https://blog.csdn.net/Neo233/article/details/72866230?locationNum=15&fps=1%20HTTP%E6%8F%A ...

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

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

随机推荐

  1. Java Web 深入分析(7) Jetty原理解析

    1Jetty的基本架构 Jetty有一个基本的数据模型,这个模式就是handle,所有拷贝拓展的组件都被当做一个handler被添加到server中,然后由jetty统一管理. 1.1Jetty基本架 ...

  2. Android 主Module引用依赖Module,却无法使用里面的依赖库

    如果模块化开发中遇到 多模块的AndroidManifest.xml没有合并or多模块的资源文件没有合并or模块A include了模块B,而无法使用模块B内依赖的其他aar包中的类的时候or提示Su ...

  3. kbmMW 5.10.10 SmartBinding问题修正

    千呼万唤始出来,最新的kbmMW 5.10.01终于发布了,详情可以看xalion发的更新日志. 我期待的Smartbinding for Listview终于来了,在这一版本中,对SmartBind ...

  4. 一段代码显示出电脑连过所有wifi的密码

    1.打开运行 2.输入cmd后回车 3.输入如下代码 for /f "skip=9 tokens=1,2 delims=:" %i in ('netsh wlan show pro ...

  5. zabbix监控MySQL,Tomcat及配置邮件报警

    目录 一.思路 二.部署.配置 环境 安装zabbix 对zabbix进行初步优化 添加监控主机 部署监控Tomcat 配置邮件报警 三.总结 一.思路 首先搭建zabbixserver,本机需要安装 ...

  6. 【已解决】如图,说我磁盘不够,看到var目录下有的个隐藏文件夹占了46G,不知道怎么删除

    后来发现不是隐藏目录,是其中的log目录,然后一步一步往下,找到jenkins.log文件,已经有40+G的log了.

  7. 系统---《windows + ubuntu双系统》

    安装 Windows + Ubuntu双系统 不是第一次安装 Windows + Ubuntu双系统了,每一遇见的问题都不一样,收获也不一样. 制作U盘的部分截图: 电脑的基本配置截图:

  8. django考点

    django考点 1 列举Http请求中常见的请求方式2 谈谈你对HTTP协议的认识.1.1 长连接3 简述MVC模式和MVT模式4 简述Django请求生命周期5 简述什么是FBV和CBV6 谈一谈 ...

  9. Java基础 TreeSet()来实现数组的【定制排序】 : Comparable接口(自然排序) 或者 Comparator接口 (定制排序)

    笔记: //排序真麻烦!没有C++里的好用又方便!ORZ!ORZ!数组排序还还自己写个TreeSet()和( Comparable接口(自然排序) 或者 Comparator接口 (定制排序))imp ...

  10. linux mint 19 打开 Windows 下制作的 TXT 文件时‘乱码’

    因为 Linux 采用的是 UTF-8 编码,Windows 的中文编码是 GB18030. 解决的办法:让 Linux 的文本编辑器支持 GB18030 1.我们安装一个小软件"Dconf ...