IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 传输层 udp && tcp -> 正文阅读

[网络协议]传输层 udp && tcp

长短连接

  • 短连接就是建立连接后,发送一次request,接收一次response连接就断开。
  • httlp/1.0使用的就是短连接。当我们需要一次性发起多次request时,建立连接,销毁连接就成了主要矛盾,需要浪费大量的资源。
  • 所以就有了长连接,在一定时间内,只需要建立一次连接,即可接收和发送数据。Connection: keep-alive。

再谈端口号

  • 端口号用来唯一标识一台主机上通信的进程,在tcp协议中用“源ip”、“源端口”、“目的IP”、“目的端口”、“协议号”这样一个五元组来标识一个通信。
  • 打开浏览器,用不同的网页会绑定不同的端口号,进而访问的数据不同。A网页访问的百度,那么B网页不会显示百度。
  • 我们得出结论,一个进程可以绑定多个端口号,但是一个端口号只能绑定一个进程。 因为端口号是用来标识唯一进程的,如果能绑定多个进程,端口号就失去了意义。

1,端口号范围划分

  • 端口号有16位,最多有65536个端口号。
  • 一般0到1023号端口为知名端口号,默认绑定了某些服务,如果HTTP,HTTPS,DNS,SSH,所以操作系统不允许用户进行绑定。
  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.

一些知名的端口号:

  • http:80
  • https:443
  • ssh : 22
  • ftp : 21
  • telnet : 23

相关指令

  1. 查看知名端口号
cat  /etc/services
  1. 查看网络状态(*****)
netstat
n:拒绝显示别名,能显示数字的全部转化成数字。
l:列出所有在listen服务状态
p:显示建立相关链接的程序名
t:只显示tcp相关选项
u:只显示udp相关选项
a:显示所有连线中的Socket。
  1. 查看服务器的进程id
pidof 服务器的名字

UDP

用户数据报协议(udp)是工作在传输层的一个重要协议,UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。

UDP的协议格式

UDP协议格式

/********                 UDP报头源码  **************/
/************来自linux kernel 3.0.4 *****************/

struct udphdr { //udp header
	__be16	source; //源端口号,__be16表示16位
	__be16	dest;   //目的端口号
	__be16	len;    //16位udp长度
	__sum16	check;  //16位校验和
};

如何将报头和有效载荷分离?

  • UDP的报头是定长报头,它的报头只有8字节,只需要读取8字节的长度就行。

16位源端口号和16位目的端口号:

  • 所有的协议都要解决分用的问题。而UDP的解决方法就是通过端口号。目的端口号就标记了该报文应该交付的进程。而源端口号则用来回复数据。

16位UDP长度:

  • 我们已经分离出了报头,但是我们如何判断有效载荷的长度呢?
  • 16位UDP长度就是有效载荷+报头的字节数,我们只需要用该数据减去8,即得到有效载荷的长度。
  • 因为这16位标记了一个UDP报文的长度,所以每次UDP报文最多能发送的有效载荷是2^16 - 8字节,这是一个很小的数字。
  • 所以如何数据过大,我们就需要手动分包几次发送,然后在接收端拼接起来。

16位UDP校验和

  • UDP是不可靠的协议,校验和一旦检测出数据有问题,立刻丢弃,其他的问题一概不管。

UDP的特点

  • 无连接:获得对方的ip和端口号时直接传输数据,不需要建立连接。
  • 不可靠:什么也不管,出错直接丢弃。
  • 面向数据报:每次发送或者读取数据都有明显的边界。比如我发送100个字节,你要么不读,要读就读取100个字节,不存在只读取10个字节的情况。

其他:

  • 相比于tcp,UDP是更快速简单的。
  • udp是全双工,可以同时接收和发送数据。
  • udp没有发送缓冲区,但是有接收缓冲区。

udp的缓冲区:

  • udp没有发送缓冲区,因为它收到上层的数据报,直接加上报文就往下层传,不需要做任何多余的动作,因为它不考虑可靠性。
  • udp有接收缓冲区,如果发送数据过多,接收缓冲区也会塞满。如果缓冲区塞满,多余的udp报文直接丢弃(没错,就是这么暴力。)。而且udp的接收缓冲区不保证udp报文的有序性。即你发送1,2,3,可能收上来就是3,2,1。

