TCP/IP 协议
首先给大家说明的是,TCP/IP 协议它其实是一个协议族,包含了众多的协议,譬如应用层协议 HTTP、FTP、MQTT…以及传输层协议 TCP、UDP 等这些都属于 TCP/IP 协议。
所以,我们一般说 TCP/IP 协议,它不是指某一个具体的网络协议,而是一个协议族。网络通信当中涉及到的网络协议实在太多了,对于应用开发来说,可能使用更多的是应用层协议,譬如 HTTP、FTP、SMTP等。
HTTP 协议
HTTP 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网数据通信的基础。HTTP 的应用最为广泛,譬如大家经常会打开网页浏览器查询资料,通过浏览器便可开启 HTTP 通信。
HTTP 协议工作于客户端(用户)、服务器端(网站)模式下,浏览器作为 HTTP 客户端通过 URL 向HTTP 服务端即 WEB 服务器发送请求。Web 服务器根据接收到的请求后,向客户端发送响应信息。借助这种浏览器和服务器之间的 HTTP 通信,我们能够足不出户地获取网络中的各种信息。
FTP 协议
FTP 协议的英文全称为 File Transfer Protocol,简称为 FTP,它是一种文件传输协议,从一个主机向一个主机传输文件的协议。FTP 协议同样也是基于客户端-服务器模式,在客户端和服务器之间进行文件传输,譬如我们通常会使用 FTP 协议在两台主机之间进行文件传输,譬如一台 Ubuntu 系统主机和一台 Windows系统主机,将一台主机作为 FTP 服务器、另一台主机作为FTP 客户端,建立 FTP 连接之后,客户端可以从服务器下载文件,同样也可以将文件上传至服务器。
FTP 除了基本的文件上传/下载功能外,还有目录操作、权限设置、身份验证等机制,许多网盘的文件传输功能都是基于 FTP 实现的。
其它的 TCP/IP 协议就不给大家介绍了,有兴趣的读者可以自行百度了解。
TCP 协议
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于 IP 的传输协议。由图 29.2.1 可知,TCP 协议工作在传输层,对上服务 socket 接口,对下调用 IP 层(网络层)。
关于 TCP 协议我们需要理解的重点如下:
①、TCP 协议工作在传输层,对上服务 socket 接口,对下调用 IP 层;
②、TCP 是一种面向连接的传输协议,通信之前必须通过三次握手与客户端建立连接关系后才可通信;
③、TCP 协议提供可靠传输,不怕丢包、乱序。
TCP 协议如何保证可靠传输?
①、TCP 协议采用发送应答机制,即发送端发送的每个 TCP 报文段都必须得到接收方的应答,才能认为这个 TCP 报文段传输成功。
②、TCP 协议采用超时重传机制,发送端在发送出一个 TCP 报文段之后启动定时器,如果在定时时间内未收到应答,它将重新发送该报文段。
③、由于 TCP 报文段最终是以 IP 数据报发送的,而 IP 数据报到达接收端可能乱序、重复、所以 TCP协议还会将接收到的 TCP 报文段重排、整理、再交付给应用层。
TCP 协议的特性
TCP 协议的特点如下所示:
⚫ 面向连接的
TCP 是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一个 TCP连接,否则将无法发送数据,通过三次握手建立连接,后面在介绍。
⚫ 确认与重传
当数据从主机 A 发送到主机 B 时,主机 B 会返回给主机 A 一个确认应答;TCP 通过确认应答 ACK 实现可靠的数据传输。当发送端将数据发送出去之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,数据丢失的可能性比较大。
在一定的时间内如果没有收到确认应答,发送端就可以认为数据已经丢失,并进行重发。由此,即使产生了丢失,仍然可以保证数据能够到达对端,实现可靠传输。
⚫ 全双工通信
TCP 连接一旦建立,就可以在连接上进行双向的通信。任何一个主机都可以向另一个主机发送数据,数据是双向流通的,所以 TCP 协议是一个全双工的协议。
⚫ 基于字节流而非报文
将数据按字节大小进行编号,接收端通过 ACK 来确认收到的数据编号,通过这种机制能够保证 TCP 协议的有序性和完整性,因此 TCP 能够提供可靠性传输。
⚫ 流量控制(滑动窗口协议)
TCP 流量控制主要是针对接收端的处理速度不如发送端发送速度快的问题,消除发送方使接收方缓存溢出的可能性。TCP 流量控制主要使用滑动窗口协议,滑动窗口是接受数据端使用的窗口大小,用来告诉发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。这个窗口大小就是我们一次传输几个数据。对所有数据帧按顺序赋予编号,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧
才允许接收。这样通过调整发送方窗口和接收方窗口的大小可以实现流量控制。
⚫ 差错控制
TCP 协议除了确认应答与重传机制外,TCP 协议也会采用校验和的方式来检验数据的有效性,主机在接收数据的时候,会将重复的报文丢弃,将乱序的报文重组,发现某段报文丢失了会请求发送方进行重发,因此在 TCP 往上层协议递交的数据是顺序的、无差错的完整数据。
⚫ 拥塞控制
如果网络上的负载(发送到网络上的分组数)大于网络上的容量(网络同时能处理的分组数),就可能引起拥塞,判断网络拥塞的两个因素:延时和吞吐量。拥塞控制机制是:开环(预防)和闭环(消除)。
流量控制是通过接收方来控制流量的一种方式;而拥塞控制则是通过发送方来控制流量的一种方式。
TCP 发送方可能因为 IP 网络的拥塞而被遏制,TCP 拥塞控制就是为了解决这个问题(注意和 TCP 流量控制的区别)。
TCP 拥塞控制的几种方法:慢启动,拥塞避免,快重传和快恢复。
TCP 报文格式
从图 29.2.4 可知,当数据由上层发送到传输层时,数据会被封装为 TCP 数据段,我们将其称为 TCP 报文(或 TCP 报文段),TCP 报文由 TCP 首部+数据区域组成,一般 TCP 首部通常为 20 个字节大小,具体格式如下图所示:
下面分别对其中的字段进行介绍:
源端口号和目标端口号
源端口号和目标端口号各占 2 个字节,一个 4 个字节,关于端口号的概念会在 29.5.3 小节进行介绍。
每个 TCP 报文都包含源主机和目标主机的端口号,用于寻找发送端和接收端应用进程,这两个值加上 IP 首部中的源 IP 地址和目标 IP 地址就能确定唯一一个 TCP 连接。有时一个 IP 地址和一个端口号也称为 socket(插口)。
序号
占 4 个字节,用来标识从 TCP 发送端向 TCP 接收端发送的数据字节流,它的值表示在这个报文段中的第一个数据字节所处位置码,根据接收到的数据区域长度,就能计算出报文最后一个数据所处的序号,因为TCP 协议会对发送或者接收的数据进行编号(按字节的形式),那么使用序号对每个字节进行计数,就能很轻易管理这些数据。
在 TCP 传送的数据流中,每一个字节都有一个序号。例如,一报文段的序号为 300,而且数据共 100 字节,则下一个报文段的序号就是 400;序号是 32bit 的无符号数,序号到达 2^32-1 后从 0 开始。
确认序号
确认序号占 4 字节,是期望收到对方下次发送的数据的第一个字节的序号,也就是期望收到的下一个报文段的首部中的序号;确认序号应该是上次已成功收到数据字节序号+1。只有 ACK 标志为 1 时,确认序号才有效。TCP 为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输,因此确认序号通常会与反向数据(即接收端传输给发送端的数据)封装在同一个报文中(即捎带),所以连接的每一端都必须保持每个方向上的传输数据序号准确性。
首部长度
首部长度字段占 4 个 bit 位,它指出了 TCP 报文段首部长度,以字节为单位,最大能记录 15*4=60 字节的首部长度,因此,TCP 报文段首部最大长度为 60 字节。在字段后接下来有 6bit 空间是保留未用的,供以后应用,现在置为 0。
6 个标志位:URG/ACK/PSH/RST/SYN/FIN
保留位之后有 6 个标志位,分别如下:
①、URG:首部中的紧急指针字段标志,如果是 1 表示紧急指针字段有效。
②、ACK:只有当 ACK=1 时,确认序号字段才有效。
③、PSH:当 PSH=1 时,接收方应该尽快将本报文段立即传送给其应用层。
④、RST:当 RST=1 时,表示出现连接错误,必须释放连接,然后再重建传输连接。复位比特还用来拒绝一个不法的报文段或拒绝打开一个连接。
⑤、SYN:SYN=1,ACK=0 时表示请求建立一个连接,携带 SYN 标志的 TCP 报文段为同步报文段。
⑥、FIN:为 1 表示发送方没有数据要传输了,要求释放连接。
窗口大小
占用 2 个字节大小,表示从确认号开始,本报文的发送方可以接收的字节数,即接收窗口大小,用于流量控制。
校验和
对整个的 TCP 报文段,包括 TCP 首部和 TCP 数据,以 16 位字进行计算所得。这是一个强制性的字段。
紧急指针
本报文段中的紧急数据的最后一个字节的序号。
选项
选项字段的大小是不确定的,最多 40 字节。
建立 TCP 连接:三次握手
前面我们提到过,TCP 协议是一个面向连接的协议,双方在进行网络通信之间,都必须先在双方之间建立一条连接,俗称“握手”,可能在学习网络编程之前,大家或多或少都听过“三次握手”、“四次挥手”这些词语,那么“三次握手”、“四次挥手”究竟是什么意思,本小节将详细讨论一个 TCP 连接是如何建
立的,需要经过哪些过程。
“三次握手”其实是指建立 TCP 连接的一个过程,通信双方建立一个 TCP 连接需要经过“三次握手”这样一个过程。
首先建立连接的过程是由客户端发起,而服务器会时刻监听、等待着客户端的连接,其示意图如下所示:
TCP 连接一般来说会经历以下过程:
⚫ 第一次握手
客户端将 TCP 报文标志位 SYN 置为 1,随机产生一个序号值 seq=J,保存在 TCP 首部的序列号(Sequence Number)字段里,指明客户端打算连接的服务器的端口,并将该数据包发送给服务器端,发送完毕后,客户端进入 SYN _SENT 状态,等待服务器端确认。
⚫ 第二次握手
服务器端收到数据包后由标志位 SYN=1 知道客户端请求建立连接,服务器端将 TCP 报文标志位 SYN和 ACK 都置为 1,ack=J+1,随机产生一个序号值 seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入 SYN _RCVD 状态。
⚫ 第三次握手
客户端收到确认后,检查 ack 是否为 J+1,ACK 是否为 1,如果正确则将标志位 ACK 置为 1,ack=K+1,并将该数据包发送给服务器端,服务器端检查 ack 是否为 K+1,ACK 是否为 1,如果正确则连接建立成功,客户端和服务器端进入 ESTABLISHED 状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
注意:上面写的 ack 和 ACK,不是同一个概念:
小写的 ack 代表的是头部的确认号 Acknowledge number,ack。
大写的 ACK,则是 TCP 首部的标志位,用于标志的 TCP 包是否对上一个包进行了确认操作,如果确认了,则把 ACK 标志位设置成 1。
在完成握手后,客户端与服务器就成功建立了连接,同时双方都得到了彼此的窗口大小,序列号等信息,在传输 TCP 报文段的时候,每个 TCP 报文段首部的 SYN 标志都会被置 0,因为它只用于发起连接,同步序号。
为什么需要三次握手?
其实 TCP 三次握手过程跟现实生活中的人与人之间的电话交流是很类似的,譬如 A 打电话给 B:
A:“喂,你能听到我的声音吗?”
B:“我听得到呀,你能听到我的声音吗?”
A:“我能听到你,………”
……
经过三次的互相确认,大家就会认为对方对听的到自己说话,才开始接下来的沟通交流,否则,如果不进行确认,那么你在说话的时候,对方不一定能听到你的声音。所以,TCP 的三次握手是为了保证传输的安全、可靠。
关闭 TCP 连接:四次挥手
除了“三次握手”,还有“四次挥手”,“四次挥手”(有一些书也会称为四次握手)其实是指关闭 TCP连接的一个过程,当通信双方需要关闭 TCP 连接时需要经过“四次挥手”这样一个过程。
四次挥手即终止 TCP 连接,就是指断开一个 TCP 连接时,需要客户端和服务端总共发送 4 个包以确认连接的断开。在 socket 编程中,这一过程由客户端或服务端任一方执行 close 来触发。
由于 TCP 连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个 FIN 来终止这一方向的连接,收到一个 FIN 只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个 TCP 连接上仍然能够发送数据,直到这一方向也发送了 FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
四次挥手过程的示意图如下:
挥手请求可以是 Client 端,也可以是 Server 端发起的,我们假设是 Client 端发起:
⚫ 第一次挥手
Client 端发起挥手请求,向 Server 端发出一个 FIN 报文段主动进行关闭连接,此时报文段的 FIN 标志位被设置为 1。此时,Client 端进入 FIN _WAIT _1 状态,这表示 Client 端没有数据要发送给 Server 端了。
⚫ 第二次挥手
Server 端收到了 Client 端发送的 FIN 报文段,向 Client 端返回一个 ACK 报文段,此时报文段的 ACK标志位被设置为 1。ack 设为 seq 加 1,Client 端进入 FIN_WAIT_2 状态,Server 端告诉 Client 端,我确认并同意你的关闭请求。
⚫ 第三次挥手
Server 端向 Client 端发送一个 FIN 报文段请求关闭连接,此时报文段的 FIN 标志位被设置为 1,同时Client 端进入 LAST_ACK 状态。
⚫ 第四次挥手
Client 端收到 Server 端发送的 FIN 报文段后,向 Server 端发送 ACK 报文段(此时报文段的 ACK 标志位被设置为 1),然后 Client 端进入 TIME_WAIT 状态。Server 端收到 Client 端的 ACK 报文段以后,就关闭连接。此时,Client 端等待 2MSL 的时间后依然没有收到回复,则证明 Server 端已正常关闭,那好,Client端也可以关闭连接了。
这就是关闭 TCP 连接的四次挥手过程。所以 TCP 协议传输数据的整个过程就如同下图所示:
在正式进行数据传输之前,需要先建立连接,当成功建立 TCP 连接之后,双方就可以进行数据传输了。当不再需要传输数据时,关闭连接即可!
TCP 状态说明 TCP 协议在建立连接、断开连接以及数据传输过程中都会呈现出现不同的状态,不同的状态采取的动作也是不同的,需要处理各个状态之间的关系。图 29.5.2、图 29.5.3 以及图 29.5.4 中就出现了一些状态标
志,除了这些状态标志之外,还有其它一些 TCP 状态,对这些 TCP 状态的说明如下所示:
⚫ CLOSED 状态:表示一个初始状态。
⚫ LISTENING 状态:这是一个非常容易理解的状态,表示服务器端的某个 SOCKET 处于监听状态,监听客户端的连接请求,可以接受连接了。譬如服务器能够提供某种服务,它会监听客户端 TCP端口的连接请求,处于 LISTENING 状态,端口是开放的,等待被客户端连接。
⚫ SYN_SENT 状态(客户端状态):当客户端调用 connect()函数连接时,它首先会发送 SYN 报文给服务器请求建立连接,因此也随即它会进入到了 SYN_SENT 状态,并等待服务器的发送三次握手中的第 2 个报文。SYN_SENT 状态表示客户端已发送 SYN 报文。
⚫ SYN_REVD 状态(服务端状态):这个状态表示服务器接受到了 SYN 报文,在正常情况下,这个状态是服务器端的 SOCKET 在建立 TCP 连接时的三次握手过程中的一个中间状态,很短暂,基本上用 netstat 你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次 TCP 握手
过程中最后一个 ACK 报文不予发送。因此这种状态时,当收到客户端的 ACK 报文后,它会进入到 ESTABLISHED 状态。
⚫ ESTABLISHED 状态:这个容易理解了,表示连接已经建立了。
⚫ FIN_WAIT_1 和 FIN_WAIT_2 状态:其实 FIN_WAIT_1 和 FIN_ WAIT_2 状态的真正含义都是表示等待对方的 FIN 报文。而这两种状态的区别是:FIN_WAIT_1 状态实际上是当 SOCKET 在ESTABLISHED 状态时,它想主动关闭连接,向对方发送了 FIN 报文,此时该 SOCKET 即进入到
FIN_WAIT_1 状态。而当对方回应 ACK 报文后,则进入到 FIN_WAIT_2 状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应 ACK 报文,所以 FIN_WAIT_1 状态一般是比较难见到的,而 FIN_WAIT_2 状态还有时常常可以用 netstat 看到。
⚫ TIME_WAIT 状态:表示收到了对方的 FIN 报文,并发送出了 ACK 报文,就等 2MSL 后即可回到 CLOSED 可用状态了。如果 FIN_WAIT_1 状态下,收到了对方同时带 FIN 标志和 ACK 标志的报文时,可以直接进入到 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态。
⚫ CLOSE_WAIT 状态:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方 close 一个SOCKET 后发送 FIN 报文给自己,你系统毫无疑问地会回应一个 ACK 报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,
如果没有的话,那么你也就可以 close 这个 SOCKET,发送 FIN 报文给对方,也即关闭连接。所以你在 CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。
⚫ LAST_ACK 状态:它是被动关闭一方在发送 FIN 报文后,最后等待对方的 ACK 报文。当收到 ACK报文后,也即可以进入到 CLOSED 状态了。
以上便是关于 TCP 状态的一些描述说明,状态之间的转换关系就如上图中所示。
UDP 协议
除了 TCP 协议外,还有 UDP 协议,想必大家都听过说,UDP 是 User Datagram Protocol 的简称,中文名是用户数据报协议,是一种无连接、不可靠的协议,同样它也是工作在传顺层。它只是简单地实现从一端主机到另一端主机的数据传输功能,这些数据通过 IP 层发送,在网络中传输,到达目标主机的顺序是无法预知的,因此需要应用程序对这些数据进行排序处理,这就带来了很大的不方便,此外,UDP 协议更没有流量控制、拥塞控制等功能,在发送的一端,UDP 只是把上层应用的数据封装到 UDP 报文中,在差错检测方面,仅仅是对数据进行了简单的校验,然后将其封装到 IP 数据报中发送出去。而在接收端,无论是否收到数据,它都不会产生一个应答发送给源主机,并且如果接收到数据发送校验错误,那么接收端就会丢弃该UDP 报文,也不会告诉源主机,这样子传输的数据是无法保障其准确性的,如果想要其准确性,那么就需要应用程序来保障了。
UDP 协议的特点:
①、无连接、不可靠;
②、尽可能提供交付数据服务,出现差错直接丢弃,无反馈;
③、面向报文,发送方的 UDP 拿到上层数据直接添加个 UDP 首部,然后进行校验后就递交给 IP 层,而接收的一方在接收到 UDP 报文后简单进行校验,然后直接去除数据递交给上层应用;
④、速度快,因为 UDP 协议没有 TCP 协议的握手、确认、窗口、重传、拥塞控制等机制,UDP 是一个无状态的传输协议,所以它在传递数据时非常快,即使在网络拥塞的时候 UDP 也不会降低发送的数据。
UDP 虽然有很多缺点,但也有自己的优点,所以它也有很多的应用场合,因为在如今的网络环境下,UDP 协议传输出现错误的概率是很小的,并且它的实时性是非常好,常用于实时视频的传输,比如直播、网络电话等,因为即使是出现了数据丢失的情况,导致视频卡帧,这也不是什么大不了的事情,所以,UDP协议还是会被应用与对传输速度有要求,并且可以容忍出现差错的数据传输中。
端口号的概念
前面给大家介绍了 IP 地址,互联网中的每一台主机都需要一个唯一的 IP 地址以标识自己的身份,网络中传输的数据包通过 IP 地址找到对应的目标主机;一台主机通常只有一个 IP 地址,但主机上运行的网络进程却通常不止一个,譬如 Windows 电脑上运行着 QQ、微信、钉钉、网页浏览器等,这些进程都需要进行网络连接,它们都可通过网络发送/接收数据,那么这里就有一个问题?主机接收到网络数据之后,如何确定该数据是哪个进程对应的接收数据呢?其实就是通常端口号来确定的。
端口号本质上就是一个数字编号,用来在一台主机中唯一标识一个能上网(能够进行网络通信)的进程,端口号的取值范围为 0~65535。一台主机通常只有一个 IP 地址,但是可能有多个端口号,每个端口号表示一个能上网的进程。一台拥有 IP 地址的主机可以提供许多服务,比如 Web 服务、FTP 服务、SMTP 服务等,这些服务都是能够进行网络通信的进程,IP 地址只能区分网络中不同的主机,并不能区分主机中的这些进程,显然不能只靠 IP 地址,因此才有了端口号。通过“IP 地址+端口号”来区分主机不同的进程。
很多常见的服务器它都有特定的端口号,具体详情如下表所示: