TCP/IP中有两个具有代表性的传输层协议,它们分别时TCP和UDP。TCP体哦概念股可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。总之,根据通信的具体特征,选择合适的传输层协议是非常重要的。
- TCP:TCP是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水管道中的水流。当应用程序采用TCP发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。
- UDP:UDP是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在UDP的情况下,虽然可以确保发送消息的大小,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。
端口号
端口号定义
数据链路和IP中的地址,分别指的是MAC地址和IP地址,前者用来识别同一链路中不同的计算机,后者用来识别TCP/IP网络中互联的主机和路由器。在传输层中也有这种类似于地址的概念,那就是端口号。端口号用来识别一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。
根据端口号识别应用
一台计算机上同时可以运行多个程序。例如接收WWW服务的Web浏览器、电邮客户端、远程登录用的ssh客户端等程序都可同时运行。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确的将数据传输。
但是仅凭目标端口识别某一个通信是远远不够的。TCP/IP或UDP/IP通信中通常采用5个信息来识别一个通信。它们是“源IP地址”、“目标IP地址”、“协议号”、“源端口号”、“目标端口号”。只要其中某一项不同,则被认为是其它通信。
端口号如何确定
在实际进行通信时,要事先确定端口号。确定端口号的方法有两种:
标准既定的端口号
这种方法也叫静态方法。它是指每个应用程序都有其指定的端口号。但并不是说可以随意使用任何一个端口号。每个端口号都有其对应的使用目的。
例如,HTTP、TELNET、FTP等广为使用的应用协议中所使用的端口号就是固定的。这些端口号也被称为知名端口号。知名端口号一般由0到1023的数字分配而成。应用程序应该避免使用知名端口号进行既定目的之外的通信,以免产生冲突。
时序分配法
第二种方法也叫时序(或动态)分配法。此时,服务端有必要确定监听端口号,但是接受服务的客户端没必要确定端口号。
在这种方法下,客户端应用程序可以完全不用自己设置端口号,而全权交给操作系统进行分配。操作系统可以为每个应用程序分配互不冲突的端口号。例如:每需要一个新的端口号,就在之前分配的号码的基础上加1.这样操作系统就可以动态的管理端口号了。
根据这种分配端口号的机制,即使是同一个客户端程序发起的多个TCP连接,是被这些通信的5部分数字也不会全部相同。
动态分配的端口号取值范围在49152到65536之间。
端口号与协议
端口号有其使用的传输层协议决定。因此不同的传输协议可使用相同的端口号,例如,TCP与UDP使用同一个端口号,但使用目的各不相同,这是因为端口号上的处理是根据每个传输协议的不同而进行的。
数据到达IP层后,会先检查IP首部中的协议号,再传给相应协议的模块。如果TCP则传给TCP模块,如果是UDP则传给UDP模块去做端口号的处理。即使是同一个端口号,由于传输协议是各自独立地进行处理,因此相互之间不会受到影响。
此外,那些知名端口号与传输层协议并无关系,只要端口号一致都将分配同一程序进行处理。
UDP
UDP是User Datagram Protocol的缩写。
UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。
即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络阻塞的行为。此外,传输途中即使出现丢包,UDP也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交由采用UDP的应用程序去处理。
由于UDP面向无连接,它可以随时发送数据。再叫上UDP本身的处理既简单又高效,因此经常用于以下几个方面:
- 包总量较少的通信(DNS、SNMP等)
- 视频、音频等多媒体通信
- 限定于LAN等特定网络中的应用通信
- 广播通信(广播、多播)
TCP
与UDP相比,TCP与UDP的区别相当大。它充分的实现了数据传输时各种控制能力,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。此外,YCP作为一种面向连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
通过序列号与确认应答提高可靠性
在TCP种,当发送端的数据到达接受主机时,接收端主机会返回一个已收到消息的通知。这个消息叫做确认应答(ACK)。TCP通过肯定的确认应答实现可靠的数据传输。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢的可能性很大。
在一定时间内没有等到确认应答,发送端就可以认为数据已经丢失,并进行重发。由此即使产生了丢包,仍然能够保证数据能到达对端,实现可靠传输。
此外,还有可能因为一些其他原因导致确认应答延迟到达,在源主机重发数据之后才到达的情况。此时源发送主机只要按照机制重发数据即可,但是对于目标主机来说简直是一种“灾难”。它会反复收到相同的数据,而为了对上层应用提供可靠的传输,必须得放弃重复的数据包。
为此,就必须引入一种机制,它能够识别是否已经收到数据,又能判断是否需要接受。接收端查询接收数据TCP首部中的序列号和数据的长度,将自己下一步应该接受的序号作为确认应答反送回去。这样通过序列号和确认应答号TCP就可以实现可靠传输。
重发超时如何确定
重发超时是指在重发数据之前,等待确认应答到来的那个特定时间间隔。如果超过了这个时间仍未收到确认应答,发送端将进行数据重发,所以问题就是如何确定这个重发超时的时间长度。
TCP要求不论在何种网络环境下都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。为此,它在每次发包时都会计算往返时间及其偏差。将这二者相加,重发超时的时间就是比这个总和要稍大一点的值。
综上所述,其重发超时时间一般设置为6秒左右。数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
当然数据也不会被无限次反复的重发,当达到一定重发次数之后,如果仍没有任何确认应答返回,就会判断为网络对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。
连接管理
三次握手
三次握手其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
进行三次握手的主要作用就是为了确认双方的接收能里和发送能力是否正常,指定自己初始化序列号为后面的可靠传输做准备,实质上就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,减缓TCP窗口大小的信息。
- 刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。
- 第一次握手:建立连接时,客户端发送SYN包(SYN=1,即同步序列号;seq=x)到服务器,并进入SYN_SENT状态,等待服务器确认。
- 第二次握手:服务器收到SYN包,必须确认客户的SYN(ack=x+1,ACK=1),同时自己也发送一个SYN包(SYN=1,seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态。
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=1,seq=x+1,ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED的状态,表示连接成功。

半连接队列
服务器第一次收到客户端的SYN之后,就会处于SYN_SEND的状态,此时双方还没有完全建立其连接,服务器就会把此状态下请求连接放到一个队列里,这种队列就称之为半连接队列。
当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…
seq的选择
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号,这个序号就是seq,也叫做ISN。
ISN随时间变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加一。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做出错的反应。因此ISN是动态生成的。
携带数据
在三次握手中,第一次、第二次握手是不可以携带数据的,但是第三次握手是可以携带数据的。
为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。
SYN攻击及解决办法
服务端的资源分配是在第二次握手时分配的,但客户端端资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是客户端在短时间内伪造大量不存在的IP地址,并向服务器不断的发送SYN包,服务器则回复确认包,并等待客户端回复。这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN攻击是一种典型的DDOS攻击。
常见的防御 SYN 攻击的方法有如下几种:
- 缩短超时时间
- 增大最大短连接数
- 过滤网关防护
- SYN cookie技术
四次挥手
- 客户端发出连接释放报文,即FIN包(FIN=1,seq=u,u为前面已经传过来的数据的最后一个字节的序号+1),并且停止发送数据,此时,客户端进入FIN_WAIT_1状态,FIN报文即使不携带数据,也要消耗一个序号。
- 服务器收到FIN包,发出确认报文,即ACK包(ACK=1,seq=v,ack=u+1),此时服务端就进入了CLOSE_WAIT状态,TCP服务器通知高层的应用程序,客户端向服务器的方向就释放了,这时候处于半关闭状态,因为即使客户端已经没有数据要发送了,但是服务器若要发送数据,客户端依然要接收。
- 客户端收到服务器的确认请求后,此时客户端就进入FIN_WAIT_2状态,等待服务器发送连接释放报文,即服务器的FIN包。
- 服务器最后的数据发送完毕后,就向客户端发送FIN包(FIN==1,ACK=1,seq=w,ack=u+1),此时服务器就进入了LAST_ACK状态,等待客户端确认。
- 客户端收到服务器的连接释放报文后,必须发出确认ACK包(ACK=1,seq=u+1,ack=w+1),此时客户端就进入了TIME_WAIT状态!!此时TCP连接还没有释放,必须经过2MSL的等待后,才进入CLOSED状态。
- 服务器收到客户端的ACK包后,立即进入CLOSED状态。结束这次的TCP连接。

等待2XMLS的意义
MSL说“最长报文段寿命”的意思,他是任何报文在网络上存在最长时间,超过这个时间报文将被丢弃。
为了保证客户端发送的最后一个ACK包能够到达服务器,因为这个ACK有可能丢失,从而导致处在LAST_ACK状态的服务器收不到对FIN_ACK的确认报文,服务器会重传这个FIN_ACK包,接着客户端再重传一次确认,重新启动时间等待器。最后客户端和服务端都能正常关闭。
假设客户端不等待2MLS,而是在发送完ACK之后直接释放关闭,一旦这个ACK丢失,服务器就无法正常进入关闭连接状态。因此可以总结为两个理由:
- 保证客户端发送的最后一个ACK报文段能够到达服务器
- 防止“已失效的连接请求报文段”出现在本连接中
TCP以段为单位发送数据
在建立TCP连接的同时,也可以确定发送数据包的单位,我们也可以称其为“最大消息长度(MSS)”。最理想的情况是,最大消息长度正好是IP中不会被分片的最大数据长度。
TCP在传送大量数据时,是以MSS的大小将数据进行分割发送,进行重发时也是以MSS为单位。
MSS是在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在TCP首部中写入MSS选项,告诉对方自己的接口能够适应的MSS大小。然后会在两者之间选择一个较小的值使用。

利用窗口控制提高速度
TCP以1个段为单位,每发一个段进行一次确认应答的处理,这样的传输方式的缺点就是,包的往返时间越长通信性能就越低。
为了解决这个这个问题,TCP引入了窗口的概念,确认应答不再是以每个分段,而是以更大的单位进行确认时,转发时间将会被大幅度的缩短怒。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。
窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。上图中窗口大小为4个段。这个机制实现了使用大量的缓冲区,通过对多个段同时进行确认应答的功能。
如下图所示,发送数据中高亮圈起的部分正是前面所提到的窗口。在这个窗口内的数据即便是没有收到确认应答也可以被发送出去。不过,在整个窗口的确认应答没有到达之前,如果其中部分数据出现丢包,那么发送端仍然要负责重传。为此发送端主机得设置缓存保留这些待被重传的数据,直到收到它们的确认应答。
在滑动窗口以外的部分包括尚未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再进行重发,此时数据就可以从缓存区清除。
收到确认应答的情况下,将窗口滑动到确认应答的序列号的位置。这样可以顺序的将多个段同时发送提高通信性能。这种机制也被称为滑动窗口控制。
窗口控制与高速重发控制
在没有使用窗口控制的时候,没有收到确认应答的数据都会被重发。而是用了窗口控制,就如下图所示,某些确认应答即使丢失也无需重发。
窗口在一定程度上较大时,即使有少部分的确认应答丢失也不会进行数据重发,可以通过下一个确认应答进行确认。
其次考虑一下某个报文段丢失的情况,接受主机如果收到一个应该接受的序号以外的数据时,会针对当前为止收到的数据返回确认应答。
当某一段报文丢失后,发送端会一直收到序号为1001的确认应答,这个确认应答好像在提醒发送端“我想接受的是从1001开始的数据”.因此,在窗口比较大,又出现报文段丢失的情况下,同一个序号的确认应答将会被重复不断地返回。而发送端主机如果连续3次收到同一个确认应答,就会将其所对应的数据进行重发。这种机制比之前提到的超时管理更加高效,因此也被称作为高速重发控制。
流控制
发送端根据自己的实际情况发送数据。但是接收端可能收到的是一个毫无关系的数据包又可能会在处理其他问题上花费一些时间。因此在为这个数据包做其他处理时会消耗一些时间,甚至在高负荷的情况下无法接收任何数据。如此一来,如果接收端将本应该接受的数据丢弃的话,就会触发重发机制,从而导致网络流量的无端浪费。
为了防止这种现象的发生,TCP提供一种机制可以让发送端根据接收端的实际接受能力控制发送的数据量。这就是所谓的流控制。
它的具体操作是,接收端主机向发送端主机通知自己可以接收数据的大小,于是发送端会发送不超过这个限度的数据。改大小限度就被称为窗口大小,上一节提高的窗口大小的值就是由接收端主机决定的。
TCP首部中,专门有一个字段来通知窗口的大小,接受主机将自己可以接收的缓冲区大小放入这个字段中,通知给发送端。这个字段的值越大,说明网络的吞吐量越高。
不过接收端的这个缓冲区一旦面临数据溢出时,窗口大小的值也会随之被设置为一个更小的值通知给发送端,从而控制数据发送量。也就是说,发送端主机会根据接收端主机的指示,对发送数据的量进行控制,这也就形成了一个完整的TCP流控制。
拥塞控制-慢启动
一般来说,如果在通信刚开始就发送大量的数据,可能会引发其他问题。在网络出现拥堵时,如果突然出现一个叫大量的数据,极有可能会导致整个网络的瘫痪。
TCP为了防止该问题的出现,在通信一开始就会发送一个叫做慢启动的算法得出的数值,对发送数据量进行控制。
首先,为了在发送端调节所要发送的数据的量,定义了一个叫做“拥塞窗口”的概念,于是在慢启动的时候,将这个拥塞窗口的大小设置为1个数据段(1MSS)发送数据,之后每次收到一次确认应答(ACk),拥塞窗口的值就加1.在发送数据包时,将拥塞窗口的大小与接收端主机通知的窗口大小做比较,然后按照它们当中较小的那个值,发送比其还小的数据量。
不过随着包的往返,拥塞窗口也会以1、2、4等指数函数的增长,拥堵状况甚至导致网络拥塞的发生。为了防止这些,引入了慢启动阈值的概念。只要拥塞窗口的值超过了这个阈值,在每收到一次确认应答时,只允许以下面这种比例放大拥塞窗口:
TCP的通信开始时,并没有设置相应的慢启动阈值,而是在超时重发时,才会设置为当时拥塞窗口一般的大小。
其它传输层协议
UDP-Lite
UDP-Lite是扩展UDP技能的一种传输层协议。在基于UDP的通信当中如果校验和出现错误,所收到的包将被全部丢弃。然而现实操作中,有些应用在面对这种情况时并不希望把已经收到的所有包丢弃。
UDP-Lite因此规定计算校验和的范围可以由应用自行决定。这个范围可以是包加上伪首部的校验和,可以是首部与伪首部的校验和计算,也可以是首部、伪首部与数据从起始到中间某个位置的校验和计算。
有了这样的机制,就可以针对不允许发生错误的部分进行校验和检查。对于其他部分,即使发生了错误,也会被忽略不计。而这个包也不会被丢弃,而是直接传给应用继续处理。
SCTP
SCTP主要用于进行通信的应用之间发送众多较小消息的情况,这些较小的应用消息被称为数据块,多个数据块组成一个包。
DCCP
DCCP是UDP的改进版本,它不能提供发送数据的可靠性传输,但是它面向连接,能够根据网络拥堵情况进行拥塞控制。
UDP首部格式

- 源端口号:表示发送端端口号,字段长16位。该字段是可选项,有时可能不会设置源端口号。没有源端口号的时候该字段的值设置为0,可用于不需要返回的通信中。
- 目标端口号:表示接收端端口,字段长度16位。
- 包长度:保存了UDP首部长度跟数据的长度之和,单位为字节。
- 校验和:校验和是为了提供可靠的UDP首部和数据而被设计。UDP也有可能不用校验和,此时该字段中填入0,这种情况下,由于不进行校验和计算,因此协议处理的开销就会降低,从而提高数据转发的速度。
TCP首部格式

TCP中没有表示包长度和数据长度的字段,因为可由IP层获知TCP的包长度,由TCP的包长可知数据的长度。
源端口号:表示发送端端口号,字段长16位。
目标端口号:表示接收端端口号,字段长16位。
序列号(seq):字段长32位,有时也叫序号,是指发送数据的位置,没法送一次数据,就会累加一次该数据字节数的大小。序列号不会从0或1开始,而是在建立连接时由计算机生成的随机数作为其初始值,通过SYN包传给接收端主机。然后再将每转发过去的字节数累加到初始值上表示数据的位置。此外,在建立连接和断开连接时发送的SYN包和FIN包虽然并不写携带数据,但是也会作为一个字节增加对应的序列号。
确认应答号(ack):字段长度32位,是指下一次应该接收到的数据的序列号。实际上它是指已收到确认应答号前一位为止的数据。发送端收到这个确认应答以后可以认为这个序号以前的数据已经被正常接收。
数据偏移:该字段表示TCP所传输的数据部分应该从TCP包的哪个位开始计算,当然也可以把它看作TCP首部的长度。
保留:长度为4位,为以后扩展时使用。
控制位:
字段长8位,每一位从左至右分别为CWR、ECE、URG、ACK、PSH、RST、SYN、FIN。这些控制标志也叫做控制位,当它们对应位上的值为1时,具体含义如下:- CWR:与后面的ECE标志都用于IP收不到ECN字段,标志通知对方以将拥塞窗口缩小。
- ECE:通知对方,从对方到这边的网络有拥塞。
- URG:表示包中有需要紧急处理的数据。
- ACK:确认应答的字段变为有效,TCP规定除了最初建立连接时的SYN包之外该位必须设置为1。
- PSH:表示需要将收到的数据立刻传给上层应用协议,为0时则表示先进行缓存。
- RST:表示TCP连接中出现异常必须强制断开连接。例如对方主机突然断电。
- SYN:用于建立连接,SYN为1表示希望建立连接,并在其序列号的字段进行序列号初始值设定。
- FIN:表示今后不会再有数据发送,希望断开连接。
窗口大小:长16为,用于通知从相同TCP收不到确认应答号所指位置开始能够接收端数据大小。TCP不允许发送超过此处所示大小的数据,不过如果窗口为0,则表示可以发送窗口探测,以了解最新的窗口大小,但这个数据必须是1个字节。
校验和:与UDP相似,区别在于TCP中无法关闭。
紧急指针:长为16位,只要在URG控制位为1时有效。该字段的数值表示本报文段中紧急数据的指针。正确来讲,从数据部分的首位到紧急指针的位置为止为紧急数据,因此可以说紧急指针指出了紧急数据的末尾在报文段中的位置。
选项:

- 类型2的MSS选项用于在建立连接时决定的那个最大段长度。
- 类型3的窗口扩大,是一个用来改善TCP吞吐量的选项。
- 类型8时间戳字段选项,用于高速通信中对序列号的管理。
- 类型4、5用于选择确认应答。