基于UDP的应用层协议

  • NFS: 网络文件系统
  • TFTP: 简单文件传输协议
  • DHCP: 动态主机配置协议
  • BOOTP: 启动协议(用于无盘设备启动)
  • DNS: 域名解析协议

TCP

TCP全称为传输控制协议,主要功能是对数据的传输进行详细的控制。

TCP的协议格式

TCP协议格式

  • tcp报头同样的,需要解决分用和解包的问题。
  • 第一行的4个字节的源端口号,和目的端口号决定了将报文交付给哪一个进程。
  • 而序号标识了数据的顺序。
  • 确认序号是对数据的确认,一般是序号+1.(后面细谈)
  • 数据偏移标识了tcp报头的长度,它的单位是4字节,也就是说如过数据偏移为x,那么报头长度为x*4。

6个标识位:

  • 实际上在源码中是8个标识位,但是后两个跟IP有关,在次不谈。
  • FIN(finish):终止连接的标识位,在4次挥手的报文中使用。
  • SYN(synchronization, 同步) : 建立连接的标识位。
  • RST:建立连接出现问题,重新建立连接。3次握手的时候,客户端收到服务器的ACK+SYN就认为连接建立,就会发送数据。此时如果服务器没有收到客户端的ACK,反而接收到了数据,服务器就会意识到ACK丢失,那么就是设置RST标识位。
  • PSH:催促自己的输出缓冲区快将数据发送,催促对方的接收缓冲区快将数据递交上层。
  • ACK:确认字符,用来确认信息收到。
  • URG:当有紧急报文时,URG被设置,配合紧急指针来使用。

保留项:

  • tcp报头不是定长的,因为在报头后面有个保留选项可选可不选。具体功能不做探讨。

16位窗口:

  • tcp是有接收缓冲区的。但是缓冲区一定是有大小的。如果发送方不停的发送大量数据给服务器,那么缓冲区一定会被填满,此时再发送数据,就会产生丢包。
  • 所以我们希望发送方获得接收方的接受能力,如果对方接受能力较弱,则发送慢一点。
  • 16位窗口在发送tcp报文的时候会被填充,填充的是(发送方)自己的未被使用的接收缓冲区大小。注意的是,在tcp3次握手的时候,双方的窗口就会被“协商”。这叫做流量控制。

实际上,tcp是有超时重传机制的,你可能会问,既然有重传机制,重传不就好了,为什么还要流量控制呢?

  • 使用超时重传处理丢包问题是不健康的!网络资源是有限的,你重传肯定会浪费资源,所以为了节省互联网的资源,进行流量控制。

紧急指针:

  • 当有紧急报文来的时候,我们需要先处理紧急报文,而紧急报文会被放到tcp报文的最前面。紧急指针就指向紧急报文的最后一个字节+1,用来标识紧急数据的长度。

检验和:

  • 如何判断数据在发送过程中是否丢失某些信息?
  • 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也包含TCP数据部分.

tcp有没有规定数据的长度?

  • 没有。因为tcp是面向字节流。一次随意读取。
/************tcp header 源码**************/
/****************linux kernel 3.0.4*****************/
struct tcphdr {  //tcp header
	__be16	source;   //源端口号
	__be16	dest;     //目的端口号
	__be32	seq;	  //32位序列号
	__be32	ack_seq;  //32位确认序列号
#if defined(__LITTLE_ENDIAN_BITFIELD) //小端存储
	__u16	res1:4,
		doff:4,
		fin:1, // Finish,终止连接
		syn:1, // Synchronization(同步),建立连接
		rst:1, // Reset,建立连接出现问题,重新建立
		psh:1, // Push,推动发送方自己快速发送数据,推动接收方快速向上分用。
		ack:1, // Acknowledge character,确认收到
		urg:1, // Urgent,紧急位
		ece:1,
		cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD) //大端
	__u16	doff:4,
		res1:4,
		cwr:1,
		ece:1,
		urg:1,
		ack:1,
		psh:1,
		rst:1,
		syn:1,
		fin:1;
