简介
网络协议使不同计算机厂家生产的计算机在不同类型的操作系统上能够相互通信,以便在更大的范围内建立计算机网络。
OSI七层协议模型
国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型(Open System Interconnection/Reference Model)。它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。-
TCP/IP四层模型
TCP/IP分层模型(TCP/IP Layening Model)被称作因特网分层模型(Internet Layering Model)、因特网参考模型(Internet Reference Model),主要分为:应用层、运输层、网络层、网络接口层
-
五层体系结构
五层体系结构包括:应用层、运输层、网络层、数据链路层和物理层。
五层协议只是OSI和TCP/IP的综合,实际应用还是TCP/IP的四层结构。为了方便可以把下两层称为网络接口层。
TCP/IP简介
TCP/IP ( Transmission Control Protocol/Internet Protocol )传输控制协
议/因特网互联协议。TCP/IP 是当前流行的网络传输协议框架,
-
链路层:
单个 0、 l 是没有意义的,链路层以字节为单位把 0 与 l 进行分组,定义数据帧,写入源和目标机器的物理地址、数据、校验位来传输数据。
MAC地址长 6 个字节共 48 位,通常使用十六进制数表示。使用ifconfig-a 命令即可看到 MAC 地址。如图 1-5 所示的 f4:5c:89,即前 24 位由管理机构统一分配,后 24 位由厂商自己分配,保证网卡全球唯一。网卡就像家庭地址一样,是计算机世界范围内的唯一标识。
- 网络层: 根据 IP 定义网络地址,区分网段。子网内根据地址解析协议(ARP)进行 MAC 寻址,子网外进行路由转发数据包,这个数据包即IP数据包。
- 传输层:数据包通过网络层发送到目标计算机后,应用程序在传输层定义逻辑端口,确认身份后,将数据包交给应用程序,实现端口到端口间通信。最典型的传输层协议是UDP和TCP。UDP只是在 IP数据包上增加端口等部分信息,是面向无连接的,是不可靠传输,多用于视频通信、电话会议等(即
使少一帧数据也无妨)。与之相反,TCP 是面向连接的。所谓面向连接,是一种端到端间通过失败重传机制建立的可靠数据传输方式,给人感觉是有一条固定的通路承载着数据的可靠传输。 - 应用层:传输层的数据到达应用程序时,以某种统一规定的协议格式解读数据。比如 , E-mail 在各个公司的程序界面、操作、管理方式都不一样,但是都能够读取邮件内容,是因为 SMTP 协议就像传统的书信恪式一样,按规定填写邮编及收信人信息。
总结一下,程序在发送消息时,应用层接既定的协议打包数据,随后由传输层加上双方的端口号,由网络层加上双方的IP地址,由链路层加上双方的MAC地址,并将数据拆分成数据帧,经过多个路由器和网关后,至达目标机器。简而言之,就是按“ 端口→IP地址→MAC地址”这样的路径进行数据的封装和发送,解包的时候反过来操作即可。
IP协议
IP是面向无连接、无状态的,没有额外的机制保证发送的包是否有序到达。IP首先规定出IP地址格式,该地址相当于在逻辑意义上进行 了网段的划分,给每台计算机额外设置了一个唯一的详细地址。既然链路层可以通过唯一的MAC地址找到机器,为什么还需要通过唯一的IP地址再来标识呢?简单地说,在世界范围内,不可能通过广播的方式,从数以千万计的计算机里找到目标MAC地址的计算机而不超时。在数据投递时就需要对地址进行分层管理。IP地址如图1-14所示,即30.38.48.22,右边为物理层发送和接收数据的统计。
IP地址属于网络层,主要功能在WLAN内进行路由寻址,选择最佳路由。IP报文格式如图1-7所示,共32位4个字节,通常用十进制数来表示。四地址的掩码0xffffff00表示 255.255.255.0,掩码相同,则在同一子网内。IP协议在IP报头中记录源IP地址和目标IP地址,如图1-7所示。
协议结构比较简单,重点说一下数据包的生存时间,即TTL,它是数据包可经过的最多路由器总数。TTL初始值由源主机设置后,数据包在传输过程中每经过一个路由器TTL值则减1,当该字段为0时,数据包被丢弃,并发送ICMP报文通知源主机,以防止源主机无休止地发送报文。这里扩展说一下ICMP( Internet Control Message Protocol),它是检测传输网络是否通畅、主机是否可达、路由是否可用等网络运行状态的协议。ICMP虽然并不传输用户数据,但是对评估网络健康状态非常重要,经常使用的ping、tracert命令就是基于ICMP检测网络状态的有力工具。图1-7中TTL右侧是挂载协议标识,表示IP数据包里放置的子数据包协议类型,如6代表TCP、17代表UDP等。
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包中的,每个IP头的后面紧接着的是TCP头,TCP报文格式如图1-8所示。
协议第一行的两个端口号各占两个字节,分别表示了源机器和目标机器的端口号。这两个端口号与 IP 报头中的源 IP 地址和目标 IP 地址所组成的四元组可唯一标识一条 TCP 连接。由于 TCP 是面向连接的,因此有服务端和客户端之分。需要服务端先在相应的端口上进行监听,准备好接收客户端发起的建立连接请求。当客户端发起第一个请求建立连接的 TCP 包时,目标机器端口就是服务端所监昕的端口号。比如一些由国际组织定义的广为人知端口号一一代表 HTTP 服务的 80 端口、代表 SSH 服务的22 端口、代表 HTTPS 服务的 443 端口。可通过 netstat 命令列出机器上已建立的连接信息,其中包含唯标识条连接的四元组,以及各连接的状态等内容,如图 1 - 9 所示,图中的红框代表端口号。
协议第二行和第二行是序列号,各占 4 个字节。前者是指所发送数据包中数据部分第一个字节的序号,后者是指期望收到来自对方的下一个数据包中数据部分第一个字节的序号。
由于 TCP 报头中存在一些扩展字段,所以需要通过长度为 4 个 bit 的头部长度字段表示 TCP 报头的大小,这样接收方才能准确地计算出包中数据部分的开始位置。
TCP的FLAG位由6个bit组成,分别代表 ACK 、 SYN 、 F町、 URG 、 PSH 、RST,都以置1表示有效。我们重点关注 SYN, ACK 和 FIN 。SYN(Synchronize Sequence Numbers)用作建立连接时的同步信号;ACK ( Acknowledgement)用于对收到的数据进行确认,所确认的数据由确认序列号表示; FIN ( Finish)表示后面有数据需要发送,通常意味着所建立的连接需要关闭了。TCP 报头申的其他字段可以阅读RC793 来掌握。
接下来重点分析 TCP 中连接建立的原理。图1-10展示了正常情况下通过三次握手建立连接的过程。三次握手指的是建立连接的三个步骤:
- A机器发出一个数据包并将SYN置1,表示希望建立连接。这个包中的序列号假设是 x。
- B机器收到A机器发过来的数据包后,通过SYN得知这是一个建立连接的请求,于是发送一个响应包并将SYN和ACK标记都置1。假设这个包中的序列号是y,而确认序列号必须是x+1,表示收到了A发过来的SYN。在TCP中,SYN被当作数据部分的一个字节。
-
A收到B的响应包后需进行确认,确认包中将ACK置1,并将确认序列号设置为y+1,表示收到了来自B的SYN。
这里为什么需要第3次握手?它有两个主要目的:信息对等和防止超时。先从信息对等角度来看,如表1-11所示,双方只有确定4类信息,才能建立连接。在第2次握手后,从B机器视角看还有两个红色的NO信息无法确认。在第3次握手后, B机器才能确认自己的发报能力和对方的收报能力是正常的。
连接三次握手也是防止出现请求超时导致脏连接。TTL网络报文的生存时间往往都会超过TCP请求超时时间,如果两次握手就可以创建连接,传输数据并释放连接后,第一个超时的连接请求才到达B机器的话,B机器会以为是A创建新连接的请求,然后确认同意创建连接。因为A机器的状态不是 SYN_SENT,所以直接丢弃了B的确认数据,以致最后只是B机器单方面创建连接完毕,简要示意图如图1-12所示。
如果是三次握手,则B机器收到连接请求后,同样会向A机器确认同意创建连接,但因为A机器不是SYN_SENT状态,所以会直接丢弃,B机器由于长时间没有收到确认信息,最终超时导致连接创建失败,因而不会出现脏连接。根据抓包分析,呈现出三次握手请求过程,SYN+ACK的应答,告诉A机器期望下一个数据包的第一个字节序号为1,如图1-13所示。
从编程的角度,TCP连接的建立是通过文件描述符(File Descriptor,fd)完成的。通过创建套接字获得一个fd,然后服务端和客户端需要基于所获得的fd调用不同的函数分别进入监听状态和发起连接请求。由于fd的数量将决定服务端进程所能建立连接的数量,对于大规模分布式服务来说,当fd不足时就会出现“open too many files”错误而使得无法建立更多的连接。为此,需要注意调整服务端进程和操作系统所支持的最大文件句柄数。通过使用ulimit -n命令来查看单个进程可以打开文件句柄的数量。如果想查看当前系统各个进程产生了多少句柄,可以使用如下的命令:
lsof -n awk '{print $2}'|sort|uniq -c |sort -nr|more
执行结果如图1-14所示,左侧列是句柄数,右侧列是进程号。lsof命令用于查看当前系统所打开fd的数量。在Linux系统中,很多资源都是以fd的形式进行读写的,除了提到的文件和TCP连接,UDP数据报、输入输出设备等都被抽象成了fd。
想知道具体的PID对应的具体应用程序是谁,使用如下命令即可:
ps -ax|grep 32764
TCP在协议层面支持Keep Alive功能,即隔段时间通过向对方发送数据表示连接处于健康状态。不少服务将确保连接健康的行为放到了应用层,通过定期发送心跳包检查连接的健康度。一旦心跳包出现异常不仅会主动关闭连接,还会回收与连接相关的其他用于提供服务的资源,确保系统资源最大限度地被有效利用。
TCP断开连接
TCP是全双工通信,双方都能作为数据的发送方和接收方,但TCP连接也会有断开的时候。所谓相爱容易分手难,建立连接只有三次,而挥手断开则需要四次,如图1-15所示。A机器想要关闭连接,则待本方数据发送完毕后,传递FIN信号给B机器。B机器应答ACK,告诉A机器可以断开,但是需要等B机器处理完数据,再主动给A机器发送FIN信号。这时,A机器处于半关闭状态(FIN_WAIT_2),无法再发送新的数据。B机器做好连接关闭前的准备工作后,发送FIN给A机器,此时B机器也进入半关闭状态(CLOSE_WAIT)。A机器发送针对B机器FIN的ACK后,进入TIME-WAIT状态,经过2MSL(Maximum Segment Lifetime)后,没有收到B机器传来的报文,则确定B机器已经收到A机器最后发送的ACK指令,此时TCP连接正式释放。具体释放步骤如图1-15所示。
通过抓包分析,如图1-16所示红色箭头表示B机器已经清理好现场,并发送FIN+ACK。注意,B机器主动发送的两次ACK应答的都是81,第一次进入CLOSE_WAIT状态,第二次应答进入LAST_ACK状态,表示可以断开连接,在绿色箭头处,A机器应答的就是Seq=81。
四次挥手断开连接用通俗的说法可以形象化地这样描述。
男生:我们分手吧。
女生:好的,我的东西收拾完,发信息给你。(此时男生不能再拥抱女生了。)(1个小时后)
女生:我收拾好了,分手吧。(此时,女生也不能再拥抱男生了。)
男生:好的。(此时,双方约定经过2个月的过渡期,双方才可以分别找新的对象。)
图1-15中的红色字体所示的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的时间。在RFC793中规定MSL为2分钟。但是在当前的高速网络中,2分钟的等待时间会造成资源的极大浪费,在高并发服务器上通常会使用更小的值。既然TIME_WAIT貌似是百害而无 一利的,为何不直接关闭,进入 CLOSED状态呢?原因有如下几点。
第一,确认被动关闭方能够顺利进入CLOSED状态。如图1-15所示,假如最后一个ACK由于网络原因导致无法到达B机器,处于LAST_ACK的B机器通常“自信”地以为对方没有收到自己的FIN+ACK报文,所以会重发。A机器收到第二次的FIN+ACK报文,会重发一次ACK,并且重新计时。如果A机器收到B机器的FIN+ACK报文后,发送一个ACK给B机器,就“自私”地立马进入CLOSED状态,可能会导致B机器无法确保收到最后的ACK指令,也无法进入CLOSED状态。这是A机器不负责任的表现。
第二,防止失效请求。这样做是为了防止己失效连接的请求数据包与正常连接的请求数据包混淆而发生异常。
因为TIME_WAIT状态无法真正释放句柄资源,在此期间,Socket中使用的本地端口在默认情况下不能再被使用。该限制对于客户端机器来说是无所谓的,但对于高并发服务器来说,会极大地限制有效连接的创建数量,成为性能瓶颈。所以,建议将高并发服务器TIME_WAIT超时时间调小。
在服务器上通过变更/etc/sysctl.conf文件来修改该省略值(秒):net.ipv4.tcp_fin_timeout=30(建议小于30秒为宜)。
netstat -n I awk '/^tcp/ {++S[$NF]} END (for(a in S) print a, S[a]}'
查看各连接状态的计数情况,为了使数据快速生效,2MSL从240秒更改为5秒。参数生效后如图1-17所示,TIME_WAIT很快从75个降为1个。
在sysctl.conf中还有其他连接参数也用来不断地调优服务器TCP连接能力,以提升服务器的有效利用率。毕竟现代网络和路由处理能力越来越强,跨国时延通常也在1秒钟以内,丢包率极低。如何快速地使连接资源被释放和复用,参数的优化往往可以取得事半功倍的效果。记得某大公司在大型购物节时,系统宕机,老总下令要加一倍服务器来解决问题。事实上,如果是参数配置错误导致的系统宕机,即使增加硬件资源,也无法达到好的效果。硬件的增加与性能的提升绝对不是线性相关的,更多的时候是对数曲线关系。
TIME_WAIT是挥手四次断开连接的尾声,如果此状态连接过多,则可以通过优化服务器参数得到解决。如果不是对方连接的异常,一般不会出现连接无法关闭的情况。但是CLOSE_WAIT过多很可能是程序自身的问题,比如在对方关闭连接后,程序没有检测到,或者忘记自己关闭连接。在某次故障中,外部请求出现超时的情况,当时的 Apache服务器使用的是默认的配置方式,通过命令
netstat -ant|grep -i "443"lgrep CLOSE_WAIT|wc -l
发现在HTTPS的443端口上堆积了2.1万个左右的CLOSE_WAIT状态。经排查发现,原来是某程序处理完业务逻辑之后没有释放流操作,但程序一直运行正常,直到运营活动时才大量触发该业务逻辑,最终导致故障的产生。
文章中的知识来自《码出高效Java 开发手册》