计算机网络——TCP/IP

 

都是下一层给上一层提供服务的。

  • 应用层,负责给应用程序提供统一的接口;
  • 表示层,负责把数据转换成兼容另一个系统能识别的格式;
  • 会话层,负责建立、管理和终止表示层实体之间的通信会话;
  • 传输层,负责端到端的数据传输;
  • 网络层,负责数据的路由、转发、分片;
  • 数据链路层,负责数据的封帧和差错检测,以及 MAC 寻址;
  • 物理层,负责在物理网络中传输数据帧;

(1)应用层专注于为用户提供应用功能,不用关心数据如何传输。HTTP/FTP/SMTP/POP3/DNS.

(2)表示层将应用层的请求表述成复合网络协议和应用要求的会话层可以理解的请求数据。

(3)会话层是请求过程的会话管理。

(2)传输层负责应用到应用的通信。在传输层上,如果数据包的大小超过MSS(TCP最大报文段长度),就要将数据包分块。这样即使中途有一个分块丢失或损坏,只需要重新这一个分块,而不用重新发送整个数据包。

(3)网络层负责将数据从一个设备传输到另一个设备(IP协议进行寻址,告诉我们去往下一个目的地该朝着哪个方向走,路由是根据下一个目的地选择路径)。在网络层上,如果IP报文大小超过MTU(以太网中一般为1500字节)就会再次进行分片,得到一个即将发送到网络的IP报文。

补充:TCP协议和UDP协议的分片方式不同。TCP的数据大小如果大于MSS大小,会在传输层进行分片,目标主机收到后,也同样在传输层组装数据包。如果中途丢失一个分片,只需要传输丢失的这个分片。UDP的数据大小如果大于MTU大小,则会在IP层进行分片,目标主机收到之后,在IP层组装完数据,接着传给传输层。但是如果中途丢失一个分片,实现可靠传输的UDP时就需要重传所有的数据包,这样传输效率非常差。所以通常UDP的报文应该小于MTU。

(4)数据链路层服务网络层,实现跨网络传输(有一个设备同时在两个网络中就是路由器)。每个网卡都有MAC地址,路由器计算下一个目的地IP后,再通过ARP协议找到目的地MAC,就可以知道要传到哪个设备。

(5)物理层实现设备和网络之间的数据传送。比如从设备发送到网络,要把数据包转换成电信号,让其可以在物理介质中传输。

 

TCP协议

标志位(控制位)有ACK/RST/SYN/FIN。序列号。确认应答号。

三次握手:

标志位(控制位)SYN和ASK,序列号seq(随机生成),应答号ask(下一次期望收到数据的序列号)
一开始客户端是close,然后发送标志位SYN,发送序列号之后变成SYN_SEND,然后接收服务器发来的应答就变成ESTABLISH。

第三次握手可以携带数据,前两次不可以。

序列号随机:防止历史报文被下一个相同四元组的连接接收,防止黑客伪造的相同序列号TCP报文被对方接收。基于是时钟计数器递增产生。

客户端的connect成功返回是在第二次握手,服务accept成功返回是在三次握手成功之后。listen的backlog再linux内核2.2之前是半连接队列,后来是全连接队列


三次握手是为了保证通信双方都具有接收和发送的能力。更重要的是防止历史连接的建立、减少双方不必要的资源开销,帮助双方同步初始化序列号。详细原因如下:

(1)主要为了防止已经失效的连接请求报文段突然又传输到了服务端,导致产生问题。确认应答号是指下一次期望收到的数据的序列号。客户端发送多次SYN去建立连接,旧报文90比新报文100先到达服务端。服务端返回ACK,客户端通过上下文比较,发现自己期望收到的ACK应该是100+1,于是发起RST报文终止连接。

客户端判断后,如果是旧历史连接就要终止连接。如果只有两次握手就不能终止连接。

(2)同步双方的初始化序列号。

(3)避免资源的浪费。服务端收到客户端都会发送应答,客户端多次阻塞就会建立很多很多连接。

 

 

 

 

SYN攻击:攻击者短时间伪造不同IP的SYN报文,服务端接收进入SYN_RCVD,但服务端发出的的ACK+SYN无法得到客户端的ACK,占满服务端半连接队列。

客户端优化:客户端收不到ACK,会一直超时重传SYN。tcp_syn_retries参数。tcp_syn_retries参数。
服务端优化:半连接队列大小,不能只是单纯增大tcp_max_syn_backlog,还需要增大somaxconn和backlog,即增大accept队列。listen增大backlog。
半连接SYN队列如果满了,还可以开启syncookies,服务器根据当前状态计算一个值,放在SYN+ACK报文中一起发出,当客户端返回ACK报文的时候,取出该值验证,如果合法就认为连接建立成功。0表示关闭,1表示仅当半连接队列放不下时候打开,2表示无条件开启,tcp_synack_retries
全连接ACCEPT队列如果满了,正常是服务端丢掉客户端发过来的ack,也可以把tcp_abort_on_overflow设置成1,服务端发送一个RST给客户端,表示废掉这个握手过程和连接,这样就可以知道客户端连接不上服务器是不是因为全连接队列溢出。
accept队列长度取决于somaxconn和backlog最小值。listen(sockfd, backlog)
netstat -s查看半连接队列溢出情况,ss -lnt查看全连接队列

 

四次挥手:

一开始客户端和服务器都是ESTABLISH。客户端先向服务器发出连接释放报文段FIN=1/seq=x,并且停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1的状态。服务器收到连接释放报文段以后就发送应答标志位和应答信号,服务端进入CLOSE_WAIT的等待关闭状态,此时TCP处于半关闭的状态,客户端不能再发数据给服务器(但服务端可能还有数据要发送)

客户端收到服务器的应答之后,进入FIN-WAIT2状态,等待服务器发出的连接释放报文段。
服务器发送完应答数据之后,就会再次发送连接释放报文段(此时应答标志位和应答信号和前面一样?)服务器进入LAST_ACK最后确认的状态,等待A的确认。客户端收到服务器的连接释放信号以后,进入TIME_WAIT的状态,此时TCP没有完全释放,服务器依旧可以向客户端发送,等到2msl,客户端才进入CLOSE状态。服务器收到客户端发送的确认报文段后关闭连接,如果没有收到,就会重传连接释放信号。

在第二次挥手后,TCP 处于半关闭状态,客户端到服务端的连接释放,但是第四次挥手仍需客户端给服务端发送报文,所以半关闭状态和发送报文是不冲突的。因为 FIN 状态位表示发送后,当前客户端仍然会接收服务器的数据,但是不会接收他本地服务的数据(应用层的数据),所以客户端在发送 FIN 后仍然能发送 ack 是不矛盾的。因为 ack 不是本地服务的数据,是放在 tcp 头部的,而不是body中。


2msl(Maximum Segment Lifetime,报文最大生存时间)是为了保证客户端最后发送的确认报文段能够到达服务器,如果丢失了服务器还会重传连接释放信号。此外,2msl还可以防止已经失效的连接释放报文段再次出现在本连接中。客户端在发送最后一个应答信号之后,再经过2msl,就可以使得这个连接所产生的所有报文段都从网络中消失。

2msl的时间是从客户端接收到FIN之后发送ACK开始计时的。

服务端进程关闭会发送一个FIN包。

 

服务端有大量的close_wait状态怎么解决?
出现原因:
  从recv返回0到close中有大量耗时的操作。
解决方法:
1、close放在耗时操作前面
2、将耗时的业务放到任务队列中或者开一个线程池去处理。

每个fd都有对应的tcb控制块,占用内存。

对于time_wait如果是主动关闭方客户端有的状态,那就会占满所有的端口资源,无法对相同IP和相同PORT的服务器发起连接,但是可以对另一个服务器发起连接。打开net.ipv4.tcp_tw_reuse这个内核参数后,客户端调用connect函数时,如果选择的端口已经被相同四元组的连接占用,就会判断该连接是否处于TIME_WAIT,如果处于超过1s就会重用这个连接,然后就可以正常使用该端口。如果没有开启就是选择下一个端口。

如果是服务器大量这个状态,不会导致端口资源限制。那么就是可能业务逻辑需要,解决方法是setsocketopt端口可重用。

防止服务器重启时,之前绑定的端口还没有释放,或者程序突然退出而系统没有释放端口。如果设置端口复用,则新启动的服务端进程可以直接绑定端口。如果没有设定端口复用,绑定失败,提示ADDR已经在使用中。


为什么要四次挥手?因为一开始是客户端要释放连接的,但是服务器不知道,可能这时候服务器还有要发送给客户端的东西。所以服务器在收到客户端的释放要求之后,先应答一下,等到服务器把想发送的都发送完了之后,再跟客服端发送关闭信号,使得客户端关闭(TIME_WAIT)。然后客户端应答,然后服务器彻底关闭。

 

TCP的特点有哪些?TCP是面向连接的传输层协议,而且他是点对点的,能够提供可靠交付的服务,提供全双工的通信,面向的是字节流。