#else
#error	"Adjust your <asm/byteorder.h> defines"
#endif	
	__be16	window; //滑动窗口
	__sum16	check;  //校验和
	__be16	urg_ptr;// 紧急指针
};

tcp确认应答机制

可靠性:只要收到了对方的应答,认为之前的数据对方已经收到!!

栗子:

  • A主机给B主机发送一个hello_B,A主机怎样确认B主机接收到了?收到B主机的回复,我收到了。但是,我收到了这句话本身就是数据,B主机怎么保证我收到了这句话被A接收到呢?
  • 你会发现一个问题:在互联网数据传输中,没有绝对的可靠性,因为你永远无法确定最后一条消息对方是否接收到。
  • 所以我们只有相对可靠性,所以我们不需要对应答进行应答,即不需要对ACK进行ACK。

tcp基于确认应答机制:

  • tcp进行的是可靠传输,传输的是字节流。因此,对于tcp协议需要做到以下两点:①确保接收端成功接收到数据②确保接收端接收到数据的有序性。
  • 即使数据按照正确顺序发送,由于网络的错综复杂,接收方也不一定会按照顺序接收。所以需要进行排序。

确认应答机制:发送方每发送一个数据,接收方就要向发送方发送一个确认信号,表明自己已经收到该数据,从而确保接收端成功接收到数据
确认应答机制

那么tcp如何保证数据的按序到达呢?
序号

  • tcp给发送缓冲区的每一个字节的数据都标号,发送的时候将最后一个字节的序号填充到tcp报文的16位序号中,代表发送方发送的数据顺序。接收方只需要按照16位序号排序即可保证数据的有序。
  • 而tcp还有一个16位确认序号, 每次接收数据,tcp需要给对方发送一个确认报文,这个确认报文的ack会被填充,而且16位的确认序号是数据的序号+1,表示你应该从确认序号开始发送数据,之前的数据已被接收。 通过序号和确认序号,tcp完美的实现了确认应答机制。

两个问题:

  • 为什么要有序号?

我们需要序号来保证接收的顺序。

  • 为什么要两套序号?

因为tcp是全双工的,可以同时发送和接受数据。

超时重传

tcp协议在保证数据可靠传输时,还需要确保丢失的数据能够重新发送。如果数据在发送后一段时间内没有收到对方的ACK,就认为数据丢失,需要重传,这就是超时重传机制。
哪些情况需要超时重传呢?
超时重传1

由于网络阻塞或其他网络原因,数据直接丢失在网络中。

2

也有可能是主机B在回复ACK时,ACK丢失在网络中。

  • 无论是数据直接丢失还是对方的ACK丢失,这对于A主机来说都是一样的,因为确认应答机制就规定A主机必须收到B主机的ACK才能确认数据被正常接收。
  • 这里又有两个问题需要解决:
  1. 既然是超时重传,那么时间怎么设置?
  2. 重传后,极有可能导致数据的重复,怎么去重?

分析:

  • 时间一定不能设置太短,否则就会大量传输重复数据,浪费网络资源。
  • 时间也不能太长,因为太长导致传输效率下降。
  • TCP为了保证无论在任何情况下都能比较高性能的通信,会动态计算这个时间。
  • 在linux中超时时间以500ms为一个单位,每次判断超时重发的时间都是500ms的整数倍。
  • 如果重发一次仍然得不到应答,会等待500*2ms后进行重传,
  • 如果还是不能得到应答则在等待4*500ms重发,指数形式增长,以此类推。
  • 当累计到一定次数,tcp会认为对端或者网络出现异常,直接强制断开连接。

去重:

  • tcp在报文中有16位序列号,序列号的另外一个作用就是去重。相同的序列号的报文保留一份即可。

http发送的一个request,然后接收到一个response,这是否对应tcp的一次确认应答呢?

  • 完全不是。上层的request和response和下层的确认应答机制没有一对一的关系。
  • send仅仅是将上层数据拷贝到内核。具体怎么发送,分几次确认应答是通信细节,不是http关心的。

