若转载教程,请注明出自SW-X框架官方文档

1、传输层概述

实际上,传输层的功能很简单,它主要负责向应用层提供端到端之间的通信服务,
通俗的讲,两个主机通讯,也就是应用层上的进程之间的通信,也就是转换为进程和进程之间的通信了,
我们之前学到网络层,IP协议能将分组准确的发送到目的主机,但是停留在网络层,
并不知道要怎么交给我们的主机应用进程,通过前面的学习,我们学习有mac地址,通过mac地址能找到同一个网络下主机,
有IP地址,通过ip地址能找到不同网络下的网络,结合mac地址就能找到对应主机,
那么怎么找到主机应用进程呢,肯定也有一个东西来标识它,那就是我们常说的端口号了。
端口,占有16位,其大小也就有65536个,是从0~65535.也就是一台计算机有65535个端口,
主机之间的通讯,也就是应用进程之间的通讯,都要依靠端口,一个进程对应一个端口,进程A和进程B通信,
进程A分到的端口为60000,进程B分到的端口为60001,
进程A通过端口60000发送数据给进程B,就知道要交给60001端口,也就到了进程B中 ,这样就达到了通信的目的。
传输层中的65536个端口,主要可以分为三类:熟知端口、登记端口和客户端端口。
1、熟知端口:
0-1023, 也就是一些固定的端口号,比如http使用的80端口,意思就是在访问网址时,
我们访问服务器的端口就是80,然后服务器那边传网页的数据给我们。
2、登记端口:
1024-49151,比如微软开发了一个系统应用,该应用在通讯或使用时,需要使用到xxx端口,
那么就要去登记一下这个端口,以免有别人公司的应用使用同一个端口号,
例如,windows系统中的3389端口,就是用来实现远程连接的,就固定了这台计算机如果要使用远程连接服务,
就打开3389端口,别人就能使用远程连接连你了,默认是不打开的。
还有3306,这是Mysql数据库中默认的端口号,如果被占用之后,安装Mysql就需要修改端口了。
3、客户端端口:
49152-65535,一般我们使用某个软件,比如QQ,等其他服务,随机拿这个范围内的端口,
而不是去拿前面哪些固定的,拿到等通讯结束后,就会释放该端口。
所以在给传输层创建端口号前,应该先查看当前服务器,除了0-1023端口之外,还使用了哪一些端口,以免产生不必要的冲突。
知道了端口是什么?
运输层具体做了什么事情呢?
运输层就是将两个端口连起来通信的介质,不然光知道两个端口有什么用,怎么通信的,
还是要靠运输层来做这个事情,其中重要的就是靠两个协议,UDP和TCP协议。
下面我们就来学习下UDP和TCP协议的具体内容和区别。

2、UDP协议

UDP是一组简单的、面向用户数据报文的传输层协议。
UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。
由于UDP在传输数据报文前,不用在客户和服务器之间建立一个连接,并且没有超时重发等机制,故而传输速度很快。
无连接:意思就是在通讯之前不需要建立连接,直接传输数据。
不可靠:是将数据报文的分组从一台主机发送到另一台主机,但并不保证数据报能够到达另一端,任何必须的可靠性都由应用程序提供。
在 UDP 情况下,虽然可以确保发送消息的大小,却不能保证消息一定会达到目的端。
没有超时和重传功能,当 UDP 数据封装到 IP 数据报传输时,如果丢失,会发送一个 ICMP 差错报文给源主机。
即使出现网络阻塞情况,UDP 也无法进行流量控制。
此外,传输途中即使出现丢包,UDP 也不负责重发,甚至当出现包的到达顺序杂乱也纠正不了。

3、TCP协议

TCP是一组相较复杂的、面向连接传输控制的传输层协议。
提供的是面向连接、可靠的字节流服务。
当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。
TCP提供超时重发,丢弃重复数据,检验数据,流量控制,拥塞控制等功能,保证数据能从一端传到另一端。
其最终功能和UDP一样,在端和端之间进行通信,但是和UDP的区别还是很大的。

4、UDP和TCP的主要区别