TCP为什么可靠?
客户端和服务器在进行TCP连接的流程比较复杂,有三次握手和四次挥手。该协议的可靠性主要和三个因素有关系。

第一个是序列号SYN和确认号ACK。客户端发送的报文里面包含序列号,每当发送一次,就需要在设置好的RTT时间内收到服务器的应答。如果没有接收到相应的ACK,客户端就会重传该报文。这就是超时重传机制。 还有一个是快速重传机制。客户端连续发送很多报文,当服务器发现接受到的序列号不对的时候,就会发送连续的三个ACK,告诉客户端这个报文在传输过程出现了丢包问题。客户端如果接收到了某个相同序列号的三个ACK报文,就会立马重发这个报文,不用等待计时器的时间结束。

第二个是基于滑动窗口的流量控制机制。客户端通过维持一个发送滑动窗口来确保不会由于发送报文太快接收方无法及时处理。

第三个是拥塞控制,前面的流量控制只考虑发送方和接收方,拥塞控制则考虑的是整个网络。

 

TCP和UDP的不同。TCP是面向连接的,而UDP在发送数据之前,不需要建立连接。TCP是可靠传输的,而UDP不提供可靠传输。TCP面向字节流,而UDP面向报文。TCP有拥塞控制,而UDP没有,因此网络拥塞不会引起发送端速率降低,很适合在传输视频会议。TCP只能点到点,而UDP都可以。TCP首部开销20字节,而UDP只有8字节。对于TCP来说,重传策略是底层网络协议栈,用户是不可控的。而对于UDP来说,重传策略是可以用户自己定制的。分片方式不同,TCP和MSS,UDP和MTU。

TCP:FTP文件传输、HTTP/HTTPS。 UDP:DNS、SNMP、视频、广播通信。

 

关于UDP。针对音视频通话的实时性等问题提出,网络不通畅,太久的数据还重传就会导致通话延迟。WebRTC音视频通话就是使用UDP通信协议。通话的时候不重传顶多就问一下你刚才说什么。对于TCP来说,重传策略是底层网络协议栈,用户是不可控的。而对于UDP来说,重传策略是可以用户自己定制的。

应用程序可以基于UDP,借鉴并实现TCP的各种特性。TCP能做到的,在应用程序层也能实现。这相当于绕开TCP,直接基于IP层编程。

QUIC(Quick UDP Internet Connection)是谷歌提出的基于UDP的多路并发传输协议。HTTP3就是http over quic。

QUIC = 部分TCP + SSL/TLS + http2。取代了一部分TCP功能,取代了SSL/TLS功能,取代了HTTP2.0功能。

QUIC的优势:

彻底阻塞队头阻塞问题。UDP无队头阻塞问题。因为无连接,所有的包并发在网络上传输,而不是像TCP一样在一个连接上串行传输。

把整个连接过程优化到0个RTT时间。TCP连接的三次握手是1.5RTT。TLS1.2和1.3是从2个RTT到1个RTT。QUIC把这两类RTT都省了。对于新网站的第一次连接需要1个RTT,类似TLS1.3原理。对于不是第一次连接的旧网站,客户端缓存了STK,0个RTT。

连接迁移能力比较强。TCP是通过四元组来唯一标识一个连接,当客户端在4G和wifi之间来回切换的时候,IP发生变化,需要重连接。QUIC不用四元组标识连接,用了一个新定义的ConnectionID64位标识。发生网络切换的时候这个ID保持不变就可以继续发送数据,

能够做到不丢包。TCP的不丢包,是通过顺序ACK +重传机制实现。UDP则是使用磁盘存储领域的Raid5和Raid6算法。对于Raid5算法,每次发送5个数据包,就会发送1个冗余包。冗余包是对5个数据包做异或运算得到的。这样依赖,服务器收到6个包,如果5个中由一个丢失了,就可以通过其他几个包计算出来。但这种丢包的恢复方法有一个限制条件,每5个当中只能丢失一个。

QUIC是用户态协议,不是内核态协议,修改更容易

 

TCP的粘包和分包。

TCP是一个面向字节流的协议,像流一样源源不断传输应用底层的数据,并不管应用层的数据包之间的分界。TCP粘包发生在发送缓冲区或者接收缓冲区,应用程序从缓冲区中取数据是整个缓冲区中有多少数据就取多少。那么就有可能第一个数据的尾部和第二个数据的头部同时存放在缓冲区里面,而TCP是流式的,数据无边界发生粘包。

所以可能分包,发送一次“abcdef”,分N次才接受完。也可能粘包,发送N次,1次就接受完。