例子:

  • 学校订购1万本书,然后出版商让顺丰快递来运这一万本书。顺丰可以选择一次运500本,或者一次运400本,最后到目的地,在整合到1万本发给学校。

连接管理机制

tcp需要3次握手建立连接,4次挥手断开连接。
tcp连接
如何理解连接?

  • 连接本身是有成本的,一方面是空间,一方面是时间。

三次握手:

  • 一般是客户端主动发起建立连接的请求。
  • 客户端向服务器发起建立连接的请求,即带有SYN的报文,并将自己设置为SYN_SENT的状态,阻塞等待。
  • 服务器接收到SYN报文后,发送ACK+SYN报文给客户端,表示已经接收到服务器的SYN报文,并要求建立服务器到客户端的连接。并将自己设置为SYN_RCVD状态。
  • 客户端接收到ACK+SYN后,立刻向服务器发送ACK报文,此时在客户端看来,连接建立完成,即ESTABLISHED。
  • 当服务器接收到最后的ACK时,服务器端的连接也建立完成。

为什么建立连接是三次握手?一次两次不行吗?四次握手呢?

  • 因为需要同步。tcp在建立连接的时候需要进行序号协商。假设A是客户端,B是服务器。A给B发送带有SYN的报文,在发送缓冲区随机选取一个序号seq = x,然后发送给服务器。tcp规定带有SYN的报文不允许携带数据,但是占有一个序号。B收到SYN报文后,将确认序号设为ack_seq = x + 1,然后设置SYN和ACK标识位。 tcp规定ACK可以携带数据,如果不携带,那么不消耗序号。在自己的缓冲区随机选取一个序号seq = y,发送给客户端。客户端收到后,将自己的ack_seq = y+1, 连接建立完成。
  • 需要验证全双工。tcp3次握手就可以确认tcp双向通信的信道是可用的。服务器和客户端都进行了一次接收和发送过程。
  • 如果是两次握手,就可能导致服务器的误判。 我们先来看看两次连接的正常情况:A发送SYN请求给B,B发送ACK,然后建立连接。如果SYN请求丢失,那么根据重传机制,A会再次发送SYN报文,此时连接建立完成,没有问题。
  • 但是, 如果SYN没有丢失,是因为网络问题阻塞在了网络中。那么在客户端看来,服务器没有收到SYN,就会重传,此时连接建立完成,完成通信后,连接释放。但是,上一个被阻塞在网络中的连接也会被B接收,B就会建立连接,向A发送ACK。但是A根本就没有发起SYN,A就可以不理会这个ACK,导致服务器的资源被白白浪费。
  • 一次握手根本就没有验证双全工的信道。而且2次通信容易受到SYN的洪水攻击,疯狂占用服务器资源。
  • 那么为什么不是四次握手,五次握手,或者更多次呢? 因为在网络中,没有绝对可靠性,我们不需要对ACK进行ACK。3次握手是最小的代价,5次7次。。。握手跟3次效果一样,更多的握手只会造成资源的浪费。

三次握手一定成功吗?

  • 不一定。但是不用担心。前两次握手的报文丢失是没有任何影响的。因为前两次报文的丢失,服务器和客户端没有一方建立好连接。没有资源的浪费。
  • 即使第三次报文丢失,客户端已经建立好连接。客户端会进行数据发送,但是服务器没有建立好连接,服务器会发送一个带有RST的报文,告诉客户端,需要重新发送第二次连接。一旦超时重传好几次都没有建立完成,那么连接就被强转断开。