下面,我们先从一张图中,了解TCP和UDP协议,在服务器数据报文上的处理区别:
image.png
UDP 与 TCP 的主要区别:
UDP 不一定提供可靠的数据传输。
事实上,该协议不能保证数据准确无误地到达目的地。
UDP 在许多方面也非常有效。
当某个程序的目标是尽快地传输尽可能多的信息时(其中任意给定数据的重要性相对较低),可使用UDP。
ICQ 短消息使用 UDP 协议发送消息。
许多程序将使用单独的TCP连接和单独的UDP连接。
重要的状态信息随可靠的TCP连接发送,而主数据流通过UDP发送。
TCP的目的是提供可靠的数据传输,并在相互进行通信的设备或服务之间保持一个虚拟连接。
TCP在数据包接收无序、丢失或在交付期间被破坏时,负责数据恢复。
它通过为其发送的每个数据包提供一个序号来完成此恢复。
记住,较低的网络层会将每个数据包视为一个独立的单元,
因此,数据包可以沿完全不同的路径发送,即使它们都是同一消息的组成部分。
这种路由与网络层处理分段和重新组装数据包的方式非常相似,只是级别更高而已。
为确保正确地接收数据,TCP要求在目标计算机成功收到数据时发回一个确认(即 ACK)。
如果在某个时限内未收到相应的 ACK,将重新传送数据包。
如果网络拥塞,这种重新传送将导致发送的数据包重复。
但是,接收计算机可使用数据包的序号来确定它是否为重复数据包,并在必要时丢弃它。

5、UDP和TCP的选择

如果比较UDP包和TCP包的结构,很明显UDP包不具备TCP包复杂的可靠性与控制机制。
与TCP协议相同,UDP的源端口数和目的端口数也都支持一台主机上的多个应用。
一个16位的UDP包包含了一个字节长的头部和数据的长度,校验码域使其可以进行整体校验。
(许多应用只支持UDP,如:多媒体数据流,不产生任何额外的数据,即使知道有破坏的包也不进行重发。)
很明显,当数据传输的性能必须让位于数据传输的完整性、可控制性和可靠性时,TCP协议是当然的选择。
当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。
在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择,如:DNS交换。
把SNMP建立在UDP上的部分原因是设计者认为当发生网络阻塞时,UDP较低的开销使其有更好的机会去传送管理数据。
TCP丰富的功能有时会导致不可预料的性能低下,但是我们相信在不远的将来,TCP可靠的点对点连接将会用于绝大多数的网络应用。
(现在大多的WEB长连接项目,都是使用的TCP协议)

6、TCP的三次握手

很多同学可能会疑惑:
在TCP建立连接的时候,为什么要经过三次握手方案,而不是两次?
因为两次握手的网络是不可靠的,因此多少次握手都不一定是可靠的连接。
三次是最少的握手次数,因为两次握手,响应端不知道发起端是否收到了响应报文,也就不能确信连接是否已经建立。
只有收到发送端的响应后,双方才能确认连接已经建立。
注意:UDP理论上就属于二次握手方案。
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手方案建立一个连接,主要步骤如下:
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -完成三次握手,客户端与服务器开始传送数据。
image.png
上述图解:
第一次握手:
客户端想与服务器进行连接了,所以状态变为主动打开,同时发送一个连接请求报文给服务器段SYN=1,并且会携带X个字节过去。
发送完请求连接报文后,客户端的状态就变为了SYN_SENT,可以说这个状态是等待发送确认(为了发送第三次握手时的确认包)。
第二次握手:
服务端接收到连接请求报文后,从接收请求状态变为被动打开状态,然后给客户端返回一个报文。
这个报文有两层意思,一是确认报文,而可以达到告诉客户端,我也打开连接了。
发完后,变为SYN_RCVD状态(也可以说是等待接受确认状态,接受客户端发过来的确认包)。
第三次握手:
客户端得到服务器端的确认和知道服务器端也已经准备好了连接后,还会发一个确认报文到服务器端,告诉服务器端,
我接到了你发送的报文,接下来就让我们两个进行连接了。
客户端发送完确认报文后,进入确立连接状态,而服务器接到了,也变为确立连接状态,到此三次握手成功。
疑问:
为什么需要第三次握手,有前面两次不就已经可以了吗?
假设没有第三次握手,客户端发送一个连接请求报文过去,
但是因为网络延迟,在等待了一个超时时间后,客户端就会在重新发一个请求连接报文过去,然后正常的进行,
服务器端发回一个确认连接报文,然后就开始通讯;
通讯结束后,那个第一次因为网络延迟的请求连接报文到了服务器端,服务器端不知道这个报文已经失效,也发回了一个确认连接报文,
客户端接收后,发现自己并没有发送连接请求(因为超时了,所以就认为自己没有发),
所以对这个确认连接请求就什么也不做,但是此时客户端不这么认为,他认为i连接已经建立了,
就一直打开着等待客户端传数据过来,这就造成了极大的浪费。
如果有了第三次握手,那么客户端就可以通知服务器了。
所以第三次握手也很重要。

7、TCP的四次挥手