粘包方法:数据包加头,每个数据包前面,加上一个4字节的长度字段。在消息的头部添加数据包的长度,接收方根据消息长度进行接收。

方法2:在每个数据包的前后,都加上一个固定长度的特殊标记,比如#。

方法3:禁用naggle算法。

方法4:自定义数据格式,开始、结束标识符。缺点是数据内不能含有开始或结束表示。

 

 

MTU和MSS分包(Maximum Segment Size)。

 

 以太网上面有一个MTU,是以太网报文的最大值,通常是1500字节。

如果TCP的报文给IP层进行分片,由于IP层本身没有超时重传机制,只有传输层TCP负责超时和重传,如果一个IP分片丢失,整个IP报文的所有分片都要重传。所以TCP协议再建立连接的时候要协商双方的MSS,当TCP层发现数据超过MSS就会分片。经过TCP分片后,如果丢失,进行重发也是以MSS为单位,不用重传所有分片,大大增加了重传效率。

默认MSS = MTU - TCP头 - IP头 = 1460字节。

 

重传机制。主要有超时重传(以时间为驱动)、快速重传(以数据为驱动)。确保可靠性。

快速重传只解决了时间的问题,但是重传的时候是重传之前的一个,还是重传所有的问题依旧存在。

因此出现了SACK方法、Duplicate SACK方法。

 

 应用程序调用read/write的时候是和发送/接收缓冲区打交道,而不是直接调用TCP/IP协议栈。

 

滑动窗口。

窗口大小就是指无需等待确认应答,就可以继续发送数据的最大值。

窗口的实现实际就是操作系统开辟的一个缓存空间,发送方主机等到确认应答返回之前,必须在缓冲区保存已经发送的数据。

如果准时收到确认应答,数据就可以从缓存区清除。

发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。所以,通常窗口的大小都是由接收方的窗口大小决定的。发送方滑动窗口使用三个指针来跟踪在四个传输类别中的每一个类别中的字节,其中两个指针是绝对指针(指特定的序列号),一个指针是相对指针(需要做偏移)。可用窗口的大小=SND.WND - (SND.NXT - SND.UNA)

接收方的滑动窗口使用RCV.WND和RCV.NXT两个指针。

因为发送方的大小是由接受方返回的ACK信号决定的,所以接收窗口和发送窗口只能是约等于的关系。

 

IP网络的设计初衷就是要提高网络的吞吐量,提高网络传输效率。不希望有小包在网络传输,浪费网络带宽。

小包是小于MSS的,极小包是header大于data size。

为了尽力避免小包,采取批量策略。第一,发送方延迟发送,累计一批再一次性发送。第二,接收方延迟ACK,累计一批再一次性ACK。

发送方延迟发送:Naggle算法。

默认情况下,Naggle算法是开启的。要禁用需要设置TCP_NODELAY。

任意一个时刻,只允许有一个没有被ACK的小包存在。这个算法可能引起网络延迟的问题,因为是牺牲延迟来换取高吞吐,所以对延迟非常敏感的应用,一般都会禁用Naggle算法。

 

下面两个问题都是发送方重传触发的问题。

流量控制。点对点问题,只和发送方/接收方有关系。解决接收方处理不过来的问题。

如果发送方一直无脑发数据给接收方,但是对方处理不过来,就会触发重发机制,浪费网络流量。

如果接收方窗口为0告诉发送方,就会阻止发送方给接收方发送数据,直到窗口大小变成非0,这就是窗口关闭。但是这种窗口关闭操作很容易造成问题。因为接收方给发送方通知窗口大小的时候,是通过ACK报文来通知的。如果这个报文丢失了,会导致发送方一直等待接收方的非0窗口通知,接收方也一直等待发送方的数据。如果不采取措施,这种相互等待的过程会造成死锁现象。(TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 )

 

拥塞控制。全局问题,和整个网络有关。解决中间节点(路由器、交换机等)处理不过来的问题。

发送方怎么知道网络拥塞了?

发送方只能猜测网络拥塞,不能确信。发送一个包出去,ACK收不到,就可能是因为接收方有问题,或者网络拥塞了。

因此发送方引入了一个拥塞窗口,间接反映了网络的拥堵程度。发送窗口 = min(拥塞窗口,接受窗口)。

如何动态调整拥塞窗口?也就间接调节了发送窗口。

答:发送方根据ACK的快慢程度,动态调节拥塞窗口的大小。

 

 (慢开始)一开始,拥塞窗口很小。每次准时收到一次ACK,窗口就会增大一次。先指数级增长,然后再线性增长。有一个叫做慢启动门限ssthresh状态变量。当cwnd<ssthresh的时候,使用慢启动算法。当cwnd>=ssthresh的时候,就会使用拥塞避免算法。

