网络协议
在计算机诞生后,从单机模式应用发展到将多台计算机连接起来,形成计算机网络,使信息共享、多机协作、大规模计算等成为现实,历经了20多年的时间。计算机网络需要解决的第 个问题是如何无障碍地发送和接收数据。而这个发送和接收数据的过程需要相应的协议来支撑,按互相可以理解的方式进行数据的打包与解包,使不同厂商的设备在不同类型的操作系统上实现顺畅的网络通信。 TCP/IP ( Transmission Control Protocol I Internet Protocol )中文译为传输控制协议/因特网互联协议,这个大家族里的其他知名协议还有 HTTP, HTTP、 FTPS、SMTP、UDP、ARP、PPP IEEE802.x 等。 TCP/IP 是当前流行的网络传输协议框架,从严格意义上讲它是一个协议族,因为 TCP、IP 是其中最为核心的协议,所以就把该协议族称为 TCP/IP 。而另一个是耳熟能详的 ISO/OSI 的七层传输协议,其中 OSI( Open System Interconnection )的出发点是想设计出计算机世界通用的网络通信基本框架,但它已经被淘汰。 TCP/IP 是在不断解决实际问题中成长起来的协议族,是经过市场检验的事实标准,已经很难被取代。就像即使键盘的布局不那么合理,比如字母A被设计在左手小指位置,不利于敲击,但原来的键盘布局已经成为群体习惯的事实标准。 TCP 分层框架图如下图所示,为了表示网络拓扑图在连接层面上的机器对等理念,故下图中采用A机器和B机器的说法,而不是服务器和客户端的说法。  链路层:单个0、1是没有意义的,链路层以字节为单位把0与1进行分组,定义数据帧,写入源和目标机器的物理地址、数据、校验位来传输数据。下图所示是以太网的帧协议。  MAC地址长 个字节共 48 位,通常使用十六进制数表示。使用 ifconfig-a 令即可看到 MAC 地址。如下图所示的 f4:5c:89,即前 24 位由管理机构统一分配,24 位由厂商自己分配,保证网卡全球唯一。网卡就像家庭地址一样 ,是计算机世界范围内的唯一标识。  网络层: 根据 IP 定义网络地址,区分网段。子网内根据地址解析协议( ARP)进行 MAC 寻址 子网外进行路由转发数据包,这个数据包即 IP 数据包。 传输层: 数据包通过网络层发送到目标计算机后,应用程序在传输层定义逻辑端口 确认身份后,将数据包交给应用程序,实现端口到端口间通信。最典型的传输层协议是 UDP和TCP,UDP 只是在 IP 数据包上增加端口等部分信息 是面向无连接的,是不可靠传输,多用于视频通信、电话会议等(即使少一帧数据也无妨)。与之相反,TCP 是面向连接的。所谓面向连接一种端到端间通过失败重传机制建立的可靠数据传输方式,给人感觉是有一条固定的通路承载着数据的可靠传输。 应用层: 传输层的数据到达应用程序时,以某种统一规定的协议格式解读数据。比如 E-mail 在各个公司的程序界面、操作、管理方式都不一样,但是都能够读取邮件内容 是因为 SMTP 协议就像传统的书信恪式一样,按规定填写邮编及收信人信息。 总结一下 程序在发送消息时,应用层接既定的协议打包数据 随后由传输层加上双方的端口 ,由网络层加上双方的 IP 地址,由链路层加上双方的 MAC 地址将数据拆分成数据帧,经过多个路由器,网关后到达目标机器。
IP协议
IP 向无连接、无状态的,没有额外的机制保证发送的包是否有序到达。 IP首先规定出 IP 地址格式,该地址相当于在逻辑意义上进行 网段的划分,给每台计算机额外设置了一个唯一的详细地址。既然链路层可以通过唯一的 MAC地址找到机器,为什么还需要通过唯一的IP地址再来标识呢?简单地说,在世界范围内,不可能通过广播的方式,从数以千万计的计算机里找到目标 MAC 地址的计算机而不超时。 IP 地址属于网络层,主要功能在 WLAN 内进行路由寻址,选择最佳路由。 IP文格式如下图所示,共 32 个字节,通常用十进制数来表示。IP地址的子网掩码0xffffff00表示 255.255.255.0 ,掩码相同,则在同一子网内。 IP 协议在 IP 报头中记录IP 地址和目标 IP 地址,如下图所示。  IP 报文在互联网上传输时,可能要经历多个物理网络,才能从源主机到达目标主机。比如在手机上给某个 PC 端的朋友发送一个信息,经过无线网的 IEEE 802.lx 认证,转到光纤通信上,然后进入内部企业网 802.3 ,并最终到达目标 PC 由于不同硬件的物理特性不同,对数据帧的最大长度都有不同的限制,这个最大长度被称为最大传输单元,即 MTU ( Maximum Transmission Unit )。那么在不同的物理网之间就可能需要对 IP 报文进行分片,这个工作通常由路由器负责完成。 IP是TCP/IP的基石,几乎所有其他协议都建立在 IP 所提供的服务基础上进行传输,其中包括在实际应用中用于传输稳定有序数据的 TCP。
TCP建立连接
传输控制协议( Transmission Control Protocol, TCP ),是一种面向连接、确保数据在端到端间可靠传输的协议。面向连接是插在发送数据前,需要先建立一条虚拟的链路,然后让数据在这条链路上“流动”完成传输。为了确保数据的可靠传输,不仅需要对发出的每一个字节进行编号确认,校验每一个数据包的有效性,在出现超时情况时进行重传,还需要通过实现滑动窗口和拥塞控制等机制,避免网络状况恶化而最终影响数据传输的极端情形。每个 TCP 数据包是封装在 IP 包中的,每个 头的后面紧接着的是 TCP 头, TCP 报文格式如下图所示。  协议第一行的两个端口号各占两个字节,分别表示了源机器和目标机器的端口号。这两个端口号与 IP 报头中的源 地址和目标 IP 地址所组成的四元组可唯一标识一条TCP 连接。由于 TCP 是面向连接的 ,因此有服务端和客户端之分。需要服务端先在相应的端口上进行监昕,准备好接收客户端发起的建立连接请求。当客户端发起第个请求建立连接的 TCP 包时,目标机器端口就是服务端所监昕的端口号。比如一些由国际组织定义的广为人知端口号一一代表 HTTP 服务的 80 端口、代表 SSH 服务的22 端口、代表 HTTPS 服务的 443 端口。可通过 netstat 命令列出机器上已建立的连接信息,其中包含唯标识条连接的四元组,以及各连接的状态等内窑,如下图所示,图中的红框代表端口号。  协议第二行和第二行是序列号,各占4个字节。前者是指所发送数据包中数据部分第一个字节的序号,后者是指期望收到来自对方的下一个数据包中数据部分第一个字节的序号。 由于 TCP 报头中存在一些扩展字段,所以需要通过长度为 bit 的头部长度宇段表示 TCP 报头的大小,这样接收方才能准确地计算出包中数据部分的开始位置。 TCP的FLAG 位由 bit 组成,分别代表 ACK、SYN、FIN、 URG、PSH、RST ,都以置1表示有效。我们重点关注 SYN, ACK、FIN、SYN (Synchronize Sequence Numbers )用作建立连接时的同步信号 ACK (Acknowledgement )用于对收到的数据进行确认,所确认的数据由确认序列号表示; FIN ( Finish )表示后面没有数据需要发送,通常意昧着所建立的连接需要关闭了。 TCP中连接建立的原理。下图展示了正常情形下通过三次握手建立连接的过程。显然,A机器是服务端角色,B机器是客户端角色,前者需要在后者发起连接建立请求时先打开某个端口等待数据传输,否则将无法正常建立连接。三次握手指的是建立连接的三个步骤:
A 机器发出一个数据包并将 SYN置1,表示希望建立连接。这个包中的序列号假设是x。
B 机器收到 机器发过来的数据包后,通过 SYN 得知这是一个建立连接的请求,于是发送一个响应包并将 SYN ACK 标记都置1。假设这个包中的序列号是y,而确认序列号必须是 x+1 ,表示收到了A发过来的 SYN,在TCP 中, SYN 被当作数据部分的一个字节。
A 收到B的响应包后需进行确认,确认包中将 ACK置1,并将确认序列号设置为 y+1,表示收到了来自B的SYN。  为什么这里要进行3次握手?它有两个主要目的 信息对等和防止超时。先从信息对等角度来看,双方只有确定4类信息(自己发/收报能力,对方收/发报能力) ,才能建立连接。在第2握手后B机器视角看还有两个红色的NO信息无法确认。在第3次握手后,B机器才能确认自己的发报能力和对方的收报能力是正常的。 连接3次握手也是防止出现请求超时导致脏连接。 TTL 网络报文的生存时间往往都会超 TCP 请求超时时间,如果两次握手就可以创建连接,传输数据并释放连接后,第一个超时的连接请求才到达B机器的话B机器会以为是A创建新连接的请求,然后确认同意创建连接。因为A机器的状态不是 SYN-SENT,所以直接丢弃了B的确认数据,以致最后只是B机器单方面创建连接完毕,简要示意图如下图所示。  从编程的角度, TCP 连接的建立是通过文件描述符( File Descriptor,fd) 完成的。通过创建套接字获得一个 fd,然后服务端和客户端需要基于所获得的fd调用不同的函数分别进入监听状态和发起连接请求。由于fd的数量将决定服务端进程所能建立连接的数量 ,对于大规模分布式服务来说,当 不足时就会出现 “open too many files”` 错误而使得无法建立更多的连接。为此,需要注意调整服务端进程和操作系统所支持的最大文件句柄数。通过使用 ulimit -n 命令来查看单个进程可以打开文件句柄的数量。如果想查看当前系统各个进程产生了多少旬柄,可以使用如下的命令。
lsof -n I awk (print $2 ) ’ |sort| uniq - c |sort -nr| more
执行结果如下,左侧列是句柄数 右侧列是进程号。 lsof 命令用于查看当前系统所打开fd的数量。在 Linux 系统中,很多资源都是以fd的形式进行读写的,除了提到的文件和 TCP 连接 UDP 数据报、输入输出设备等都被抽象成了 fd。  想知道具体的 PID 对应的具体应用程序是谁,使用如下命令即可。
ps -axlgrep 32764
TCP 在协议层面支持 Keep Alive 功能,即隔段时间通过向对方发送数据表示连接处于健康状态。不少服务将确保连接健康的行为放到了应用层,通过定期发送心跳包检查连接的健康度。一旦心跳包出现异常不仅会主动关闭连接,还会回收与连接相关的其他用于提供服务的资源,确保系统资源最大限度地被有效利用。
TCP断开连接
TCP 是全双工通信,双方都能作为数据的发送方和接收方,但 TCP 连接也会有断开的时候。所谓相爱容易分手难,建立连接只有三次,而挥手断开则需要四次,如下图所示。A机器想要关闭连接,则待本方数据发送完毕后,传递 FIN 信号给机器B。 B机器应答 ACK ,告诉A机器可以断开,但是需要等B机器处理完数据,再主动给A机器发送 FIN 信号。这时,A机器处于半关闭状态( FIN_WAIT_2 ),无法再发送新的数据。B机器做好连接关闭前的准备工作后,发送 FIN给A机器,此时B机器也进入半关闭状态( CLOSE_ WAIT)。B机器发送针对B机器 FIN的ACK 后,进入 TIME-WAIT 状态,经过 2MSL ( Maximum Segment Lifetime )后,没有收到B机器传来的报文,则确定B机器已经收到A机器最后发送的 ACK 指令,此时 TCP连接正式释放。具体释放步骤如下图所示。  四次挥手断开连接用通俗的说法可以形象化地这样描述。 男生我们分手吧。 女生:好的,我的东西收拾完,发信息给你。( 此时男生不能再拥抱女生了。) ( 1个小时后)女生:我收拾好了,分手吧。 (此时女生也不能再拥抱男生了。) 男生:好的。 (此时 双方约定经过2个月的过渡期,双方才可以分别找新的对象。) 上图中的红色字体所示的 TIME WAIT CLOSE WAIT 分别表示主动关闭和 被动关闭产生的阶段性状态,如果在线上服务器大量出现这两种状态,就会加重机器负载,也会影响有效连接的创建,因此需要进行有针对性的调优处理。 ? TIME_WAIT:主动要求关闭的机器表示收到了对方的 FIN 报文,并发送出ACK 报文,进入 TIME_WAIT 状态,等 2MSL 后即可进入到 CLOSED态。如果 FIN_WAIT_1状态下,同时收到带 FIN 标志和 ACK 标志的报文肘,可以直接进入 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态。 ? CLOSE_WAIT: 被动要求关闭的机器收到对方请求关闭连接的 FIN 报文,在第一次 ACK 应答后,马上进入 CLOSE_WAIT 状态。这种状态其实表示在等待关闭,并且通知应用程序发送剩余数据,处理现场信息,关闭相关资源。 TIME_WAIT 等待的 2MSL 是报文在网络上生存的最长时间,超过阈值便将报文丢弃。一般来说, MSL 大于 TTL 衰减至0的时间。在RC793 中规定 MSL为2分钟。但是在当前的高速网络中,2分钟的等待时间会造成资源的极大浪费,在高并发服务器上通常会使用更小的值。既然 TIME_WAIT 貌似是百害而无一利的,为何不直接关闭,进入 CLOSED 状态呢?原因有如下几点。 第一,确认被动关闭方能够顺利进入 CLOSED 状态。假如最后一个 ACK 由于网络原因导致无法到达B机器,处于 LAST_ACK的B机器通常“自信”地以为对方没有收到自己的 FIN+ACK 报文,所以会重发。A机器收到第二次的 FIN+ACK 报文,会重发一次 ACK ,并且重新计时。如果B机器收到A机器的FIN+ACK 报文后,发送一个 ACK给B机器,就“自私”地立马进入 CLOSED 状态,可能会导致B机器无法确保收到最后的 ACK 指令,也无法进入 CLOSED 状态。这是A机器不负责任的表现。 第二,防止失效请求。这样做是为了防止己失效连接的请求数据包与正常连接的请求数据包混淆而发生异常。
|