四次挥手

  • tcp的连接释放需要进行4次挥手。我们假设A是服务器,B是客户端。
  • A主动断开连接,向B发送连接释放报文,设置FIN为1,主动关闭TCP连接,不在发送数据。注意,这里的不发送数据是指应用层不再发送数据,不是指传输层等通信细节。其序号seq = u,u是A已传送过的数据+1。此时A进入FIN_WAIT_1。tcp规定FIN报文即使不携带数据,也消耗一个序号。
  • B收到A的FIN报文后,就发出确认,确认序号ack_seq = u+1,序号为v,v等于B已发送的数据序号+1。B进入CLOSE_WAIT状态。此时tcp处于半关闭状态,A已经无法发送数据,但若B继续发送数据,A仍要接收。
  • A收到B的确认后,就进入FIN_WAIT_2状态,等待B发出FIN报文。
  • 若B没有要发送的数据,B就会发送FIN报文。将FIN标识位设置为1,将序号设置为w(为什么不是v?因为在B没有发送FIN期间,B还可以继续发送数据,单向通信。),同时B还必须重复发送上次的确认号ack_seq = u + 1。此时B进入LAST_ACK状态。
  • A在收到B的FIN报文后,必须对此进行确认。确认报文中ACK标识位设为1,seq = u + 1(FIN消耗一个序号), ack_seq = w + 1。然后A进入TIME_WAIT状态。 注意,此时tcp连接现在还未释放掉!!!直到TIME_WAIT设置的时间2*MSL只后,A才进入CLOSED状态。
  • B在收到ACK后,也进入CLOSED状态。

CLOSE_WAIT && TIME_WAIT

  • CLOSE_WAIT: 当服务器接收到客户端的FIN,发送出ACK时,服务器就会变成CLOSE_WAIT状态。此时,如果服务器没有继续关闭对应的套接字(第三次挥手),那么连接就会卡在CLOSE_WAIT状态,浪费服务器资源。所以我们应该避免这种情况。
  • TIME_WAIT: 主动断开连接的一方(没错,服务器也可能处于这个状态!),在最后发出ACK时,需要进行等待。
  • 意义:1,最后一个ACK是可能在网络中丢失的,如果ACK丢失,那么B就无法释放连接。所以当B等待一段时间,没有等待ACK时,B就会进行超时重传FIN,此时若A已经CLOSED,那么无法接收ACK,导致服务器的资源一直被浪费。虽然,最终通过几次超时重传B也会被释放,但是这是一种级不健康的做法!!!所以,我们需要A等一等,这个时间就是2*MSL(最长报文段寿命),即发送一次数据被接收的最长时间。
  • 2,等待历史数据在网络上进行消散。A断开连接后,B仍可发送数据给A。有可能B发送的数据阻塞在网络中,但是此时A已经销毁连接,无法接收B的数据。所以,需要A等数据消散在网络中。
  • TIME_WAIT: 2*MSL,2倍的最大传送时间。

为什么是4次挥手?

  • 因为连接是双方的事情,需要互相请求和确认。

TIME_WAIT状态的体现:

  • 打开一个服务器,然后立刻关闭它。当我们再次通过同一个端口号连接它时,发现连接不上状态,因为此时服务器处于TIME_WAIT状态,底层还未断开。
  • 但是服务器挂掉后需要立刻重启,否则损失惨重。我们需要解决这个问题。使用setsockopt函数进行端口复用。
  • 端口复用
 抓包
tcpdump -i any -nn tcp port 8080

滑动窗口

  • 用来弥补确认应答机制的缺点。确认应答机制要求我们必须收到ACK才能确定信息的可靠,但是上面我们一次只确认一条数据,这是串行的,这种方式虽然保证了数据很高的可靠性,但是效率有点低。
  • 而滑动窗口就是牺牲一部分可靠性带来的效率极高的方法
  • 滑动窗口是发送缓冲区的一部分。可以将发送缓冲区想象成一个环形队列。

滑动窗口1