(拥塞避免)进入该算法后,每当收到一个ACK,cwnd增加1/cwnd。即当8个ACK应答确认到来的时候,8个ACK确认一共增加1。拥塞避免算法就是把原本慢启动算法的指数增长变成了线性增长,但是增长速度变慢了。

(拥塞发生)当网络出现拥塞,会发生数据包重传。重传机制主要有两种。

超时重传:ssthresh设置为cwnd/2,cwnd重置为1。接着重新开始慢启动,方法激进,会造成网络卡顿。

快速重传:当接收方发送三次同一个包的ACK,发送端就会快速重传。TCP认为这种情况不严重,因为大部分没丢,只丢了小部分。cwnd设置为原来的一般,ssthresh设置为cwnd,然后进入快速恢复。
(快速恢复)cwnd=ssthresh+3,然后重传丢失的数据包,那么cwnd增加1。如果收到新数据的ACK之后,把cwnd设置为第一步中的ssthresh数值,原因是该ACK确认了新的数据,说明从duplicateACK的时候的数据都已经收到,该恢复过程已经结束,重新回到拥塞避免。

 

 

当发生ACK超时的时候,就会认为网络发生拥塞了。此时,把窗口降下来,再次开始之前的增长过程。

那么ACK超时时间设置多少合适呢?

设置太短,很容易超时,会频繁重传。设置太长,网络时延很长,包已经丢了发送方迟迟不发起重传,接收端也收不到。

需要自适应调整。可以根据RTT来算,使得超时时间稍微大于RTT。但是RTT不是一个值,而是一个时间序列。采用时间序列预测EWNA算法。

RTO(超时时间) = RTTS(预测值) + 4 * RTTD(预测和实际的偏差)

 

(快重传)前面是超时之后就会重传,快重传是指没有等到超时就重传。需要接收方一旦收到失序的报文,就要马上ACK不能延迟。

(快恢复)发生快重传的拥塞后,从中间开始直接线性增长。

当前主流的拥塞控制算法

Tahoe算法,太浩(只有慢启动和拥塞避免状态)。

Reno算法(慢启动、冲突避免、快恢复)。

Vegas算法,基于延迟的拥塞控制算法。

BBR算法。https://cloud.tencent.com/developer/article/1482633

 

丢包问题

如果通信中发现缺少数据或者丢包,那么,最大的可能在于程序发送的过程或者接收的过程出现了问题。例如服务器给客户端发送大量数据,send的频率很高,那么就有可能在send的时候发生错误。原因可能有很多种,可能是程序处理逻辑问题、多线程同步问题、缓冲区溢出问题,如果没有对send失败做处理重新发送数据,那么客户端收到的数据会比理论应该收到的少,就会造成丢数据、丢包的现象。这种现象,其实本质上来说不是丢包,也不是丢数据,只是因为程序处理有错误,导致有些数据没有成功被socket发送出去。

常用解决方法:拆包、加包头、发送、组合包,如果客户端、服务端掉线,常采用心跳测试。

IP协议

网络层负责在没有直连的两个网络之间进行通信传输,数据链路层负责实现直连的两个设备之间的通信。网络层相当于行程表,数据链路层相当于车票。

IP地址分为网络号和主机号。网络号是用于进行路由控制的,路由控制表记录网络地址下一步应该发送至路由器的地址。在主机和路由器上都会有各自的路由器控制表。

IP地址和子网掩码进行与操作就可以得到网络号。

发送IP包的时候,首先要确定IP包的首部目标地址,从路由控制表里面找到和该地址具有相同网络号的记录,根据该记录将IP包发送给相应的下一个路由器。如果路由控制表中存在多条相同网络号的记录,就选择相同位数最多的网络地址,也就是最长匹配。

IPv4地址是由32位正整数来表示的,在计算机中以二进制方式处理,最大值也就是2^32=43亿台电脑连接到网络。32位分别为网络号和主机号。在IP地址中,主机号全为0和全为1的地址比较特殊。

全为1表示某个网络下的所有主机,用于广播。全为0指定某个网络。

当IP数据包大于MTU的时候,IP数据包就会被分片。经过分片后的IP数据报在重组的时候,只能由目标主机执行,路由器是不会进行重组的。在分片传输的过程中,一旦某个分片丢失,则会造成整个IP数据报作废,所以TCP引入了MSS,也就是在TCP层进行分片不由IP层分片,那么对于UDP我们尽量不要发送一个大于MTU的数据报文。

