导图社区 网络通信和TCPIP协议
网络通信和TCPIP协议的思维导图,如IP是TCP/IP中非常重要的协议,往往用来确定网络中唯一的一台计算设备,它的作用就好比我们现实生活中的电话号码或者或者通讯地址。
编辑于2023-07-18 00:39:52 北京市网络通信和TCP/IP协议
计算机网络体系结构
OSI参考模型
OSI采用了分层的结构化技术,共分七层,物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
TCP/IP模型
OSI模型比较复杂且学术化,所以我们实际使用的TCP/IP模型,分5层,物理层、数据链路层、网络层、传输层、应用层。 也有TCP/IP模型将物理层、数据链路层合称为网络接口层,与之对应的,协议就被称为TCP/IP四层协议模型)
应用层:运行中的SpringCloud项目、Mysql服务、浏览器等
传输层:TCP、UDP
网络层:IP、ICMP、RIP、IGMP
数据链路层:可以近似的理解为驱动程序
物理层:例如我们的网卡、网线
IP、TCP和UDP
IP:IP是TCP/IP中非常重要的协议,往往用来确定网络中唯一的一台计算设备,它的作用就好比我们现实生活中的电话号码或者或者通讯地址。所以这层负责对数据加上IP地址(有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址))和其他的数据以确定传输的目标。而TCP和UDP都是传输层的协议,传输层主要为两台主机上的应用程序提供端到端的通信。
TCP:类似于我们日常生活中的打电话,电话接通后通过“喂”确认对方身份,听不清会要求对方重说,对方说的太快了会要求对方说慢点,讲完了各说一句“再见”结束通话。TCP提供了一种可靠的数据传输服务,TCP是面向连接的,也就是说,利用TCP通信的两台主机首先要经历一个建立连接的过程,等到连接建立后才开始传输数据,而且传输过程中采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,发送完成后还会关闭连接。
UDP:类似于我们日常生活中通过不靠谱的物流系统寄东西。UDP是把数据直接发出去,而不管对方是不是在接收,也不管对方是否能接收的了,也不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。所以TCP要比UDP可靠的多。
TCP/IP网络传输中的数据
每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层必要的信息,如发送的目标地址以及协议相关信息。通常,为协议提供的信息为包首部,所要发送的内容为数据。在下一层的角度看,从上一层收到的包全部都被认为是本层的数据。 网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据。首部的结构由协议的具体规范详细定义。在数据包的首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解该协议必要的信息以及所要处理的数据。我们用用户A发送,用户B接受来说说明:
① 用户A应用程序处理 首先应用程序会进行编码处理产生报文/消息(message)交给下面的TCP层。
② 用户A TCP 模块的处理 TCP 根据应用的指示,负责建立连接、发送数据以及断开连接。TCP 提供将应用层发来的数据顺利发送至对端的可靠传输。为了实现这一功能,需要将应用层数据封装为报文段(segment)并附加一个 TCP 首部然后交给下面的IP层
③ 用户A IP 模块的处理 IP 将 TCP 传过来的 TCP 首部和 TCP 数据合起来当做自己的数据,并在 TCP 首部的前端加上自己的 IP 首部生成IP数据报(datagram)然后交给下面的数据链路层。
④ 用户A数据链路层的处理 从 IP 传过来的 IP 包对于数据链路层来说就是数据。给这些数据附加上链路层首部封装为链路层帧(frame),生成的链路层帧(frame)将通过物理层传输给接收端。
⑤ 用户B数据链路层的处理 用户B主机收到链路层帧(frame)后,首先从链路层帧(frame)首部找到 MAC 地址判断是否为发送给自己的包,若不是则丢弃数据。 如果是发送给自己的包,则从以太网包首部中的类型确定数据类型,再传给相应的模块,如 IP、ARP 等。这里的例子则是 IP 。
⑥ 用户B IP 模块的处理 IP 模块接收到 数据后也做类似的处理。从包首部中判断此 IP 地址是否与自己的 IP 地址匹配,如果匹配则根据首部的协议类型将数据发送给对应的模块,如 TCP、UDP。这里的例子则是 TCP。
⑦ 用户B TCP 模块的处理 在 TCP 模块中,首先会计算一下校验和,判断数据是否被破坏。然后检查是否在按照序号接收数据。最后检查端口号,确定具体的应用程序。数据被完整地接收以后,会传给由端口号识别的应用程序。
⑧ 用户B 应用程序的处理 接收端应用程序会直接接收发送端发送的数据。通过解析数据,展示相应的内容。
端口号
在传输层也有这种类似于地址的概念,那就是端口号。端口号用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。 一台计算机上同时可以运行多个程序。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确地将数据传输。
面试题:为什么端口号有65535个? 因为在TCP、UDP协议报文的开头,会分别有16位二进制来存储源端口号和目标端口号,所以端口个数是 2^16=65536个,但是0号端口用来表示所有端口,所以实际可用的端口号是65535个。
观察端口号命令 Windows下使用netstat -ano 查看所有端口号,netstat -ano|findstr “<端口号>”查看指定端口号。 Linux下可以用root 用户执行 lsof -i:端口号查看指定端口占用。 我们用的更多的是netstat -tunlp 用于显示 tcp,udp 的端口和进程等相关情况。 netstat 查看端口占用语法格式: netstat -tunlp | grep 端口号 -t (tcp) 仅显示tcp相关选项 -u (udp)仅显示udp相关选项 -n 拒绝显示别名,能显示数字的全部转化为数字 -l 仅列出在Listen(监听)的服务状态 -p 显示建立相关链接的程序名
综述
所以一般来说,不管计算机中有多少网卡,每个网卡都会有自己的MAC 地址,这个MAC 地址是不会变化的。而每个网卡在正常工作的情况下,都会有一个IP地址,这个IP地址完全是可以变化的。而这台计算机中承载的各种应用程序可以拥有自己的端口号,然后通过服务器的网卡,正确地进行网络通信。 一台服务器上的不同网络应用程序必须有不同的端口号,A程序启动了使用了端口x,B程序启动就不能使用端口x,否则会报错“Address already in use”。 总的来说,操作系统是通过源IP地址、目标IP地址、协议号(协议类型)、源端口号以及目标端口号这五个元素唯一性的识别一个网络上的通信。
面试题:一台主机上只能保持最多 65535 个 TCP 连接,对吗? 这个说法不对,我们分服务器和客户端分开讨论,以下的讨论都基于服务器和客户端都只有1个IP地址。
服务端 我们已经知道网络通信五元组是由过源IP地址、目标IP地址、协议号(协议类型)、源端口号以及目标端口号构成。现在考察的是 TCP 连接,自然五元组中的协议号已经定下来了,于是网络通信五元组就变化为TCP四元组。 那就是说TCP连接四元组是由源IP地址、源端口、目的IP地址和目的端口构成。 很明显当四元组中任意一个元素发生了改变,那么就代表的是一条完全不同的新连接。拿我们常用的MySQL 举例,假设它的 IP 是 X,端口3306。用户A基于IP地址A1,端口PA连接MySQL ,于是构成了一个TCP连接四元组(A1,PA,X,3306)。用户B基于IP地址B1,端口PB连接同一个MySQL,这个时候MySQL需要开启一个新端口来和用户B通信吗?从我们日常的开发就可以知道,MySQL并不需要这么做,所以用户B就和MySQL构成了一个新的TCP连接四元组(B1,PB,X,3306)。 服务端理论上能达成的最高并发数量是多少?从我们上面的用户A和用户B构成的TCP连接四元组: (A1,PA,X,3306) (B1,PB,X,3306) 可以看到目的IP地址和目的端口(X,3306)是不变的,这样就只剩下源IP地址、源端口是可变的。IP 地址是一个 32 位的整数,所以源 IP 最大有 2 的 32 次方这么多个。 端口是一个 16 位的整数,所以端口的数量就是 2 的 16 次方。2 的 32 次方(ip数)× 2的 16 次方(port数)大约等于两百多万亿。所以理论上,我们每个 server 可以接收的连接上限就是两百多万亿。 当然实际上做不到,目前工程实践中可以达到的连接数在千万级别。基于Java的应用程序大概能支持百万级别,具体怎么做会在后续中详细说明。
客户端 前面我们已经说过,“客户端应用程序完全可以不用自己设置端口号,而全权交给操作系统进行分配”,可用的端口号只有6万多,从这个角度考虑,客户端最多只能发起6万多条 TCP 连接。但其实也不是。 从TCP连接四元组来考虑:源IP地址、源端口、目的IP地址和目的端口,目的IP地址和目的端口指的是服务器的IP和端口,源IP地址、源端口自然就是客户端的。 只要服务器的 IP 或者端口不一样,即使客户端的 IP 和端口是一样的。这个四元组也是属于一条完全不同的新连接。比如: 连接1:客户端IP 10000 服务器IP 10000 连接2:客户端IP 10000 服务器IP 20000 虽然客户端的 IP 和端口完全一样,但由于服务器侧的端口不同,所以仍然是两条不同的连接。问题来了,客户端同一个端口可以连接不同的服务器吗?答案是可以的。 客户端只要启动时不显示绑定到某个端口上,内核是可以使用一个端口连不同的服务端,内核会自己进行选择并恰当地复用的,而且完全不会产生数据混乱,因为“源IP地址、目标IP地址、源端口号以及目标端口号就能唯一性确定一个TCP连接”。 那么对客户端来说,四元组里有3个可变,自然客户端能同时支持的连接数比服务器还要大得多。
子主题
*TCP连接
三次握手
TCP 提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。 所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,所以网络通信中,发起连接的一方我们称为客户端,接收连接的一方我们称之为服务端。
第一次握手:客户端将请求报文标志位SYN置为1,请求报文的Sequence Number字段(简称seq)中填入一个随机值J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。 第二次握手:服务器端收到数据包后由请求报文标志位SYN=1知道客户端请求建立连接,服务器端将应答报文标志位SYN和ACK都置为1,应答报文的Acknowledgment Number字段(简称ack)中填入ack=J+1,应答报文的seq中填入一个随机值K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。 第三次握手:客户端收到应答报文后,检查ack是否为J+1,ACK是否为1,如果正确则将第三个报文标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
为什么TCP握手需要三次?
为了实现可靠数据传输, TCP协议的通信双方,都必须维护一个序列号, 以标识发送出去的数据包中,哪些是已经被对方收到的。 举例说明:发送方在发送数据包(假设大小为 10 byte)时, 同时送上一个序号( 假设为 500),那么接收方收到这个数据包以后, 就可以回复一个确认号(510 = 500 + 10) 告诉发送方 “我已经收到了你的数据包, 你可以发送下一个数据包, 序号从 511 开始” 。 三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。 至于为什么不是四次,很明显,三次握手后,通信的双方都已经知道了对方序列号起始值,也确认了对方知道自己序列号起始值,第四次握手已经毫无必要了。
TCP的三次握手的漏洞-SYN洪泛攻击
在TCP三次握手中是有一个缺陷,被称为SYN洪泛攻击。三次握手中有一个第二次握手,服务端向客户端应答请求,应答请求是需要客户端IP的,而且因为握手过程没有完成,操作系统使用队列维持这个状态(Linux 2.2以后,这个队列大小参数可以通过/proc/sys/net/ipv4/tcp_max_syn_backlog设置)。于是攻击者就伪造这个IP,往服务器端狂发送第一次握手的内容,当然第一次握手中的客户端IP地址是伪造的,从而服务端忙于进行第二次握手,但是第二次握手是不会有应答的,所以导致服务器队列满,而拒绝连接。 面对这种攻击,有以下的解决方案,最好的方案是防火墙。
无效连接监视释放 这种方法不停监视所有的连接,包括三次握手的,还有握手一次的,反正是所有的,当达到一定(与)阈值时拆除这些连接,从而释放系统资源。这种方法对于所有的连接一视同仁,不管是正常的还是攻击的,所以这种方式不推荐
延缓TCB分配方法 一般的做完第一次握手之后,服务器就需要为该请求分配一个TCB(连接控制资源),通常这个资源需要200多个字节。延迟TCB的分配,当正常连接建立起来后再分配TCB则可以有效地减轻服务器资源的消耗。
使用防火墙 防火墙在确认了连接的有效性后,才向内部的服务器(Listener)发起SYN请求,
四次挥手
四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
第一次挥手:某个应用进程首先调用close,我们称该端执行主动关闭(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕,应用进程进入FIN-WAIT-1(终止等待1)状态。 第二次挥手:接收到这个FIN的对端执行被动关闭(passive close),发出确认报文。因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收,接收端进入了CLOSE-WAIT(关闭等待)状态,这时候处于半关闭状态,即主动关闭端已经没有数据要发送了,但是被动关闭端若发送数据,主动关闭端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。主动关闭端收到确认报文后进入FIN-WAIT-2(终止等待2)状态。 第三次挥手:一段时间后,被动关闭的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN,表示它也没数据需要发送了。 第四次挥手:接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN发出一个确认ACK报文,并进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命/最长分节生命期 max segement lifetime,MSL是任何IP数据报能够在因特网中存活的最长时间,任何TCP实现都必须为MSL选择一个值。RFC 1122[Braden 1989]的建议值是2分钟,不过源自Berkelcy的实现传统上改用30秒这个值。这意味着TIME_WAIT状态的持续时间在1分钟到4分钟之间)的时间后,当主动关闭端撤销相应的TCB后,才进入CLOSED状态。 关闭链接:被动关闭端只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,被动关闭端结束TCP连接的时间要比主动关闭端早一些。 (既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。我们使用限定词“通常”是因为:某些情形下步骤1的FIN随数据一起发送;另外,步骤2和步骤3发送的分节都出自执行被动关闭那-一端,有可能被合并成一个分节。)
为什么TCP的挥手需要四次?
TCP是全双工的连接,必须两端同时关闭连接,连接才算真正关闭。 如果一方已经准备关闭写,但是它还可以读另一方发送的数据。发送给FIN结束报文给对方,对方收到后,回复ACK报文。当这方也已经写完了准备关闭,发送FIN报文,对方回复ACK。两端都关闭,TCP连接正常关闭。
为什么需要TIME-WAIT状态?
TIME_WAIT状态存在的原因有两点 1、可靠的终止TCP连接。 2、保证让迟来的TCP报文有足够的时间被识别并丢弃。 根据前面的四次握手的描述,我们知道,客户端收到服务器的连接释放的FIN报文后,必须发出确认。如最后这个ACK确认报文丢失,那么服务器没有收到这个ACK确认报文,就要重发FIN连接释放报文,客户端要在某个状态等待这个FIN连接释放报文段然后回复确认报文段,这样才能可靠的终止TCP连接。 在Linux系统上,一个TCP端口不能被同时打开多次,当一个TCP连接处于TIME_WAIT状态时,我们无法使用该链接的端口来建立一个新连接。反过来思考,如果不存在TIME_WAIT状态,则应用程序能过立即建立一个和刚关闭的连接相似的连接(这里的相似,是指他们具有相同的IP地址和端口号)。这个新的、和原来相似的连接被称为原来连接的化身。新的化身可能受到属于原来连接携带应用程序数据的TCP报文段(迟到的报文段),这显然是不该发生的。这是TIME_WAIT状态存在的第二个原因。