滑动窗口2

  • 滑动窗口允许一次发送一批数据,而发送过程中不需要等待某一个数据的应答就可以直接发送。滑动窗口的大小就是发送的最大值(也可以发送小于滑动窗口的数据!)。

  • 如上图,红框就是滑动窗口,28-31号数据已经被发送,而32-33号数据也可以直接发送,不用等待28-31号的ACK。已经发送但是未确认的数据不应该被清除,以防超时重传。

  • 发送窗口里的序号就是允许发送的序号。滑动窗口越大,那么效率越高。但是滑动窗口的必须小于对方的窗口大小!滑动窗口还受限于拥塞窗口的大小,后面讲解。

  • 滑动窗口后沿左边的数据可以清除,因为已经收到ACK。前沿右边的数据不可被发送,因为对方可能没有额外的空间来存储这部分数据。

  • 滑动窗口的位置和大小都会改变!滑动窗口后沿 可以前移,当滑动窗口部分数据收到ACK。也可以不动,当没有收到ACK。不允许后移。当收到滑动窗口的某一个ACK时,就必须前移,因为tcp不会撤销确认。 比如28-31号数据,收到了30号的ACK,那么滑动窗口后沿就会前移3个字节而不管28-29号数据。滑动窗口的前沿通常是前移的,但也有可能不动。一是没有收到新的确认,对方的窗口大小也不变;二是收到了新的确认,对方的窗口变小了。

  • 一个重要的结论:滑动窗口的前沿和后沿不是同步变化的!!

  • 前沿也可以后移,但是标准强烈不推荐!! 因为可能滑动窗口已经将数据发送出去,但是前沿后移了,将你已经发送的数据标识位不允许发送的数据,这就会造成错误。

描述一个滑动窗口:

  • 我们只需要3个指针即可描述一个滑动窗口,即p1,p2,p3。

滑动窗口的丢包:

  • 情况1,ACK丢失。
    丢包1
    此时滑动窗口直接不管,只要后续的ACK收到,就直接后移。

  • 情况2,报文丢失。

滑动窗口2

  • 当数据包真正丢失了,是需要进行重传的,这也就要求滑动窗口中发送的ACK要确保他之前的数据都已经接收到。例如,接收端接收到的数据的顺序是1~ 1000、5001~ 6000、4001~ 5000,当接收到5001~ 6000的数据后不能立即发送6001的ACK,必须等到6001之前的数据全部到达才能发送。
  • 当1001-2000的报文丢失后,接收端接下来的几个ACK报文都会将ack_seq设为1001,提醒客户端1001-2000号报文丢失。一但客户端收到3次同样的ack_seq,那么客户端就会重传这部分报文。
  • 由于这种方式比超时重传更快,称为快重传
  • 这个时候接收端收到了 1001 之后 , 再次返回的 ACK 就是 7001 了 ( 因为 2001 - 7000) 接收端其实之前就已 经收到了, 被放到了接收端操作系统内核的接收缓冲区。

有了快重传,为什么还需要超时重传?
当你的滑动窗口小于3时,无法触发快重传。而且,如果所有的ACK都丢了,也无法触发快重传。所以需要超时重传兜底。这两种方式互相补充。

流量控制

-如果发送端一直向对方发送数据,那么就可能导致对方的接收缓冲区满了。此时如果再次发送,就可能丢包,所以需要根据接收端的能力进行流量控制。
探测

  • 在SYN建立连接的时候就会进行窗口协商,此时就已经进行流量控制了。
  • 如果B已经无法接收数据,那么就会将自己的窗口设置为0,然后发给A。A收到后就会阻塞等待。
    那么,如何知道对方的窗口已经有了空间?
  • A会时不时的发送一个不带有数据的窗口探测报文,用来获取对方的窗口大小。
  • 但是也有可能你的窗口探测报文也会丢失,那么你就需要重传,如果窗口探测设置的时间是1s,但是你的缓冲区在0.1s以后就已经更新了窗口大小,那么发送方就还需要在等待0.9s在进行发送,会造成时间的浪费,所以接收方还应该设置一个窗口更新通知,当窗口更新完毕以后,主动的给发送方,这样也就节省了时间,提高了效率。
  • 如果一直不取数据,那么就是应用层的bug。

拥塞控制

  • 如果10000个报文有几个需要重传,那么就说明网络没问题。但是如果10000个报文,有9999个需要重传,那么tcp就会认为网络状态不好。
  • 在不清楚当前网络状态下 , 贸然发送大量的数据 , 是很有可能引起雪上加霜的. TCP引入 慢启动 机制 , 先发少量的数据 , 探探路 , 摸清当前的网络拥堵状态 , 再决定按照多大的速度传输数据 ;
  • 我们引入拥塞窗口的概念。而拥塞窗口就代表探路使用的少量数据的大小。开始发送数据的时候,拥塞窗口定义为1,每收到一个ACK拥塞窗口+1,每次发送数据的时候将拥塞窗口和主机端反馈的窗口大小做比较,取最小的作为实际发送的窗口大小。