如果IP层有一个超过MTU大小的数据要发送,那么IP层就要进行分片,把数据分成若干片,保证每一个分片都小于MTU。把一份IP数据报进行分片以后,由目标主机的IP层进行重新组装后,再交给上一层的TCP传输层。但是这其中存在一个隐患,那就是如果一个IP分片丢失,整个IP报文的所有分片都得重传。因为IP层本身没有重传机制,所以是由传输层得TCP负责超时和重传的。当接收方发现TCP报文的某一片丢失之后,不会响应ACK给对方,那么发送方的TCP在超时之后,就会重发整个TCP报文(头部+数据)。

因此,可以得知由IP层进行分片传输,是非常没有效率的。

所以,为了达到最佳的传输效能,TCP协议在建立连接的时候要协商双方的MSS数值,当TCP层发现数据超过MSS的时候,会先进行分片,由它形成的IP包的长度就不会大于MTU,自然也就不用IP分片了。经过TCP层分片之后,如果一个TCP分片丢失之后,进行重发也是以MSS为单位,不用重传所有的分片,大大增加了重传的效率。

 

IPv6地址是128位的。

(1)DNS域名解析。

DNS解析过程,就是域名变成IP的过程。先找浏览器的DNS缓存,没有就找操作系统的DNS缓存和host文件,没有就找本地域名服务器,没有就找根域名服务器,顶级域名服务器(com),权限域名服务器(server.com),返回IP给本地域名服务器缓存。返回给操作系统DNS缓存。浏览器得到IP。

递归:客户端只发出一次请求,要求对象给出最终结果。

迭代:客户端发出一次请求,如果对方没有授权回答,就会返回一个能解答这个查询的其他名称的服务器列表。客户端回再向返回的列表中发出请求,直到找到最后负责所查域名的名称服务器,从它那得到最终结果。

客户端——本地DNS服务端的部分,属于递归查询。本地DNS服务端——外网的部分,属于迭代查询。递归查询只返回成功或失败,迭代查询返回最佳的查询点或主机地址。

 

 

(2)ARP和RARP协议。传输一个IP数据报的时候,确定了源IP地址和目标IP地址之后,就会通过主机路由表,确定IP数据包的吓一跳。网络层的下一层就是数据链路层,所以我们还要知道下一跳的MAC地址。

主机会通过广播发送ARP请求,这个包里面包含了想要知道的MAC地址的主机IP地址。当同链路中的所有设备都收到ARP请求的时候,就会拆开ARP请求包的内容,如果ARP请求包中的目标IP地址和自己的IP地址一致,这个设备就会把自己的MAC地址放到ARP响应包返回给主机。获取过一次就会缓存,但是缓存也有有期限。

RARP协议是已知MAC地址求IP地址。比如将打印机等小型嵌入式设备接入网络就会经常用到。

(3)DHCP动态获取IP地址。

我们的电脑通常是通过DHCP动态获取IP地址,大大省去配IP信息繁琐的过程。

DHCP客户端进程监听的是68端口号,DHCP服务端进程监听的是67端口号。

客户端发起DHCP发现报文,使用UDP广播通信,其使用的广播目的地址是255.255.255.255(端口67),并且使用0.0.0.0(端口68)作为源IP地址。DHCP客户端把该IP数据包传递给数据链路层,链路层将帧广播到所有的网络中设备。

DHCP服务器收到发现报文的时候,用DHCP提供报文向客户端做出响应。该报文信息携带服务器提供可租约的IP地址、子网掩码、默认网关、DNS服务器以及IP地址租用期,仍然使用IP广播地址255.255.255.255。客户端收到一个或多个服务器的DHCP报文后,从中选择一个服务器,并向选中的服务器发送DHCP请求报文进行响应,回显配置的参数。最后,服务器用DHCP ACK报文对DHCP请求报文进行响应,应答所要求的参数。

如果租约的DHCP IP地址快到期,客户端就会向服务端发送DHCP请求报文。服务器如果同意续租,则用DHCP ACK报文进行应答,客户端就会延长租期。如果不同意续租,则用DHCP NACK报文,客户端就要停止使用租约的IP地址。

如果DHCP服务器和客户端不在一个局域网内,就使用DHCP中继代理。DHCP客户端会向DHCP中继代理发送DHCP请求包,而DHCP中继代理在收到这个广播包之后,再以单播的形式发给DHCP服务器。服务端收到该包以后,向DHCP中继代理返回应答,并由DHCP中继代理将此包广播给DHCP客户端。因此,DHCP服务器即使不在同一个链路上也可以实现统一分配和管理IP地址。

