文章目录
- 前言
- 端口号
- netstat -- pidof
- UDP协议
- TCP协议
- 三次握手 四次挥手
- 确认应答(ACK)机制
- 超时重传机制
- 连接管理机制
- 理解TIME_WAIT状态
- 理解 CLOSE_WAIT 状态
- 滑动窗口
- 流量控制
- 拥塞控制
- 延迟应答
- 捎带应答
- 面向字节流
- 粘包问题
- TCP异常情况
- TCP小结
- 基于TCP 和 UDP 的应用层协议
- TCP/UDP对比
- 用UDP实现可靠传输(经典面试题)
- TCP :理解 listen 的第二个参数
- 使用 wireshark 分析 TCP 通信流程
- 参考
全文约 11201 字,预计阅读时长: 32分钟
前言
- CPU和内存之间的总线叫系统总线,内存和外设交互的总线叫IO总线。
- 万事万物皆可网络化
- 万事万物皆可系统化
- 本地两台主机,与远在千里的一台主机数据传输的区别在于:传输的距离更远了,数据更容易丢失。
- 需要上下软硬件做大量的工作:解决传输中的数据可靠性问题,定位设备的问题。
- 任何协议,都必须解决两个问题:
- 如何将自己的报头和有效载荷分离
- 要将自己的有效载荷交付给上一层的哪一个协议。
- 传输层和网络层都是内嵌在操作系统当中的。Linux 操作系统 使用C语言写的。填充报头,就是给对应的结构体成员变量赋值。一层一层的封装就是将上一层结构体的数据拷贝至下一层。
- 传输层存在的意义:上面下来的数据什么时候发,发多少;丢包了怎么办?
端口号
端口号范围划分 … 2^16 … 0 – 65535
- 0 - 1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他们的端口号都是固定的。
- 1024 - 65535:操作系统动态分配的端口号。客户端程序的端口号,就是由操作系统从这个范围分配的。
- 知名端口号(Well-Know Port Number):有些服务器是非常常用的,为了使用方便,人们约定一些常用的服务器, 都是用以下这些固定的端口号:
- ssh 服务器,, 使用22端口
- ftp 服务器, ,使用21端口
- telnet 服务器, ,使用23端口
- http 服务器,, 使用80端口
- https 服务器,, 使用443
- 我们自己写一个程序使用端口号时, ,要避开这些知名端口号.。
- 执行下面的命令, 可以看到知名端口号:
cat /etc/services
netstat – pidof
netstat
是一个用来查看网络状态的重要工具:- n 拒绝显示别名,能显示数字的全部转化成数字
- l 仅列出有在 Listen (监听) 的服務状态
- p 显示建立相关链接的程序名
- t (tcp)仅显示tcp相关选项
- u (udp)仅显示udp相关选项
- a (all)显示所有选项
pidof
在查看服务器的进程id时非常方便:pidof [进程名]
- 终止进程的三种方法:
$ grep t1 | grep -v grep | awk '{print $2}' | xargs kill -9 //显示特定列$ killall t1$ pidof t1 | xargs kill -9
UDP协议
- UDP协议 是 User Datagram Protocol 的简称, 中文名是用户数据报协议,一种无连接的传输层协议,提供面向数据报的简单不可靠信息传送服务。
UDP协议报头格式:
- UDP报头内核数据结构:
- 定长报头,8个字节;将自己与有效载荷进行分离。
- 通过16位端口号,知道将有效载荷交付给上一层的哪一个协议。
- 校验和:检验整个报文中是否有出错。
struct udphdr {__u16 source; // 源端口__u16 dest; // 目标端口__u16 len; // 数据包长度__u16 check; // 校验和
};__u16 位段
UDP的特点:传输层中的另类,任何时候都发,丢包了我不管。
- 无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接。
- 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。
- 不可靠:中性词。UDP用起来足够简单。相反TCP为了保证可靠,要做大量的保障工作。
- TCP与UDP互补,根据使用场景来确定使用哪一个。如果拿不准,一律 TCP。。。
- 面向数据报: 不能够灵活的控制读写数据的次数和数量。
- 比如寄了一次信,就必须收一次;报文之间彼此独立。
- 上层来不及读走的数据,会存在接收方的接收缓冲区,上层在合适的时候读取。
面向数据报
- 应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并。
- 假设用UDP传输100个字节的数据:
- 如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节;而不能循环调用10次recvfrom, 每次接收10个字节。
- UDP不会在意顺序,是哪个报文先送到。
UDP的缓冲区
- UDP没有真正意义上的 发送缓冲区。 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作。
- UDP具有 接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了, 再到达的UDP数据就会被丢弃。
- UDP的socket 能同时读写, 这个概念叫做 全双工。
UDP使用注意事项
- UDP协议首部中有一个16位的最大长度, 也就是说一个UDP能传输的数据最大长度是64K(包含UDP报头本身)。
- 然而64K在当今的互联网环境下, 是一个非常小的数字。需要传输的数据超过64K, 就需要在应用层手动的分包,多次发送, 并在接收端手动拼装。
基于UDP的应用层协议
- NFS: 网络文件系统
- TFTP: 简单文件传输协议
- DHCP: 动态主机配置协议。
- 电脑没网的时候,没有IP;通过手机热点或者路由器接入网络以后,IP地址是接入设备动态给你分配的。
- BOOTP: 启动协议(用于无盘设备启动)
- DNS: 域名解析协议
- 当然, 也包括你自己写UDP程序时自定义的应用层协议。
TCP协议
- 大名鼎鼎的复杂的 TCP 协议:全称为 “传输控制协议(Transmission Control Protocol”)。 人如其名, 要对数据的传输进行一个详细的控制。
TCP协议报文格式
- 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去.
- 32位序号/32位确认号:TCP是全双工的,必须是两个序号机制。
- 序号:一个主机向另一个主机发送多个报文时,不同路径的通常程度是不一样的。根据报头中的序号,可以对接收到的报文进行顺序重排。
- 确认序号:确认之前编号的数据,已经全部收到,请从确认序号处开始发数据。
- TCP的接受与发送缓冲区,内核角度可以看作是,一个大的字符数组,天然的,每个字节就有了编号。
- TCP发给对方的数据,对方收到时必须给与确认;本方只有在收到对方的确认时,才会把自己发送缓冲区的数据删除。
- 4位TCP报头长度:
- 表示该TCP头部有多少个32位bit(有多少个4字节);
- TCP头部最大长度是15 * 4 = 60;最小的TCP报头长度是20个字节,可携带选项是40个字节。
- 6位标志位:
URG
: 紧急指针是否有效,搭配16位紧急指针使用。- 因为TCP是按序到达的,要想对后来的数据优先处理,就要设置URG标志位。
- send 的 flag 标志位设置成:MSG_OOB,紧急指针对应的数据也叫带外数据,recv 收的时候也要设置。
SYN
: 标识该报文是一个连接建立请求报文,我们把携带SYN标识的称为同步报文段。ACK
: 标识该报文,也有对上一个报文的确认成分。PSH
: 提示接收端上层应用程序立刻把TCP接受缓冲区的数据读走。RST
: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段FIN
:通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
- 16位窗口大小: 表明自己的接受缓冲区剩余空间的大小。
- 原因:有可能发送的数据量过大、或者对方的上层应用迟迟不从接受缓冲区取走数据;导致对方来不及接受,或者对方的接受缓冲区已经满了。且一个报文在网络间,需要做大量的传输工作,随意丢弃浪费网络资源。
- 套接字提供的接口,本质上是把数据从用户空间,拷贝到tcp协议的发送缓冲区。站在OS角度,发送数据,是把数据从我的发送缓冲区,通过网络,转移到了你的接收缓冲区。
- TCP协议控制,什么时候发,发多少,出错了怎么办。将应用层应用,和底层通信细节,进行逻辑解耦。
- 互相通报自己的接受能力给对方,以达到两个方向上的传输速度的控制,这个就叫流量控制。
- 对方的上层应用将数据取走,对方接受缓冲区就有了位置,己方就可以发送数据,这便是网络中的生产者消费者模型。
- 16位校验和: 发送端填充, CRC校验, 接收端校验不通过, 则认为数据有问题。 此处的检验和不仅包含TCP首部, 也包含TCP数据部分。
- 16位紧急指针: 标识哪部分数据是紧急数据。
- 40字节头部选项: 暂时忽略。
SYN+ACK:同步报文段
为什么要有SYN: 接收方收到的众多报文中,区分断开连接的报文、建立连接的报文、进行正常通信的报文。
SYN + ACK:确认收到了你的请求报文;同时向你请求建立连接。
RST:复位报文段
主机一只要把第三次ACK扔出去,就认为建立连接成功;这是在赌。假设ACK最后一次丢失了,而主机一认为建立连接成功,就会直接发送数据。主机2会设置RST标志位提醒主机1,连接还没建立成功呢。TCP不保证握手时的建立连接可靠性,但是尽最大的概率,最小的成本来保证成功建立连接。
TCP如何将报头与有效载荷进行分离:
获取到TCP报文后,首先读取报文的前20个字节,并从中提取出4位的首部长度,此时便获得了TCP报头的大小,读取完TCP的基本报头和选项字段后,剩下的就是有效载荷了。
三次握手 四次挥手
三次握手的过程
第一次握手:客户端向服务器发送的报文当中的SYN位被设置为1,表示请求与服务器建立连接
第二次握手:服务器收到客户端发来的连接请求报文后,紧接着向客户端发起连接建立请求并对客户端发来的连接请求进行响应,此时服务器向客户端发送的报文当中的SYN位和ACK位均被设置为1
第三次握手:客户端收到服务器发来的报文后,得知服务器收到了自己发送的连接建立请求,并请求和自己建立连接,最后客户端再向服务器发来的报文进行响应
第三次握手是可以携带数据的,前两次握手是不可以携带数据的。
为什么是三次
网络传输需要时间的,维护连接是需要成本的。某一方发起最后一次握手前,会进行连接的建立。建立连接的本质是在内核空间分配资源,创建数据结构。
最后一次握手会有丢失的风险,发起方会存有一个短暂的连接。奇数次与偶数次,最大的区别在于:建立短暂连接的成本承担方不一样。奇数次,让客户端承担风险。偶数次则当在OS面对承担风险。
-
三次意味着短暂建立连接的成本回嫁到客户端。
-
同时是验证全双工的最小次数。全双工,起码要建立两条网络信道,也是对网络信道通常程度的验证。
四次挥手
确认应答(ACK)机制
对方给你发消息,你要确认收到;同理,你给对方发消息,对方也要确认收到。一次也可以发送多个报文。确认应答机制是双方都要遵守的。但是确认应答机制,不是对最新数据的可靠保证,而是对历史数据的可靠性保证。因为最新的一条消息,我们人是无法保证可靠性的。
超时重传机制
数据丢失,应答丢失
-
数据丢失:主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发。
-
确认应答丢失:此时主机B会收到很多重复数据,那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉。 这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果。
超时的时间如何确定
- 最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.
- 但是这个时间的长短, 随着网络环境的不同, 是有差异的.
- 如果超时时间设的太长, 会影响整体的重传效率;
- 如果超时时间设的太短, 有可能会频繁发送重复的包
- TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。
- Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
- 如果重发一次之后,仍然得不到应答, 等待 2*500ms 后再进行重传.
- 如果仍然得不到应答, 等待 4*500ms 进行重传。 依次类推, 以指数形式递增.
- 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接。
连接管理机制
服务端状态转化:
- [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接;
- [LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文.
- [SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行读写数据了.
- [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT;
- [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
- [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接.
客户端状态转化
- [CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段;
- [SYN_SENT -> ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态, 开始读写数据;
- [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1;
- [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段;
- [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK;
- [TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态.
注意
- connect :触发三次握手;只关心返回值。
- accept:底层三次握手已经完成,可以返回对应的连接。
- write写数据:把用户空间的数据拷贝到内核空间的TCP发送缓冲区。
- 四次挥手是最低成本断开连接的方案。
- 执行close(fd),不一定非得执行close();如果有任何一个进程退出,OS会自动关闭进程打开的文件。因为FD的生命周期随进程。
- 半关闭:单向关闭。
理解TIME_WAIT状态
首先启动server,然后启动client,然后用Ctrl-C使server终止
- TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态.
- 我们使用Ctrl-C终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听同样的server端口(只能被一个进程绑定);
- MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s;可以通过
cat/proc/sys/net/ipv4/tcp_fin_timeout
查看msl的值。
为什么是TIME_WAIT的时间是2MSL?
- MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL(A–>B)的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
- 断开的一瞬间,历史上还存在着一些数据。
- 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK);
为什么要等待
四次挥手,依旧担心最后一个ACK失败;等待较大概率保证最后一个ACK被对方收到,从而较大概率保证是正确的四次挥手。
解决TIME_WAIT状态引起的bind失败的方法
- 服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求).
- 这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接.
- 由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip, 源端口, 目的ip, 目的端口, 协议). 其中服务器的ip和端口和协议是固定的. 如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了,就会出现问题。
- 使用
setsockopt()
设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符。解决因它导致的无法立即重启的问题。- setsockopt()函数功能介绍
理解 CLOSE_WAIT 状态
- 服务端获取到链接,没有进行任何读写,更没有close()。此时客户端关闭退出,服务端会一直处于CLOSE_WAIT 。可以认为四次挥手没有正确完成。维持较长的时间,再确认几次,也会关闭连接。
- 对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题.
滑动窗口
- TCP保证的是通信时的安全数据可靠性。我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了).
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。
start、end= start + 16位窗口大小
- 可大可小。
- 比如:发送前四个段的时候, 不需要等待任何ACK, 直接发送
- 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
- 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
- 窗口越大, 则网络的吞吐率就越高。
- TCP的发送缓冲区是一块儿环状结构。当end 到开始时,意味着可以直接覆盖了。
丢包:
- 情况一: 数据包已经抵达, ACK被丢了:这种情况下,允许少量丢包 ,不要紧,因为可以通过后续的ACK进行确认。后续的ACK说明,前面少量的数据包已经收到了。
- 情况二: 数据包就直接丢了:1000 - 7000
- 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 “我想要的是 1001” 一样;
- 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
- 这种机制被称为 “高速重发控制”(也叫 “快重传”).
- 快重传在超时重传的基础上提高效率。
滑动窗口,可以让发送方一次发送大量的数据,保证效率。配合对端的接受能力,实现流量控制。
流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应. 因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
- 窗口大小字段越大, 说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
- 发送端接受到这个窗口之后, 就会减慢自己的发送速度;
- 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端。
- 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么? 实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位;
拥塞控制
虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题. 因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的.
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
拥塞窗口:一个预定义的值。发送开始的时候, 定义拥塞窗口大小为1K;每次收到一个ACK应答, 拥塞窗口加1K;每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为滑动窗口的大小。1,2,4,8.。。
像上面这样的拥塞窗口增长速度, 是指数级别的。 “慢启动” 只是指初使时慢, 但是增长速度非常快.为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍。 此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长 。
当TCP开始启动的时候, 慢启动阈值等于窗口最大值;在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1K;
少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.
延迟应答
- 如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小:
- 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
- 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
- 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M
- 一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。
- 那么所有的包都可以延迟应答么? 肯定也不是;
- 数量限制: 每隔N个包就应答一次;
- 时间限制: 超过最大延迟时间就应答一次;
- 具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms;。
捎带应答
- 发送ack报文时,顺带捎上自己的数据。提高 TCP 协议效率的机制,与 延迟应答 一起作用提高 TCP 效率。
- 会将四次挥手搞成三次。…
面向字节流
- 分批次发送的批量字节数据,不一定非要对等次数的接受。
- 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
- 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;
粘包问题
- 对于一个报文可能多读也可能少读。
- 站在传输层的角度, TCP是一个一个报文过来的. 按照序号排好序放在缓冲区中.
- 站在应用层的角度, 看到的只是一串连续的字节数据.
- 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包。
解决方案
- 明确两个应用层的数据包之间的边界。
- 特殊字符 + 自描述字段:http的 \n + Content_Length
- 固定大小
- 特殊字符
- 自描述
TCP异常情况
- 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN。 和正常关闭没有什么区别。
- 机器重启: 和进程终止的情况相同。
- 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset复位。即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在。 如果对方不在, 也会把连接释放。
- 应用层的某些协议, 也有一些这样的检测机制:例如HTTP长连接中, 也会定期检测对方的状态.。例如QQ。在QQ断线之后, 也会定期尝试重新连接。QQ检测到用户长期没有操作QQ,会进入忙碌等状态;背后其实是在应用层先把这个连接暂时关掉。
TCP小结
- 为什么TCP这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能。
可靠性
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
- 其他: 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)。
基于TCP 和 UDP 的应用层协议
- 基于TCP的:HTTP、HTTPS、SSH、Telnet、FTP、SMTP。
- 基于 TCP 和 UDP 的应用层协议 详细 一览:
TCP/UDP对比
- 我们说了TCP是可靠连接, 那么是不是TCP一定就优于UDP呢? TCP和UDP之间的优点和缺点, 不能简单, 绝对的进行比较:
- TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景。
- UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等, 另外UDP可以用于广播。
归根结底, TCP和UDP都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定。
用UDP实现可靠传输(经典面试题)
- 参考TCP的可靠性机制,在应用层实现类似的逻辑,例如:
- 引入序列号, 保证数据顺序
- 引入确认应答, 确保对端收到了数据
- 引入超时重传, 如果隔一段时间没有应答, 就重发数据。
- …
TCP :理解 listen 的第二个参数
对于服务器, listen 的第二个参数设置为 2, 并且不调用 accept。此时启动 3 个客户端同时连接服务器, 用 netstat 查看服务器状态, 一切正常.但是启动第四个客户端时, 发现服务器对于第四个连接的状态存在问题了。客户端状态正常, 但是服务器端出现了 SYN_RECV 状态, 而不是 ESTABLISHED(已确立的) 状态。
这是因为, Linux内核协议栈为一个tcp连接管理使用两个队列:
- 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)
- 全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)
- 而全连接队列的长度会受到 listen 第二个参数的影响.
- 全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了.
- 这个队列的长度通过上述实验可知, 是 listen 的第二个参数 + 1。
accept : 不调用也能把连接建立好;accept只是把建立好的连接拿到应用层,也就是说,accept 保存的连接是建立好的连接。谁先三次握手成功,谁就建立连接。
为什么全连接的长度不能太大,也不能太小
在底层,维护全连接队列是提高效率的一种机制。1,越靠后,等待的时间越长,失去了被服务的耐性。2,与其维护长队列,不如花费更多的资源来提高服务器的吞吐量。
使用 wireshark 分析 TCP 通信流程
wireshark是 windows 下的一个网络抓包工具。虽然 Linux 命令行中有 tcpdump 工具同样能完成抓包, 但是 tcpdump 是纯命令行界面, 使用起来不如 wireshark 方便。官网下载。需要在一个报文中找很多信息。
- 直接双击安装,没啥太多注意的。
- 启用 telnet 客户端。
- 启动 wireshark 并设置过滤器
- 由于机器上的网络数据报可能较多, 我们只需要关注我们需要的。
- 因此需要设置过滤器在过滤器栏中写入:
ip.addr == [服务器 ip]
,则只抓取指定ip的数据包。 - 或者在过滤器中写入:tcp.port == 9090 ,则只关注 9090 端口的数据。
参考
- TCP的发送缓冲区
- 下一篇:IP协议。