慢启动:

  • 为了避免网络更加阻塞,我们将阻塞窗口一开始设置的很小,然后指数增长的启动方式。慢启动并不是说阻塞窗口增长的慢,而是说我们一开始将阻塞窗口设置的非常小,这样比起一开始将大量数据放到网络上显得慢多了。

为了防止慢启动的指数增长过快,拥塞窗口增长到一定程度就会呈线性增长。

快重传和快恢复算法

  • 因为拥塞控制的存在,又时候丢失少量报文,发送发也会重置拥塞窗口为1,这样就会损失效率。此时我们就需要快重传,快速的将丢失的报文告知发送方。
  • 当发送方发现是少量报文丢失而非网络不好时,发送方就会执行快恢复算法,快速的扩大拥塞窗口的大小。

一个人的数据在网络看来是毛毛雨,那么为什么有拥塞控制?

  • 拥塞控制是多人控制
  • 一个人的拥塞控制对网络影响不大, 但是现在几乎所有的进程都支持tcp服务,都有拥塞控制,所以所有的拥塞控制都发挥作用,就会让网络变好。

延迟应答

  • 当服务器接收到tcp报文时,可以等一等再发送ACK,等一等。等待上层将自己的缓冲区的数据拿走,这样自己的窗口就会变得更大,然后ACK给客户端更大的窗口,这样能增加效率。

  • 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;

  • 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
    在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;

  • 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;

**窗口越大,网络吞吐量越大。**我们要在网络不拥塞的情况下,尽量提高效率。但是我们也不是每个包都会延迟,一般是隔几个包,或者隔一段时间延迟一个包。

捎带应答

当收到对方的数据时,我们往往会回复对方。此时给对方数据的ACK就可以搭顺风车,跟随我们的回复数据一起交付给对方。

tcp黏包问题

  • A使用tcp协议发送给B好几个数据包,由于tcp是面向字节流的,那么B在交付上层的时候,有没有可能一次交付半个包,或者一次交付1个半呢?这样就导致数据包的不完整。我们的应用层协议应该具有主动将每个数据包分离的作用,例如http就有Content-Length:size键值对。

tcp异常情况

  • A和B建立了tcp连接。
  • A跟B正在通信,A主机的基于tcp进程挂了。由于文件生命周期随进程,所以socket会被关闭。此时底层的tcp连接就会发送FIN,跟正常关闭连接一样。
  • 机器重启: 和进程终止的情况相同(在重启前,电脑会提示还有正在运行的进程是否确认关闭,也就是说重启前会先关闭进程).
  • 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset.。即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在。 如果对方不在, 也会把连接释放。另外, 应用层的某些协议, 也有一些这样的检测机制。例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ 断线之后, 也会定期尝试重新连接。

Listen的第二个参数

  • tcp需要建立连接,但是有可能网络资源已经满了。此时服务器无法立刻accept连接。那么就需要连接进行排队。而根据排队的连接状态,又可分为以下的2种:
  1. 全连接队列: 即服务器和客户端处于ESTABLISHED状态的连接。三次握手已经完成。
  2. 半连接队列: 即服务器和客户端处于SYN_SENT和SYN_RECV状态的连接,三次握手没有完成。
  • 而Listen的第二个参数就是全连接队列的长度。即这些连接已经完成,但是无法立刻被accept。当全连接队列满了之后,新来的连接无法进入ESTABLISHED状态,只能进入半连接队列。
  • 全连接队列的长度是Listen的第二个参数+1(因为在内核中使用的是大于号来判断是否满,而不是大于等于号)。

基于TCP的应用层协议

  • HTTP
  • HTTPS
  • SSH
  • Telnet
  • FTP
  • SMTP

(本章完)

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-11-23 12:43:57  更:2021-11-23 12:44:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 7:45:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码