可能很多同学会有疑惑,为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端在接收请求的状态下,SOCKET当收到SYN报文的连接请求后,
它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。
但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;
但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,
也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,
所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
image.png
第一次挥手:
从确立连接状态变为主动关闭状态,客户端主动发送释放连接请求给服务器端,FIN=1。
发送完之后就变为FIN_WAIT_1状态,这个状态可以说是等待确认状态。
第二次挥手:
服务器接收到客户端发来的释放连接请求后,状态变为CLOSE_WAIT,然后发送确认报文给客户端,告诉他我接收到了你的请求。
为什么变为CLOSE_WAIT,原因是是客户端发送的释放连接请求,可能自己这端还有数据没有发送完呢,所以这个时候整个TCP连接的状态就变为了半关闭状态。
服务器端还能发送数据,并且客户端也能接收数据,但是客户端不能在发送数据了,只能够发送确认报文。
客户端接到服务器的确认报文后,就进入了FIN_WAIT_2状态。也可以说这是等待服务器释放连接状态。
第三次挥手:
服务器端所有的数据度发送完了,认为可以关闭连接了,状态变为被动关闭,所以向客户端发送释放连接报文,
发完之后自己变为LAST_WAIT状态,也就是等待客户端确认状态。
第四次挥手:
客户端接到释放连接报文后,发送一个确认报文,然后自己变为TIME_WAIT,而不是立马关闭,
因为客户端发送的确认报文可能会丢失,丢失的话服务器就会重传一个FIN,也就是释放连接报文,这个时候客户端必须还没关闭。
当服务器接受到确认报文后,服务器就进入CLOSE状态,也就是关闭了。
但是由于上面说的这个原因,客户端必须等待一定的时间才能够进入CLOSE状态。

8、TCP的半关闭状态

TCP连接是全双工的,所以它允许两个方向的数据被独立关闭。
换而言之,通信的一端可以发送结束报文给对方,告诉它本端已经完成了数据的发送,
但允许继续接收来自对方的数据,直到对方也发送结束报文以关闭连接。
TCP将这种连接状态称之为半连接状态,如下图:
image.png
注意:
上图中,服务器和客户端应用程序,判断对方是否已经关闭连接的方法是:
read系统调用返回0(表示收到结束报文)。
当然,Linux还提供了其他检测连接是否被对方关闭的方法。

9、TCP的同时关闭连接

正常情况下,通信一方请求连接关闭,另一方响应连接关闭请求,并且被动关闭连接。
但是若出现同时关闭连接请求时,通信双方均从 确立连接 状态转换为 FIN_WAIT_1 状态。
任意一方收到对方发来的 FIN 报文段后,其状态均由 FIN_WAIT_1转变到 关闭连接 状态,并发送最后的 ACK 数据段。
当收到最后的 ACK 数据段后,状态转变化 TIME_WAIT,在等待 2MSL 时间后进入到 关闭连接 状态,最终释放整个 TCP 传输连接。
其过程如下:
image.png
举一个最常用TCP的例子,一般需要保证数据可靠时,都会使用TCP协议。
1、HTTP协议进行网站的访问时,使用的就是TCP。

10、TIME_WAIT状态及其存在的必要性

image.png
从上图中可以看出,客户端连接在接收到服务器的结束报文段6之后,并没有进入关闭连接状态,而是转移到TIME_WAIT状态。
在这个状态,客户端连接需要等待一段长度为2MSL的时间,才能完全关闭。
MSL是TCP报文段在网络中最大的生存时间,标准是参照RFC1122文档中的建议值:2min。
TIME_WAIT状态存在的原因有2点:
1、可靠地终止TCP连接。
2、保证让迟来的TCP报文段有足够的时间被识别并丢弃。
第一个原因很好理解。
假设上图中用于确认服务器结束报文段6和TCP报文段7丢失,那么服务器将重发结束报文段。
因此客户端需要停留在某个状态以处理重复收到的结束报文段(既向服务器发送确认报文段)。
否则,客户端将以复位报文段来回应服务器,服务器则认为这是一个错误,
因为它期望的是一个像TCP报文段7这样的确认报文段。
在Linux操作系统中,一个TCP端口不能被同时打开多次(两次及以上)。
当一个TCP连接出于TIME_WAIT状态时,我们将无法立即使用该连接占用着的端口来建立一个新连接。
反过来思考,如果不存在TIME_WAIT状态,则应用程序能够立即建立一个和刚关闭的连接相似的连接(这里说的相似,是指它们具有相同的IP地址和端口号)。
这个新的、和原来相似的连接被称为原连接的化身。
新的化身可能接收到属于原来连接的、携带应用程序数据的TCP报文段(迟到的报文段),这显然是不应该发生的。
这就是TIME_WAIT状态存在的第二个原因。
另外,因为TCP报文段的最大生成时间是MSL,所以坚持2MSL时间的TIME_WAIT状态能够确保网络上两个传输方向上,
尚未被接收到的、迟到的TCP报文段都已经消息(被中转路由器丢弃)。
因此,从一个连接的新的化身可以在2MSL时间之后安全地建立,而绝对不会被接收到属于原连接的应用程序数据,
这就是TIME_WAIT状态要持续2MSL时间的原因。