(4)NAT网络地址转换。

NAT就是同一个公司有一个共有的基础IP,同一个公司的主机对外通信的时候,把私有的IP地址转换成共有的IP地址。NAPT就是IP+端口号。生成一个NAPT路由器的转换表,就可以正确转换地址和端口的组合,令客户端A和B能同时和服务器之间进行通信。这种转换表在NAT路由器上自动生成。例如,在TCP的情况下,建立TCP连接首次握手的时候的SYN包一旦发出,就会生成这个表。然后随着收到关闭连接的时候发出FIN包的确认应答从表中被删除。

(5)ICMP互联网控制报文协议。

网络包在复杂的网络传输环境里,常常会遇到各种问题。当遇到问题的时候,需要传出消息,报告遇到了什么问题,才可以调整传输策略,以此来控制整个局面。ICMP的主要功能包括:确认IP包是否成功送达目标地址;报告发送过程中IP包被废弃的原因和改善网络设置等等。

场景:主机A要给主机B发送,途中经路由器1和路由器2,能到路由器2,但是路由器2不能发现主机B的存在。

这时,路由器2就会给主机A发送一个ICMP目标不可达数据包。

ICMP的通知消息会使用IP进行发送。ICMP大致可以分成两大类。

一类是用于诊断的查询消息,也就是查询报文类型。0表示回送应答,8表示回送请求。这是ping的原理,可以向对端主机发送回送请求的消息8,也可以接收对端主机发回来的回送应答消息0.

另一类是通知出错原因的错误消息,也就是差错报文类型。3表示目标不可达,4表示原点抑制,5表示重定向或改变路由,11表示超时。IP路由器无法将IP数据包发送给目标地址的时候,会给发送端主机返回一个目标不可达的ICMP消息,并在这个消息中显示不可达的具体原因,原因记录在ICMP包头的代码字段。网络不可达代码为0,主机不可达代码为1,协议不可达代码为2,端口不可达代码为3,需要进行分片但是设置了不分片位的代码为4。

具体差错报文类型展开如下。
(1)网络不可达代码为0。IP地址是分为网络号和主机号的,所以当路由器中的路由表匹配不到接收方IP的网络号,就通过ICMP协议以 网络不可达 的原因告知主机。

(2)主机不可达代码为1。当路由表没有该主机消息,或者该主机没有连接到网络,就会通过ICMP协议以主机不可达的原因告知主机。

(3)协议不可达代码为2。当主机使用TCP协议访问对端主机的时候,能找到对端主机。但是对端主机的防火墙已经禁止了TCP协议的访问,那么会通过ICMP协议以协议不可达的原因告知主机。

(4)端口不可达代码为3。当主机访问对端主机的8080端口的时候,能找到对端主机,防火墙也没有限制,可是发现对端主机没有进程监听8080端口,那么会通过ICMP协议以端口不可达的原因告知主机。

(5)需要进行分片但设置了不可分片位代码为4。发送端主机发送IP数据包的时候,将IP首部的分片禁止标志位设置成1。根据这个标志位,途中的路由器遇到超过MTU大小需要分片的数据包的时候,不会进行分片,而是直接抛弃。随后,通过一个ICMP的不可达消息类型,告知发送端主机。

(6)原点抑制消息代码为4。使用原点抑制消息是为了缓和网络拥堵的情况。

(7)重定向消息代码为5。路由器发现了发送端没有使用最优的路径发送数据,那么路由器会通过ICMP重定向消息告诉主机,在这个消息里面包含了最合适的路由信息和源数据。

(8)超时消息代码为11。IP包里有一个叫TTL的字段,就是Time to live生存周期。它的值随着每经过一次路由器就会减1,直到减到0的时候该IP包会被丢弃。此时,路由器将会发送一个ICMP超时消息给发送端,告知它这个包被丢弃了。

设置IP包的TTL的目的在于,在路由控制遇到问题发生循环状况的时候,避免IP包无休止的在网络上被转发。

 

ping客户端发送的ICMP回送请求报文包括:类型为8,序号为1,发送时间为t。

然后加上目的地址的IP头部和协议1(表示ICMP协议)。然后加上目标MAC头部。

ping服务端发送的ICMP回送应答报文包括:类型为0,序号为1,发送时间为t。

 

IP协议不能保证IP数据报准确到达接收端,只是承诺尽最大努力。很多情况都能导致发送失败,比如中转路由器发现IP数据报存活时间太长,也就是TTL字段太长,就会丢弃并返回ICMP超时错误信息。

  

ping的原理。

ping是基于ICMP的查询报文类型实现的,发送端发送ICMP回送请求8给接收端,然后接收端发送ICMP回送响应(回送应答)0给发送端。

 

补充硬件:Hub、交换机、路由器。

Hub是使用广播的方式让主机A和主机B通信,会广播给线上所有机器。共享一个冲突域,即所有机器共享一个带宽。

交换机是使用转发的方式让A和B通信,只会在AB间进行转发。每个机器的带宽独立,不占用别人的带宽。

交换机的端口多,直连很多终端设备。路由器的端口少,不直连终端,而是直连一个子网。

 

交换机:遇到广播数据包会转发,所以不分隔广播域。

路由器:遇到广播数据包会丢弃,所以分隔了广播域。

 

常用网络层命令:

ifconfig:可显示本机的IP地址。

netstat -r:可显示路由表。

tcpdump:可显示硬件地址。

ping:测试另一个主机是否可达。

traceroute:利用ICMP跟踪途径的所有路由。

route:命令可查看和修改路由表。

gated:可查看IGP内部网关协议和EGP外部网关协议类型。

https://cloud.tencent.com/developer/article/1482633

 

 

如果tcp连接被对方正常关闭,对方是正确调用closesocket或者shutdown,那么send或recv调用就能马上返回,并且报错。它们有个正常的关闭过程,会告诉对方tcp连接已经关闭。
但是如果意外断开,客户端没有正常关闭socket,双方没有按照协议上的四次挥手去断开连接。这时候正在执行的send或recv就会因为没有任何连接中断的通知而一直等待下去,会被长时间卡住。
解决意外断开是用保活机制。
 
(1)当recv()返回值小于等于0的时候,socket连接断开。但是还需要判断errno是否等于EINTR,如果是则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不能close掉socket连接。
(2)如果使用了select系统调用,如果远端断开,则select返回1,recv返回0则断开。
我们在注册epoll的时候主要是监听读就绪和写就绪,可以创建读写回调函数,当检测到对应事件的时候,调用对应的程序。为了检测客户端和服务端断开我们需要注册另一个EPOLLRDHUP,代表读关闭,当远端close的时候,会触发这个事件。
 
(3)TCP层,内核态实现的keepAlive属性,设置后,如果断开,则在使用该socket读写的时候会立即失败,并返回ETIMEDOUT错误。
so_keepalive保持连接检测对方主机是否崩溃,避免服务器永远阻塞在TCP连接的输入。设置这个选项之后,如果两小时内在此套接口的任一方向都没有数据交换,TCP就会自动给对方发送一个存活的探测。
(4)在应用层自己实现一个心跳检测,一定时间内没有收到自定义的心跳包就标记位已经断开。
so_keepalive是实现再服务器端,客户端是被动响应的,是实现在TCP协议栈。应用层的心跳包实现在第七层,本质没有任何区别,但是应用层需要自定义心跳包格式。
之所以实现在服务端,是因为和客户端相比,服务端的寿命更长,因为服务端需要不间断提供服务,而客户端可能由于用户上下班休眠电脑,这样,服务端就有很多不可用的TCP连接,这样的连接会占用服务器内存资源,于是设计了keepalive,客户端没有响应,就删除连接,释放资源。
需要注意的是,超时时间是指TCP连接没有任何数据、控制字传输的时间,如果有数据传输,会刷新定时器,重新走表。
 
TCP keepalive检查连接是否存活。系统默认是设置两小时的心跳频率。但是它检测不到机器断电、网线拔出等断线。一般用于保活。(内核实现,当客户端和服务端长时间没有进行数据交互,内核为了确保连接是否还有效,就会发送探测报文检测对方是否在线,决定是否要关闭i连接)
应用层keepalive检测应用是否可以正常响应。判断掉线,只需要send或者recv一下,如果结果为0,就是掉线。在长连接下,可能很长一段时间都没有数据往来。用于长连接的保活和掉线处理。
 
(5)getsockpot
get_sockopt(g_fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); if((info.tcpi_state == TCP_ESTABLISHED))
 
拔掉网线:有数据传输(服务端超时重传,重传过程插回网线当作没事,没插回就会服务端断开连接,客户端一开始保留等待插回服务器返回RST就会释放连接),无数据传输(TCP保活机制,没开启连接就会一直存在,开启就可以确定对方连接是否存活)
 宕机:没数据一样,有数据就会进行四次挥手。

posted @   花与不易  阅读(467)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示