最近又重温了TCP/IP的知识,果然比第一次看的时候有感悟多了,那就写一点东西来总结一下。
网络模型
现在我们主要了解计算机网络的两种模型:OSI模型和TCP/IP模型。需要注意的是由于OSI模型过于复杂导致难以实现,因此导致TCP/IP模型更早地应用在现实中。这也使得TCP/IP模型成为事实上的标准,而OSI仅仅是纸面上的标准。
在OSI模型中,将计算机网络分成了7层,而在TCP/IP模型中则分成了4层,其各层对应关系如下表所示:
OSI | TCP/IP | 对应网络协议 | 所在位置 |
应用层 | 应用层 | TFTP, FTP, NFS, WAIS | 主机 |
表示层 | Telnet, Rlogin, SNMP, Gopher | ||
会话层 | SMTP, DNS | ||
传输层 | 传输层 | TCP, UDP | |
网络层 | 网际层 | IP, ICMP, ARP, RARP, AKP, UUCP | 媒介 |
数据链路层 | 网络接口层 | FDDI, Ethernet, Arpanet, PDN, SLIP, PPP | |
物理层 | IEEE 802.1A, IEEE 802.2到IEEE 802.11 |
UDP与TCP协议
在整个计算机网络体系中,最核心的当属是位于传输层的TCP与UDP协议了。因为他们位于主机协议栈的最底层,向上方应用层提供不同的数据交付方式。
因为UDP协议相对TCP协议来说相对简单,就先回顾一下UDP协议。
UDP协议
特点
UDP的全称也叫做用户数据报协议(User Datagram Protocal),它有以下几个值得注意的特点:
- UDP不能保证可靠传输,也就更不能保证所发送的数据的到达顺序,它所实现的是尽最大的努力交付。
- UDP是面向数据报文的、无连接的协议,因此它的开销低并且发送器前的时延小(因为不用建立连接啊),面向报文也使得IP层在传输UDP协议的报文时既不会拆分也不会合并。
- UDP可以支持一对一、一对多、多对一、多对多的通信。
- UDP没有拥塞控制功能,它的发送速率不会随着网络出现的拥塞而降低,所以它的实时性较好。这也是许多视频聊天应用采用它的原因。
报文格式
如下是UDP报文头部格式
其中从1到8字节分别是来源端口号、目的端口号、报文长度、检验和,每个字段各占两字节。
UDP的通信方式较为简单,发送端发送完一个报文继续发送下一个,待将所有报文发送完毕通信就结束了。
接收方也是如此。
TCP协议
传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP协议所要实现的功能是端到端之间的可靠传输,因此,相较于UDP协议而言,TCP协议要复杂的多。
特点
相对于UDP协议,TCP协议有下面的几个特点:
- TCP协议保证可靠传输,也就是说发送的数据是什么样,接收的数据也是什么样。
- TCP协议是有连接的、面向数据流的协议。有连接是说数据传送前通信双方需要建立连接、通信完毕后需要断开连接,不过这里所提到的连接都是逻辑上的连接。面向数据流的意思是说发送方应用程序发送的数据是什么顺序,接收方应用读取的接收到的数据也是什么顺序。
- TCP协议提供的是端到端的通信,也就是说一条TCP连接只能提供一对一的通信。不过,一个应用可以同时建立多条TCP连接来实现与多个目标的通信。
- TCP协议提供拥塞控制功能,会在网络状况良好的情况下适当提高发送/接收速率,反之则适当降低发送/接收速率。这样,将会提高对网络的利用率。
此外,还需要注意的是,TCP提供的是全双工的通信。
数据封包格式
要理解TCP协议中各功能的实现,要先从TCP的数据封包结构开始。
下面的图片就是TCP数据封包的结构示意图:
各个字段功能如下:
- 来源端口(2字节):标识来源端口号。
- 目的端口(2字节):标识目的端口号。
- 序列号码(4字节):表明此封包在字节流中的顺序号。因为TCP是面向字节流的协议,需要保证最终的数据顺序与发送方发送的顺序一致,所以需要这个字段来表明该封包在字节流中的位置。
- 确认号码(4字节):对此前按顺序收到的最后一个封包的序列号码的确认。
例如:当33号以前的封包都已经完整的到达了后,就向发送方发送确认号码为34(确认的封包号码+1)来告诉发送方:33号以前的所有封包我都已经收到了,可以向我发送34号封包了。
这里要注意: 如果收到了34、35、37号封包而未收到36号封包,则向发送端发送确认号为35的封包。
总之要记住,确认号=N,表示N-1及其之前的封包都已经收到。
- 报头长度(1字节):指示报文头部的长度。
但是需要注意,这里的单位是4字节。例如,这个属性的值是15的话则说明报文头部的长度是60(15*4)字节。 - 保留字段(10位):暂时没打算好干啥用,一律置0。
- 标识符(6位):每个比特位分别表示如下标识符:
URG—为1表示高优先级数据包,紧急指针字段有效。
ACK—为1表示确认号字段有效。TCP规定连接建立后,所有传送的报文段都必须把该字段置为1。
PSH—为1表示是带有PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。
RST—为1表示出现严重差错。可能需要重现创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。
SYN—为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
FIN—为1表示发送方没有数据要传输了,要求释放连接。 - 窗口(2字节):表示从确认号开始,本报文的源方可以接收的字节数,即源方接收窗口大小。用于流量控制。
- 校验和(2字节)对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段。
- 紧急指针(2字节):本报文段中的紧急数据的最后一个字节的序号。
选项字段(最多40字节):每个选项的开始是1字节的kind字段,说明选项的类型。
其中上面的3、4、7、8是重点。
超时重传
为了搞明白下面要说的TCP的一些机制,首先需要知道TCP协议能够实现可靠传输的一个基本的原理–超时重传。
说来很简单,其实就是收到当接收方收到一个数据封包的时候就向发送方发送一个确认数据封包。而当发送方发送完一个数据封包经过一段时间没有收到接收方的确认封包时,就会将上一个封包再次向接收方发送一次。
当然,TCP中真正的机制比这个要复杂的多,但是基于的原理都是一样的。
TCP连接管理
在说TCP连接之前先插播一个小故事。
说是有一天,一个程序员到酒吧去喝酒,于是跟服务员之间发生了如下的对话。
程序员:我要一瓶酒。
服务员:你确定你要一瓶酒?
程序员:我确定我要一瓶酒。
于是服务员就给了程序员一瓶酒。
是不是很罗嗦?没错,这就是典型的TCP连接建立的过程。如下面的图:
图中的小人就是客户端,另一边不用说也知道就是服务器端。图中画的比较简略,大概过程是下面这样:
下面过程中,ACK表示上面说的TCP包头中的确认标识,ack表示确认号(4字节那个)。
- 客户端向服务器发送建立连接请求数据包,其中包头内容SYN=1,seq=x(自己随机挑选的起始序号)。(我要一瓶酒)
- 服务器收到请求后,如果同意建立连接,就向客户端发送同意建立连接请求数据封包,其包头内容SYN=1,ACK=1,ack=x+1(还记得为什么吗),seq=y(同样也是自己随机挑选的)。(你确定你要一瓶酒吗)
- 客户端收到服务器发来的确认请求后,也向服务器发送确认封包,其内容ACK=1,ack=y+1(原理同上),seq=x+1(毕竟已经发送过seq=x的封包了),随后客户端就进入连接建立状态,而服务器就在收到这个确认封包后也进入连接建立状态。(我确定我要一瓶酒)
以上步骤就是俗称的“TCP三次握手”。
当然,最初的时候服务器肯定是处于监听状态啦,要不然怎么能够对客户端的数据封包做出回应呢。
要注意的是,这里有个很经典的问题就是为什么在收到服务器的确认封包后还要再向服务器发送一个确认封包呢?
这主要是因为TCP协议的下层网络是不可靠的,也就是说数据封包可能丢失也可能滞留在某个节点很长时间。
所以这就有一种可能就是客户端发送给服务器的封包在网络中滞留了,这样客户端当然也就不会收到服务器发来的确认封包。还记得TCP有个超时重传机制吗?没错,这个时候客户端是以为自己发出去的封包在网络中是丢了的,于是过一段时间又会重传刚才的封包。
幸运的是,这次重传的封包十分顺利的到达了服务器,于是服务器也十分顺利的向客户端发出了确认封包。本来就要愉快的建立连接了,可是天有不测风云,刚才滞留的请求封包此时到达了服务器这里。
这个时候服务器就懵了,这到底是客户端又要跟我建立一次连接还是刚才滞留的封包又给了我呢?
于是这个时候就需要客户端再发送一个确认封包给服务器了。
当然,有连接建立,就有连接释放,客户端与服务器之间的连接释放过程大概是下面这个样子的。
跟建立连接的时候差不多,大概过程大概向下面这样:
- 客户端向服务器发送连接请求释放封包,封包内容为FIN=1,seq=u。
- 服务器收到后,决定要跟客户端释放连接,可是还有数据没传送完啊,就先发一个封包告诉客户端我可以释放连接,你可以不用向我发送数据了,可是我还有数据没有传送完,所以在我告诉你结束之前你得一直接收我的数据(别忘了,TCP可是全双工的)。所以所发送封包内容为:ACK=1,ack=u+1,seq=v。(FIN=0表示我还有数据要发送)
- 服务器发送完数据后,告诉客户端我都发送完了,可以结束了。于是发送封包为FIN=1,ACK=1,ack=u+1(这里得注意啦),seq=w(中间还传输过数据,所以可能不是v+1)。
- 客户端收到服务器的确认后,再次向服务器发送确认,内容是ACK=1,ack=w+1,seq=u+1。
- 发送完上面的确认封包后,客户端再等一段时间(2MSL)后,就断开连接。至此,连接正常释放。
如上步骤就是俗称的“TCP4次挥手”。
这里之所以是4次,主要是与建立连接时相比,服务器的确认和结束被分成了两个封包分别发送了出去。
当然,这里也有个比较经典的问题就是客户端为什么在发送完最后一个封包后还要再等待一段时间?
这里也主要是因为TCP协议下层的网络并不是很可靠,有可能客户端发出的最后一个封包在路上丢了而导致服务器等待很长世间浪费服务器资源。于是就让客户端再等待一段时间,这样当服务器收不到客户端发来的确认封包时,还会再重传一个连接结束封包,此时的客户端再次受到这个连接结束封包后还可以再重传一次确认封包来让服务器正常结束连接。这样,就不会白白浪费服务器资源了。
一个数据包的旅程
最后,再从整个网络的范围来看,一个数据封包到底是要经过怎样的旅程才会从出发地到达目的地呢?
以TCP协议为例,当TCP协议接到上层应用层交付下来的数据封包后,就在这个数据封包的前面加上TCP的头部。
然后,再交付给下层的网络层。
网络层接收到上层传输层传递下来的封包后,就在封包的头部加上IP协议的头部和网际接口层的首部和头部后经过层层路由,最终到达距离目的主机最近的路由器。
然后距离目的主机最近的路由器再对数据包进行解包,去掉网际接口层和网络层的封装后将数据包送到目的主机的传输层(TCP接收缓冲池),再由目的主机的传输层交给上层的应用层。
一图以蔽之: