层级网络的思想(为什么网络需要分层?)
- 符合高内聚低耦合的思想。
- 每一层只专注干一件事
- 每一层都使用该层和下一层提供的服务(每一层都暴露接口供上一层使用)
- 各层之间相互独立,不用关心其他层是怎么实现的,只需调用它暴露的服务接口就行
- 提高整体灵活性,每一层都可以用最合适的技术实现,层内能灵活修改,只需暴露的接口规则不变就行
- 将复杂问题转换成若干个小问题
OSI七层模型是什么?每一层有什么作用?
自顶向下是:
- 应用层:应用程序,为计算机用户提供服务
- 表示层:数据的格式化表示和数据转换处理,确保应用程序间的数据能被对方识别
- 会话层:管理(建立、管理、终止)应用程序之间的会话,由应用程序之间的服务请求和响应组成
- 传输层:建立两台主机的进程间的通信信道,提供端到端的数据传输服务
- 网络层:负责数据的路由和寻址(寻找数据在网络中的游走路径),通过ip地址建立节点间的连接
- 数据链路层:负责无错传输数据,使用链路层地址访问物理介质,帧编码,并进行差错纠正控制(奇偶校验等)。
- 物理层:通过物理介质传输比特流。
TCP/IP五层网络模型是什么?每一层的作用是什么?
自顶向下:
-
应用层:面向用户的交互,包含了OSI中的应用层、表示层、会话层。
- 协议:HTTP、HTTPS、FTP、DNS、POP、IMAP、SMTP、SSH、Telnet
-
传输层:建立两台主机的进程间的通信,提供端到端的数据传输服务。
- 协议:TCP、UDP
-
网络层:负责数据的路由和寻址(寻找数据在网络中的游走路径),通过ip地址建立节点间的连接。
- 协议:IP、ICMP、RIP
-
数据链路层:负责无错传输数据,使用链路层地址访问物理介质,帧编码,合法性检验,并进行差错纠正控制。
- 协议:ARP、RARP、PPP
-
物理层:通过物理介质传输比特流。
- 协议:802.11、蓝牙、WiFi协议
TCP/IP模型的应用层有哪些常见的协议?
-
HTTP (超文本传输协议)
- 主要用于Web浏览器和Web服务器之间的通信。
- 主流用HTTP1.1协议,默认长连接。
- 基于TCP协议,HTTP请求之前要建立TCP连接(3次握手)。
- HTTP是无状态的,需要通过cookie、session来记录客户端的状态。
- 默认端口:80
- 明文,不安全
-
HTTPS (安全的超文本传输协议)
-
最主要改进了3个点:
- 信息加密。采用混合加密,混合使用对称加密和非对称加密,也就是用非对称加密来加密对称加密的会话密钥。保证了安全和效率。
- 防内容篡改:摘要算法 + 数字签名。
- 身份校验:CA证书。
-
披着SSL/TLS外衣的HTTP协议。HTTP 与 TCP 层之间加入了
SSL/TLS
协议。 -
SSL是TLS的前身,现在的浏览器已经不支持SSL了,只支持TLS。
-
默认端口:443
-
-
SMTP(简单邮件传输(发送)协议)
- 传输是只管发送,不管接收,需要与POP3区分开。
- 基于TCP。
- 发送邮件的具体流程(eg:123@163.com传给456@qq.com):
- 通过SMTP协议将写好的邮件发送给对应的SMTP服务器(163的服务器)
- 163的服务器使用SMTP协议将邮件发给qq的服务器
- qq服务器收到后通知456@qq.com的用户来收邮件,用户通过POP3或IMAP协议来取邮件
-
POP3/IMAP (邮件接收协议)
- 只负责接收邮件
- IMAP比POP3新一些
-
FTP (文件传输协议)
- 基于TCP,而且是两条TCP连接,效率更高:
- 控制连接:传送命令和相应
- 数据连接:传送文件数据
- 基于TCP,而且是两条TCP连接,效率更高:
-
Telnet (远程登录协议)
- 基于TCP
- 通过远程的终端登录到某台服务器
- 最大的缺点:所有数据(包括用户名和密码)都以明文形式发送,有安全风险
-
SSH (secure shell,是安全的网络传输协议)
- 基于TCP
- 也是用于远程登录
- 传输数据是加密的
MSL、TTL、RTT是什么?
- MSL(Maximum Segment Lifetime)最大报文存活时间。任何报文(如TCP/UDP报文)在网络上存在的最长时间,超过这个时间的报文被接受后都将被丢弃,防止扰乱网络系统。
- TTL(time to live)在ip头中有一个TTL字段,存的是IP包最大路由数,而不是存的具体时间,每经过一个处理他的路由器此值就减1,当此值为0时将被丢弃,同时发送ICMP报文通知源主机。
- RTT(round-trip time)发起请求到得到响应(一个来回)的时间。TCP拥塞控制中最重要的部分就是对某个连接的RTT的测量。TCP会跟踪RTT的变化来做拥塞控制。
TCP
TCP头部格式
TCP有6种标识:
- SYN(建立联接)
- ACK(确认)
- PSH(传送) :告知对方这些数据包收到后应该马上交给上层的应用,不能缓存
- FIN(结束)
- RST(重置,Reset,标示复位,用于强制关闭连接)
- URG(紧急);
TCP建连4要素
- 源IP
- 源端口
- 目标IP
- 目标端口
建立连接——3次握手
假设 A 为客户端,B 为服务器端。
- 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。
- A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。
- B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。
- A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。
- B 收到 A 的确认后,连接建立。
简化版:
- 客户端:SYN
- 服务器:SYN+ACK
- 客户端:ACK
建立连接的过程就是在确认双方都有发送和接收的能力,一开始客户端向服务器发送SYN就确认了客户端具有发送能力,服务器收到后发送SYN和ACK就确认了服务器具有发送和接收的能力,客户端收到后再发送ACK就确认了客户端具有接收能力。**3次握手就能确认双方都有发送和接收的能力。**理解了这个,就能能知道为什么不能两次或者四次。
为什么不能两次?
若客户端第一次发送的SYN滞留在网络,超时后客户端又发一次SYN,在两者建立连接后,滞留的SYN发到了服务器,服务器就对同一个客户端建立了两个连接,造成资源浪费。
为什么不能四次?
3次已经可以了,4次往上就浪费了。
TCP三次握手中,最后一次发送的ACK丢失,会发生什么?
- 此时服务器处于SYN_RECV状态,会根据TCP超时重传机制依次等待1、2、4、8、16秒都重发SYN+ACK让客户端应答,若重发了指定次数(默认是5次,也就是1+2+4+8+16+32=63秒)客户端还未ACK回应,过一段时间后服务端会自动关闭此连接。
- 但客户端此时处于ESTABLISH状态,会正常向服务器发送数据,如果服务器收到数据,会以RST(Reset,标示复位,用于强制关闭异常的连接)响应,客户端就知道这个连接是异常的。
3次握手可以携带数据吗?
第1、2次不可以,第3次可以。如果可以的话,就能在第一次SYN中插入数据,让接收端花时间和空间处理数据,若有大量SYN涌入,会使接收端受攻击。
第3次发送端已经为establish状态,且接收端已确认了发送和接收的能力,这时携带数据就相对安全很多了。
如果双方同时发送SYN会怎么样?
- 双方发送SYN后,双方都变成SYN-SENT状态
- 双方都收到对方发送的SYN后,又变成SYN-RCVD状态(双发都只发送了一次SYN就能到这个状态了)
- 接着双发发送SYN+ACK,双发状态又变成ESTABLISH。建立连接成功。
第一次握手时syn半连接队列满了会怎样?
没开启tcp_syncookies:
- 如果半连接队列满了,则会丢弃;
- 如果半连接队列没满,但全连接队列满了,且没有重传 SYN+ACK 包的连接请求多于 1 个,则会丢弃;
- 两个队列都没满,但max_syn_backlog 减去当前半连接队列长度小于 (max_syn_backlog >> 2),则会丢弃;
开启tcp_syncookies:
syncookies 功能可以在不使用 SYN 半连接队列的情况下成功建立连接然后进入accept全连接队列。服务器收到SYN后不立即进行第二次握手并分配资源,而是只打开一个半开的套接字,并根据这个SYN计算出一个cookie,连同第二次握手发给客户端,客户端第三次握手时带上这个cookie,验证合法后服务器再完全分配资源。TCP可以选择是否开启syncookies功能,参数主要有以下三个值:
- syncookies=0,表示关闭该功能;
- syncookies=1,表示仅当 SYN 半连接队列放不下时,再启用它;
- syncookies=2,表示无条件开启功能(不使用半连接队列);
syncookies 方案为什么不直接取代半连接队列?
虽然能防 SYN Flood攻击,但是也有一些问题。
-
服务端并不会保存连接信息(没有半连接队列保存连接信息,只保存了cookies信息),所以如果第二次握手丢包了是不会重发的,无法保证连接的建立。
-
防不了ACK攻击:编解码
cookies
,比较耗CPU,利用这一点,如果此时攻击者构造大量的第三次握手包(ACK包),同时带上各种瞎编的cookies
信息,服务端收到ACK
后以为是正经cookies,憨憨地跑去解码(耗CPU),最后发现不是正经数据包后才丢弃。
第三次握手时accept全连接队列满会怎样?
tcp_abort_on_overflow参数代表两个策略:
- tcp_abort_on_overflow=0 :如果全连接队列满了,那么 server 直接丢弃;通常情况下选这个,因为能更好应对突发流量,举个例子,当 TCP 全连接队列满导致服务器丢掉了 ACK,与此同时,客户端的连接状态却是 ESTABLISHED,只要服务器没有为请求回复 ACK,请求就会被多次重发。如果服务器上的进程只是短暂的繁忙造成 accept 队列满,那么当全连接队列有空位时,返回ACK,仍然能成功建立连接。
- tcp_abort_on_overflow=1 :如果全连接队列满了,server 发送一个
reset
包给 client,表示废掉这个握手过程和这个连接;
SYN洪泛攻击(SYN Flood,或者叫半开放攻击)是什么?怎么解决?
利用的是TCP设计上的缺陷。SYN Flood属于Dos/DDos攻击,常用不存在的IP发送大量伪造的TCP连接请求的第一个SYN握手包,被攻击服务器会回应SYN+ACK,连接会变为SYN_RECV状态,加入到半连接队列,而且IP不存在不会应答,还会默认重发5次SYN+ACK,大量的SYN攻击让半连接队列爆满,正常的业务请求就无法进来。(服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击)
如何发现:半连接队列里有大量SYN,而且IP地址比较随机的,基本上可以判断这是一次SYN泛洪攻击。Linux可以用netstats命令查看。
如何解决:(不能被完全解决,除非重新设计TCP,能做的只有减轻其危害)
- 增大半连接队列。
- 缩短超时时间。
- 减少重发SYN+ACK的次数。
- 加网关过滤防护。
- 开启SYN cookies技术。
断开连接——4次挥手
- A 发送连接释放报文,FIN=1。
- B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。
- 当 B 发送完所有数据不再需要连接时,发送连接释放报文,FIN=1。
- A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 个MSL(最大报文存活时间)如果没有收到B的重发请求,就会释放连接。
- B 收到 A 的确认后释放连接。
简化版:
- 客户端:FIN
- 服务器:ACK
- 服务器:FIN+ACK
- 客户端:ACK
为什么是4次挥手?为什么不能是3次?
相比于3次握手,多出来的一次是服务器发送ACK,这是为了等服务器发送完所有数据。如果剩下的数据很多,要等比较长的时间,如果没有多出来的一次告诉客户端已经接收到了FIN,那么在FIN+ACK发送之前这么长的数据发送时间会让客户端以为FIN丢包了,就会重传。
为什么要有TIME-WAIT?
确保最后的ack一定能够到达服务器,否则服务器一直关闭不了链接。
TIME-WAIT为什么要2个MSL?
- 防止客户端发送的最后一个ACK丢失,如果第1个MSL内服务器没有收到ACK,服务器就会重发FIN,让客户端再次进入TIME-WAIT。第2个MSL能确保服务器重发的FIN能到达客户端。
- 防止下一个连接收到之前连接的旧报文,因为2MSL可以让上一个连接持续的时间内所产生的所有报文都从网络中消失,下一个新的连接就不会收到上一个连接旧的报文。
如果没有这段时间没收到服务器的FIN就会自动关闭连接。
LAST_ACK阶段一直收不到对方的ACK会怎样?
服务器超时重传FIN,重传几次后仍无ack,就自动关闭连接。
TCP和UDP能bind同一个端口吗?
可以。
多个TCP服务进程可以bind同一个端口吗?/多个UDP服务进程可以bind同一个端口吗?
不能。会显示“Address in use”的报错信息。
重启 TCP 服务进程时,为什么会有“Address in use”的报错信息?怎么解决?
-
原因:当 TCP 服务进程重启时,连接会变成 TIME_WAIT 状态,重启前TIME_WAIT 状态的连接使用的 IP+PORT 仍然被认为是一个有效的 IP+PORT 组合,那么执行 bind() 函数的时候,就会返回了 Address already in use 的错误。等 TIME_WAIT 状态的连接结束后,重启的 TCP 服务进程就能成功。
-
解决:在进程调用 bind() 前,对 socket 设置 SO_REUSEADDR 属性。如果当前启动进程绑定的 IP+PORT 与处于TIME_WAIT 状态的连接占用的 IP+PORT 存在冲突,但是新启动的进程使用了 SO_REUSEADDR 选项,那么该进程就可以绑定成功。
服务器出现大量 TIME_WAIT 状态的原因有哪些?
TIME_WAIT 状态是主动关闭连接方才会出现的状态,说明服务器主动断开了很多 TCP 连接。
原因有:
- 在使用 HTTP/1.1长连接的时候,HTTP 请求的客户端数量超过服务器指定的最大长连接个数。解决方式:调大最大长连接个数。
- 如果服务器是反向代理服务器,nginx(在服务器上跑)与后端进行大量的短连接请求,由于nginx 会主动挂断这个连接,在服务器上就会出现大量的 TIME_WAIT 状态。解决方式:使用长连接。
- 服务器的大量进程挂掉了,内核的协议栈会发起 FIN 报文,相当于服务器主动断开连接。
客户端 TCP 连接 TIME_WAIT 状态过多,会导致端口资源耗尽而无法建立新的连接吗?
要看客户端是否都是与同一个服务器(IP+port)建立连接。
如果客户端都是与同一个服务器(目标地址和目标端口一样)建立连接,客户端 TIME_WAIT 状态的连接过多会造成端口资源被耗尽,就无法与这个服务器再建立连接了。
但是,只要客户端连接的服务器(IP+port)不同,端口资源是可以重复使用的。
所以,如果客户端都是与不同的服务器建立连接,即使客户端端口资源只有几万个, 客户端发起百万级连接也是没问题的(当然这个过程还会受限于其他资源,比如文件描述符、内存、CPU 等)。
如何解决客户端 TCP 连接 TIME_WAIT 过多,导致无法与同一个服务器建立连接的问题?
打开 net.ipv4.tcp_tw_reuse 这个内核参数,客户端调用 connect 函数时,如果选择到的端口,已经被相同四元组的连接占用的时候,就会判断该连接是否处于 TIME_WAIT 状态。如果该连接处于 TIME_WAIT 状态并且 TIME_WAIT 状态持续的时间超过了 1 秒,那么就会重用这个连接,然后就可以正常使用该端口了。
TIME-WAIT 状态过多会产生什么后果?怎样处理?
客户端和服务器都可以进入TIME-WAIT,具体的危害要分开两者来看:
- 服务器有大量TIME-WAIT:占用大量服务器端口和资源,会让部分客户端显示连接不上服务器。
- 客户端有大量TIME-WAIT:占用大量客户端端口和资源,客户端无法建立新连接。
- 具体地看高并发短连接的TCP服务器,处理完请求后主动关闭连接,会有大量socket进入TIME-WAIT:
- 短连接:业务处理+传输数据的时间(干正事的时间)远小于TIME-WAIT超时时间的连接。
- 占用大量端口,高并发请求下会显示连接不上服务器。
解决方案:
-
负载均衡来分配短连接请求。
-
发送RST,绕过TIME-WAIT,强制关闭。
-
多个连接重用客户端同一个IP+port:因为**TCP 连接是由四元组(源IP,源端口,目的IP,目的端口)唯一确认的,那么只要四元组中其中一个元素发生了变化,那么就表示不同的 TCP 连接的。**但因为同一台机器的同一个ip+port只能被同一个socket进程占用,所以属于一个进程复用多个不同的连接。
-
优化服务器TIME-WAIT相关的内核配置参数:打开 net.ipv4.tcp_tw_reuse 这个内核参数等。
服务器出现了大量 CLOSE_WAIT 状态是啥原因?如何解决?
- 根因:服务器忙于读写不能及时释放资源或者是代码层面上忘了释放资源。可能出现问题的地方及解决方案有:
- 代码没写好,忘了释放资源,要检查代码。
- 数据库长时间IO读写阻塞,要设置超时时间。
- 检查一下处理请求的配置。
TCP 序列号和确认号是如何变化的?
- seq序列号 = 上一次发送的seq + len(数据长度)。特殊情况,如果上一次发送的报文是 SYN 报文或者 FIN 报文,则为上一次发送的序列号 + 1。
- ack确认号 = 上一次收到的报文中的seq + len(数据长度)。特殊情况,如果收到的是 SYN 报文或者 FIN 报文,则改为上一次收到的报文中的序列号 + 1。
TCP和UDP的对比
TCP | UDP | |
---|---|---|
是否面向连接 | 是 | 否 |
是否可靠 | 是 | 否 |
是否有状态 | 是 | 否 |
传输效率 | 较慢 | 较快 |
传输形式 | 字节流 | 数据报文段 |
首部开销 | 20 ~ 60 bytes | 8 bytes |
是否提供广播或多播服务 | 否 | 是 |
- TCP主要用于对数据准确性要求高的场景,如文件传输、邮件传输、远程登录等。
- UDP主要用于即使通信,如直播、视频、语音通话等对传输数据准确性要求不高的场景。
粘包拆包问题
之所以会说 TCP 是面向字节流的协议,UDP 是面向报文的协议,是因为操作系统的网络协议栈对 TCP 和 UDP 协议的发送机制不同,也就是问题原因在发送方。
UDP是面向报文的协议
操作系统网络协议栈不会对UDP的数据包消息进行拆分,组装好 UDP 头部后就交给网络层来处理,所以发出去的 UDP 报文中的数据部分就是完整的用户消息,也就是每个 UDP 报文就是一个用户消息的边界,接收方读一个 UDP 报文就能读到完整的消息。接收方的操作系统在收到 UDP 报文后,会将其放入队列,队列里的每一个元素就是一个 UDP 报文,用户调用 recvfrom() 系统调用读数据的时候,就会从队列里取出一个数据,然后从内核里拷贝给用户缓冲区。
TCP面向字节流的协议
TCP是基于字节流的,它不包含消息、数据包等概念,当消息通过 TCP 传输时,消息可能会被操作系统网络协议栈分成多个的 TCP 报文。当调用 send()完成数据“发送”以后,数据并没有真正从网络上发送出去,只是从应用程序拷贝到了操作系统内核协议栈中,至于什么时候真正被发送,取决于发送窗口、拥塞窗口以及当前发送缓冲区的大小等条件,不能保证每次 send()都会将一个完整的消息发送出去。所以需要应用层协议(应用程序)自己设计消息的边界来模拟一个个“数据包”,即消息帧(Message Framing)。
如果应用层协议没有使用基于长度或者基于终结符息边界等方式进行处理,则会导致多个消息的粘包和拆包。粘包这个问题的根因是开发人员没有正确理解 TCP 面向字节流的数据传输方式,并不是 TCP 本身设计的问题,是开发者的锅。所以粘包拆包尽量不要说成“tcp的粘包拆包”。
解决方法:
- 固定长度的消息,如果长度不足可通过补0或空等填充到指定长度。灵活性不高,实际中很少用。
- 特殊字符作为边界。有一点要注意,这个作为边界点的特殊字符,如果刚好消息内容里有这个特殊字符,我们要对这个字符转义,避免被接收方当作消息的边界点而解析到无效的数据。HTTP、FTP等协议都是这么干的。
- 自定义消息结构。一个“数据包”由包头和数据组成,其中包头是固定大小的,而且包头里有一个字段来记录紧随其后的数据有多大。(数据长度加数据)
TCP滑动窗口?
-
滑动窗口的出现是为了解决TCP每发送一次数据,就需要一次确认应答造成的传输效率低的问题。
-
滑动窗口的大小就是无需等待确认应答,可以继续发送数据的最大值。即使中途有某个包丢掉了,也不需要重传,可以通过下一个确认应答进行确认。
-
其是基于缓存来实现的,发送方在收到确认应答之前,需要把已发送的数据保存在缓存中。收到确认应答才能从缓存中淘汰这些数据。
-
滑动窗口有两种:发送窗口和接收窗口。若发送的数据大于接收窗口的大小,接收方就无法正常接收数据。
发送窗口:
- 发送方缓存中数据有4部分:
- 已发送已确认:当接收方应答ACK后发送窗口就可以向右滑动了
- 已发送未确认
- 未发送但可发送(可用窗口,接收方在应答时发送自己的窗口大小就可以知道)
- 未发送但不可发送
- 窗口的大小和范围由3个指针来记录:
- SND.WND:发送窗口的大小
- SND.UNA:发送窗口的第一个字节
- SND.NXT:可用窗口的第一个字节
接收窗口:
- 接收方缓存中数据有3部分:
- 已接收已确认
- 未接收但可接收
- 未接收但不可接收
- 窗口的大小和范围由2个指针来记录:
- REV.WND:接收窗口的大小
- REV.NXT:接收窗口的第一个字节
- 窗口大小通常由接收方决定,通过TCP报文头的Window字段,接收端就可以告诉发送端自己还可以接收多少数据(接收窗口有多大),发送端可以据此来调整发送窗口的大小,控制发送的数据量。流量控制就是基于滑动窗口以及接收窗口大小的通信实现的。
- 因为存在网络迟延,所以发送窗口和接收窗口的大小不能永远一致,两者是约等于关系。
- 发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。
TCP拥塞控制?
如果网络出现拥塞,数据包会丢失,此时发送方会按原来的速率继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。**流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。**有四种算法实现拥塞控制:慢开始、拥塞避免、快重传、快恢复。
拥塞窗口(cwnd),指发送方能传输的最大数据大小,注意拥塞窗口与发送窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送窗口。拥塞窗口是用来限制发送窗口大小的,发送窗口大小不可超过拥塞窗口。
-
慢开始与拥塞避免
-
慢开始:发送的最初执行慢开始,令 cwnd = 1,发送方最多只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 …
-
拥塞避免:慢开始每个轮次都将 cwnd 加倍,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。如果出现了超时就会进行超时重传,则令 **ssthresh = cwnd / 2,然后cwnd=1重新执行慢开始。**这种方式太激进了,反应也很强烈,会造成网络卡顿。
-
-
快重传与快恢复
-
快重传:如果发生了丢包,即接收端发现数据段不是按序到达的时候,接收端的处理是重复发送之前的 ACK。当发送端收到3个连续的M2的ACK,就知道M2往后的M3丢包了,会快重传M3。
-
快恢复:进行了快重传后,因为出现了丢包,会觉得现在网络可能出现拥塞,进行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,**直接进入拥塞避免。**这种方式就没那么激进。
-
TCP 如何保障可靠性?
- 3次握手建立连接,4次挥手关闭连接
- 序列号和确认应答
- 超时重传
- 校验和:校验不通过,直接丢弃并重传。
- 滑动窗口控制
- 拥塞控制
Linux是如何收发网络包的?
Linux网络协议栈
实现了tcp/ip五层模型了,对数据进行封装(自上往下)和分用(自下往上)。
数据在每一层的封装格式:
接收网络包流程
-
当网卡(是硬件)接收到一个网络包后,通过 DMA 将网络包写入到Ring Buffer(内核空间的一个环形缓冲区)中 ,接着网卡向 CPU 发起硬中断,CPU根据中断表,触发软中断,ksoftirqd 内核线程就会轮询获取和处理数据,从 Ring Buffer 中获取一个数据帧,用 sk_buff 将其表示为一个网络包,交给网络协议栈进行逐层分用。
-
进入到网络协议栈:
-
网络接口层:在这一层检查报文的合法性,如果不合法则丢弃,若合法则会找出该网络包的上层协议类型(比如是 IPv4还是 IPv6),接着再去掉帧头和帧尾,然后交给网络层。
-
网络层:则取出 IP 包,判断下一步的路由路径,比如是交给上层处理还是转发出去。当确认这个网络包要发送给本机后,就会从 IP 头里看看上一层协议类型是 TCP 还是 UDP,接着去掉 IP 头,然后交给传输层。
-
传输层:取出 TCP 头或 UDP 头,根据四元组「源 IP、源端口、目的 IP、目的端口」 ,找到对应的 Socket,并把数据放到 Socket 的接收缓冲区。
-
应用层:应用程序调用 Socket 接口,将内核的 Socket 接收缓冲区的数据**「拷贝」到应用层的缓冲区**,然后唤醒用户进程来接收数据。结束。
-
发送网络包流程
流程与接收相反,主要区别是一些实现细节。
因为协议栈采用的是分层结构,层间传递数据时需要增加或去掉包头,如果每一层都用一个结构体,层间传递数据就要多次拷贝,影响效率。所以是用同一个sk_buff 来表示各个层的数据包的,在应用层数据包叫 data,在 TCP 层叫 segment,在 IP 层叫 packet,在数据链路层叫 frame。通过调整 sk_buff 中
data
的指针就能实现:
- 接收报文时,自下往上传递,通过增加 skb->data 的值来逐步剥离协议首部。
- 发送报文时,创建 sk_buff 结构体,在数据缓存区的头部预留足够的空间来填充各层首部,自上往下传递,通过减少 skb->data 的值来增加协议首部。
-
应用层:应用程序调用 Socket 发送数据包的系统调用接口,从用户态切换到内核态中的 Socket 层,内核会申请一个内核态的 sk_buff 内存,将待发送的数据拷贝到 sk_buff 内存,并将其加入到发送缓冲区。然后协议栈从 Socket 发送缓冲区中取出 sk_buff,逐层封装。交给传输层。
-
传输层:如果使用 TCP 协议,会先拷贝一个新的 sk_buff 副本 ,因为 TCP 支持重传,在收到对方 ACK 之前,这个 sk_buff 不能被删除。所以每次调用网卡发送的都是 sk_buff 的一个拷贝,等收到 ACK 再真正删除。接着,对 sk_buff 填充 TCP 头,交给网络层。
-
网络层:路由寻址(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超过 MTU(最大传输单元, 以太网为
1500
字节) 大小的数据包进行分片。交给网络接口层。 -
网络接口层:通过 ARP 协议获得下一跳的 MAC 地址,然后对 sk_buff 填充帧头和帧尾,接着将 sk_buff 放到网卡的发送队列(是input_pkt_queue,这个队列是所有网卡共有的队列)中。
-
协议栈处理完后,触发「软中断」告诉网卡驱动程序,驱动程序会从发送队列中读取 sk_buff,将其挂到 RingBuffer 中,接着将 sk_buff 数据映射到网卡可访问的内存 DMA 区域,最后真正地发送。
-
当发送完成后,网卡触发硬中断来释放内存,主要释放 sk_buff 内存和清理 RingBuffer 内存。
-
最后,当收到这个 TCP 报文的 ACK 应答,传输层释放原始的 sk_buff 。
DNS解析流程是什么?
DNS解析就是把域名解析成其所在服务器的ip地址。
- 本地浏览器发起DNS解析请求,会去本地的HOST文件(其是将一些常用的网址域名与其对应的ip地址建立一个关联“数据库”,与本地dns服务器的缓存不是同一个东西)中查找,如果找到,直接返回这个ip地址。
- 如果没找到就将请求发送给本地dns服务器。
- 本地dns服务器收到请求后查询缓存。
- 如果缓存能找到,直接返回ip地址。
- 如果没找到本地dns服务器就将请求发送到根服务器。
- 根服务器会返回给本地dns服务器一个所查询域(根的子域)的主域名(.com.fi)服务器的地址。
- 本地dns服务器向上一步返回的dns服务器发送解析请求,接受请求的dns服务器查询其缓存,如果找到就返回ip,如果找不到就返回下级的域名服务器的ip。
- 重复第7步,直到找到对应的ip。
- 本地dns服务器把返回的ip保存到缓存中,供下次访问使用,并返回给浏览器。
上述步骤属于迭代查询:若某个dns服务器没有可以响应的结果,会向客户机提供其他能够解析查询请求的DNS服务器地址,本地再去请求这个服务器,迭代查询直到找到结果。
递归查询:DNS服务器接收到客户机请求,必须使用一个准确的查询结果回复客户机。如果DNS服务器本地没有存储查询DNS信息,那么该服务器会询问其他服务器,递归进行该过程,最后将返回的查询结果提交给客户机。
为什么DNS解析通常基于UDP?
DNS通常是基于UDP的,但当数据长度大于512字节的时候,为了保证传输质量,就会使用基于TCP的实现方式。
- DNS解析请求只需一个请求一个应答就行了,不需要建立连接,用UDP刚刚好。TCP还要多次握手挥手去建立连接,比较浪费网络资源。
- DNS解析的数据包不是大包,不用考虑分包,丢包就全丢,重试一次就行。而且DNS的报文允许填入序号字段,对于请求报文和其对应的应答报文,这个字段是相同的,通过它可以区分DNS应答是对应的哪个请求。
DNS劫持是啥?
劫持了dns服务器,可以修改域名解析结果,导致用户拿到的IP是修改后的IP,进入的就是假网站,从而实现窃取资料和破坏服务的目的。
DNS污染是啥?
是一种DNS缓存投毒攻击(DNS cache poisoning)。因为DNS请求没有认证机制,而且UDP是不可靠的,所以DNS请求很容易被篡改,通过对UDP端口53上的DNS请求进行入侵检测,一经发现与关键词相匹配的请求则立即伪装成目标域名的解析服务器(NS,Name Server)返回虚假结果。
一些被禁止访问的网站很多就是通过DNS污染来实现的,例如YouTube、Facebook等网站。
DNS劫持发生在DNS服务器,而DNS污染则发生在用户请求第一步,直接从协议上对用户的DNS请求进行干扰。
从输入URL到页面加载经历了哪些过程?
https://xiaolincoding.com/network/1_base/what_happen_url.html
- DNS域名解析找到对应的服务器的IP地址。
- 建立TCP连接
- 浏览器发送http/https请求
- 服务器响应请求并返回http报文
- 浏览器解析渲染页面
- 断开连接
HTTP
http状态码有哪些
- 1xx:指示信息——表示请求已接收,继续处理
- 2xx:成功——表示请求已被成功接收、理解、接受
- 3xx:重定向——要完成请求必须进行更进一步的操作
- 4xx:客户端错误——请求有语法错误或请求无法实现
- 5xx:服务器端错误——服务器未能实现合法的请求
http常用的请求方法有哪些?
- get:获取资源
- post:提交(创建)资源
- put:修改资源(客户端提供更新后的整个资源,也就是全部更新)
- patch:修改资源(客户端只提供某个修改后的属性,也就是局部更新,不常用)
- delete:删除资源
- option:客户端查看服务器的性能
get和post有啥区别?
-
作用:
- get:获取资源。
- post:提交资源。
-
参数:
- get:没有请求体,只能url路径传参和查询参数传参。只支持ASCII码,若出现中文需要进行urlencode为如
%E4%B8%AD%E6%96%87
。 - post:有请求体,可以请求体传参也可以url路径传参和查询参数传参。请求体中支持标准字符集。
- get:没有请求体,只能url路径传参和查询参数传参。只支持ASCII码,若出现中文需要进行urlencode为如
-
请求url长度:
- get:其实http协议没有限制,只是浏览器和服务器有限制。不同浏览器限制不一样(2k-8k);服务器一般在9k,且一般都有参数可以调。
- post:http协议没有限制。
-
安全性(是否影响服务器状态考虑):
- get:不改变服务器状态,对服务器安全。
- post:会改变服务器状态,不安全。
-
幂等性:
- get:幂等。多次调用收到的结果都一样。
- post:不幂等。如果调用多次,就会增加多行数据。
-
浏览器是否可缓存请求纪录:
- get:会缓存
- post:不会缓存
如果要对响应进行缓存,需要满足以下条件:
- 请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。
- 响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。
- 响应报文的 Cache-Control 首部字段没有指定不进行缓存。
http请求格式
- 请求行(request line)
- 请求头(header)
- 请求空行(blank line)
- 请求体(request body)
http响应报文
- 状态行(status line)
- 响应头(header)
- 响应空行(blank line)
- 响应体(response-body)
http/1.1、http/2.0、http/3.0的区别有哪些?
HTTP/1.1 的缺陷
HTTP/1.1 实现简单是以牺牲性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量;
- 不支持资源处理优先级,需要处理完这个http请求之后才能处理下一个,致使底层 TCP 连接的利用率低下。如果响应迟迟不来,那么后续的请求是无法发送的,也造成了http/1.1的队头阻塞问题(是http协议方面)。
http/2.0 有些啥改进?
-
二进制分帧层:
HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,极大提高了传输效率,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)。帧是http/2中最小单位
-
并发传输:(解决了http/1.1的在http协议层面的队头阻塞问题,但TCP层面的队头阻塞没解决,http/3能彻底解决)
- 多个双向数据流(Stream)复用同一个TCP连接,Stream是可以并发的,实现单连接的并发传输(一个TCP连接上并发的http请求响应)。
- 多个 Stream 跑在一条 TCP 连接,同一个 HTTP 请求与响应是跑在同一个 Stream 中,(但同一个连接中的 Stream 是不能被不同的http请求响应复用的,可理解为一个Stream就是一次http请求和响应,Stream ID只能顺序递增,当 Stream ID 耗尽时,就会关闭TCP连接),HTTP 消息可以由多个 Frame 构成, 一个 Frame 可以由多个 TCP 报文构成。
- 每一个数据流(Stream)都有一个唯一标识符StreamID和可选的优先级信息,支持按优先级处理。
-
服务端推送
HTTP/2.0 在客户端请求某个资源时,服务端会把该资源相关的其他资源一起响应给客户端,客户端就不需再次请求相关的其他资源。
-
首部压缩
-
通过「静态表、动态表、Huffman 编码」共同完成的。
-
HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表(字典),从而避免了重复传输。
-
使用 Huffman 编码对首部字段进行压缩。
-
HTTP/2.0 要求客户端和服务器之间:
- 维护一份相同的静态字典(Static Table),包含常见的头部名称,以及特别常见的头部名称与值的组合;
- 维护一份相同的动态字典(Dynamic Table),可以动态地添加内容;
- 支持基于静态哈夫曼码表的哈夫曼编码(Huffman Coding);
http/2.0有什么缺点?
- tcp队头阻塞:
http/1.1在http层面的队头阻塞是http只能在一个tcp连接上串行请求应答,一个http响应后才能请求下一个。
http/2.0tcp层面是存在TCP层面的队头阻塞问题。TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据。
所以,一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。
-
TCP 和 TLS 的握手延迟
发起 HTTP 请求时,需要经过 TCP 三次握手和 TLS 四次握手(TLS 1.2)的过程,因此共需要 3 个 RTT 的时延才能发出请求数据。另外,TCP 由于具有「拥塞控制」的特性,所以刚建立连接的 TCP 会有个「慢启动」的过程,它会对 TCP 连接产生“减速”效果。
-
网络迁移需要重新连接
一个 TCP 连接是由四元组(源 IP 地址,源端口,目标 IP 地址,目标端口)确定的,这意味着如果 IP 地址或者端口变动了,就会导致需要 TCP 与 TLS 重新握手,这不利于移动设备切换网络的场景,比如 4G 网络环境切换成 WiFi。
http3.0有什么改进?
HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议直接改成了 UDP!
UDP 发送是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。大家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。
QUIC 有以下 3 个特点。
- 无队头阻塞
- 更快的连接建立
- 连接迁移
- 无队头阻塞
QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,一个Stream 可以认为就是一条 HTTP 请求。
QUIC 有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。
所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。
- 更快的连接建立
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手。
HTTP/3 在传输数据前虽然需要 QUIC 协议三次握手,但是这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。
但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是 QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商。
甚至,在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。
如下图右边部分,HTTP/3 当会话恢复时,有效负载数据与第一个数据包一起发送,可以做到 0-RTT(下图的右下角):
- 连接迁移
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。
那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
所以, QUIC 是一个在 UDP 之上的伪 TCP + TLS + HTTP/2 的多路复用的协议。
QUIC 是新协议,对于很多网络设备,根本不知道什么是 QUIC,只会当做 UDP,这样会出现新的问题,因为有的网络设备是会丢掉 UDP 包的,而 QUIC 是基于 UDP 实现的,那么如果网络设备无法识别这个是 QUIC 包,那么就会当作 UDP包,然后被丢弃。
HTTP Keep-Alive和TCP Keep-alive是一个东西吗?
HTTP 的 Keep-Alive 也叫 HTTP 长连接(感觉更像是保持TCP长链接),该功能是由「应用程序」实现的,可以使得用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,减少了 HTTP 短连接带来的多次 TCP 握手挥手的开销。核心思想是“复用”。
TCP 的 Keepalive 也叫 TCP 保活机制,该功能是由「内核」实现的,当客户端和服务端长达一定时间没有进行数据交互时,内核为了确保该连接是否还有效,就会发送探测报文,来检测对方是否还在线(检查对方是否是宕机导致长时间无数据交互),然后来决定是否要关闭该连接。核心思想是“检测”。
- 如果对方宕机(注意不是进程崩溃,进程崩溃后操作系统的网络协议栈在回收进程资源的时候,会发送 FIN 报文。而对方宕机是无法感知的,所以需要 TCP 保活机制来探测对方是否宕机),或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。
https
安全的超文本传输协议,披着SSL/TLS外衣的HTTP协议。HTTP 与 TCP 层之间加入了 SSL/TLS
协议的安全层。SSL是TLS的前身,现在的浏览器已经不支持SSL了,只支持TLS。
-
https比http最主要改进了3个点:
-
信息加密(TLS握手实现)。采用混合加密,混合使用对称加密和非对称加密,也就是用非对称加密来协商对称加密的会话密钥。保证了安全和效率。
-
在通信建立前采用非对称加密的方式交换「会话密钥」,后续就不再使用非对称加密。
-
在通信过程中全部使用对称加密的「会话密钥」的方式加密明文数据。
-
-
防内容篡改。摘要算法 + 数字签名:
- 摘要算法(哈希算法)得到哈希值。
- 数字签名:用私钥加密哈希值得到数字签名,将数字签名和内容一起发过去,就能保证哈希值和内容不被篡改。
-
身份校验。通过数字证书确保对方的身份:
- CA (数字证书认证机构)颁发数字证书,CA数字签名到数字证书上,并将服务器公钥放在数字证书中,只要证书可信,公钥就可信。
-
TLS握手过程
使用TLS证书来对通信双方进行认证。TCP3次握手之后会进行TLS的4次握手。
- TLS握手(秘钥"交换")过程如下,这一个秘钥"交换"过程也是身份认证的过程,(能做到不需要传送会话密钥就可以在双方生成回话密钥,安全性好)。(使用了混合加密:对称加密和非对称加密)
- ClientHello
客户端向服务器发起加密通信请求,客户端主要向服务器发送以下信息:
(1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。
(2)客户端生产的随机数(Client Random
),后面用于生成「会话秘钥」条件之一。
(3)客户端支持的加密套件列表,如 RSA 加密算法。
- SeverHello
服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello
。服务器回应的内容有如下内容:
(1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。
(2)服务器生产的随机数(Server Random
),也是后面用于生产「会话秘钥」条件之一。
(3)确认的加密套件列表,如 RSA 加密算法。
(4)服务器的数字证书。
- 客户端回应
客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,验证服务器的数字证书。
如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:
(1)一个随机数,也叫预主秘钥(pre-master key
)。该随机数被服务器公钥加密发给服务器。
(2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
(3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。
服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」。
- 服务器的最后回应
服务器收到客户端的第三个随机数(pre-master key
)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。
然后,向客户端发送最后的信息:
(1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
为什么需要TLS握手来非对称加密得到会话密钥?直接用公钥私钥来加密不就好了吗?
- 性能:非对称加密算法相较于对称加密算法在计算上更为复杂和耗时。因此,在建立起一个安全层之后,使用对称加密算法来加密数据传输可以大大提高性能。
- 安全性:可能会导致中间人攻击。通过TLS握手过程中的四次交换,可以确保建立起一个只有通信双方知道的会话密钥,从而增强安全性。
- 前向保密:通过TLS握手过程,即使服务器的长期私钥被泄露,之前的通信记录也无法被解密,因为每次会话都会生成新的会话密钥。
ARP协议的工作流程?
ARP工作在数据链路层。作用就是找到IP地址对应的MAC地址。
每个交换机都会有一个ARP缓存,里面有本局域网上各个主机的IP地址和MAC地址的映射表。如果能找到IP对应的MAC就直接发送到对应的主机上,如果找不到,这个交换机就会广播ARP 请求分组,对应IP的主机收到请求后就会发送 ARP 响应分组给交换机,并把自己的MAC也发给交换机,交换机将其写入映射表中。
ping的工作原理
ping基于ICMP协议。ICMP又是通过IP协议发送的,所以ICMP 报文是封装在 IP 报文里。
ICMP 包头格式
- 原生的ICMP:
-
Linux实际的icmp报文多了两个字段:标识符、序号
ICMP报文类型
ping流程
在规定的时间内,源主机如果没有收到 ICMP 的应答包,则说明目标主机不可达;如果接收到了 ICMP 回送响应消息,则说明目标主机可达。
- 源主机首先会构建一个 ICMP 回送请求消息(ICMP Echo Request Message)数据包。icmp类型为
8
:回送请求。序号最先是1,每发出一个请求数据包,序号会自动加1
。为了能够计算往返时间RTT
,还在报文的数据部分插入发送时间。
- 然后将这个icmp数据包和源目地址一起交给 IP 层。IP 层设置源地址和目的地址,协议字段设置为
1
表示是ICMP
协议,再加上一些其他控制信息,构建一个IP
数据包。
- 然后将ip数据包交给MAC层,加入
MAC
头。如果在本地 ARP 映射表中查找出 IP 地址 192.168.1.2 所对应的 MAC 地址,则可以直接使用;如果没有,则需要发送ARP
协议查询 MAC 地址,获得 MAC 地址后,由数据链路层构建一个数据帧,目的地址是 IP 层传过来的 MAC 地址,源地址则是本机的 MAC 地址;还要附加上一些控制信息,依据以太网的介质访问规则,将它们传送出去。
- 目标主机收到这个数据帧后,先检查它的目的 MAC 地址,并和本机的 MAC 地址对比,如符合,则接收,否则就丢弃。接收后就逐层分用。然后目标主机会构建一个 ICMP 回送响应消息数据包,icmp类型为
0
,序号为接收到的请求数据包中的序号,也放入发送时间,然后再发送给源主机。
- 源主机可以计算时间延迟。
上述是同一个局域网的情况。如果跨网段的话,还会涉及网关的转发、路由器的转发等等。但是对于 ICMP 的头来讲,是没什么影响的,有影响的是需要换 MAC 头里面的 MAC 地址。
断网了能ping通127.0.0.1吗?
能ping通。如果ping不通说明网络协议栈出问题了,要重新安装。
-
127.0.0.1
是回环地址。localhost
是域名,但默认等于127.0.0.1
-
ping
回环地址和ping
本机地址都一样的,走的是lo0 “假网卡”,都会经过网络层和数据链路层(网络接口层)等逻辑,最后在“快要到网卡前狠狠拐了个弯”,协议栈发现ip是本机的,所以再将数据加入到发送队列input_pkt_queue后触发软中断ksoftirqd 内核线程来接收数据,并不会挂到真网卡的ringbuffer上。
Socket网络通信
创建 socket 的系统调用:
int socket(int domain, int type, int protocal)
// domain 参数用来指定协议族,比如 AF_INET 用于 IPV4、AF_INET6 用于 IPV6、AF_LOCAL/AF_UNIX 用于本机;
//type 参数用来指定通信特性,比如 SOCK_STREAM 表示的是字节流,对应 TCP、SOCK_DGRAM 表示的是数据报,对应 UDP、SOCK_RAW 表示的是原始套接字;
// protocal 参数原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成,protocol 目前一般写成 0 即可;
TCP 的 socket 编程模型
- 服务端和客户端初始化
socket
,得到文件描述符; - 服务端调用
bind
,将绑定在 IP 地址和端口; - 服务端调用
listen
,进行监听; - 客户端调用
connect
,向服务器端的地址和端口发起连接请求; - 服务端在TCP三次握手之后调用
accept
(很容易坑人),返回用于传输的socket
的文件描述符; - 客户端调用
write
写入数据;服务端调用read
读取数据; - 客户端断开连接时,会调用
close
,那么服务端read
读取数据的时候,就会读取到了EOF
,待处理完数据后,服务端调用close
,表示连接关闭。
这里需要注意的是,服务端调用 accept
时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。
成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。
UDP 的 socket 编程模型
UDP 是没有连接的,所以不需要三次握手,也就不需要像 TCP 调用 listen 和 connect,但是 UDP 的交互仍然需要 IP 地址和端口号,因此也需要 bind。
对于 UDP 来说,不需要要维护连接,那么也就没有所谓的发送方和接收方,甚至都不存在客户端和服务端的概念,只要有一个 socket 多台机器就可以任意通信,因此每一个 UDP 的 socket 都需要 bind。
另外,每次通信时,调用 sendto 和 recvfrom,都要传入目标主机的 IP 地址和端口。
针对本地进程间通信的 socket 编程模型
本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。
WebSocket
特点
- 是一种在单个TCP连接上进行全双工通信的协议
- 握手后,两者之间就直接可以创建持久性的连接
- 与 HTTP 协议有着良好的兼容性:默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 开销:数据格式比较轻量,性能开销小,通信高效。
- 传输数据:可以发送文本,也可以发送二进制数据。
- 没有同源限制:客户端可以与任意服务器通信。
为什么需要websocket
- 与http不同,websocket是双向通信协议,可以由服务器发起,且具备服务器推送能力。虽然http2.0也支持了,但只支持推送请求相关的静态资源,无法推送指定的信息
- 即使http1.1有keep-alive保持长链接,但单向请求的特性决定了一个应答只能对应一个请求,如果需要获取实时数据需要长轮询,效率低非常占用资源。
websocket连接过程
- 客户端发起 HTTP 请求,经过 3 次握手后,建立起 TCP连接;HTTP 请求里存放 WebSocket 支持的版本号等信息请求协议转换,如:Upgrade、Connection、WebSocket-Version 等。
- 服务器收到客户端的握手请求后,同样采用 HTTP 协议应答,协议转换成了websocket;
- 客户端收到连接成功的消息后,借助 TCP 信道进行websocket全双工通信。
websocket 和 HTTP 有哪些不同
相同点:
- 都基于 TCP
- 都是可靠性传输协议。
- 都是应用层协议。
联系: WebSocket 在建立握手时,数据是通过 HTTP 传输的。但是建立之后,在真正传输时候是不需要 HTTP 协议的。
区别
- WebSocket 是双向通信协议,模拟 Socket 协议,可以双向发送或接受信息,而 HTTP 是单向的。
- WebSocket 是需要浏览器和服务器握手进行建立连接的,而 HTTP 是浏览器发起向服务器的连接。
- 虽然 HTTP/2 也具备服务器推送功能,但 HTTP/2 只能推送静态资源,无法推送指定的信息。
websocket 如何解决断线问题
websocket 一直没有传输消息,导致超时自动断开连接
在小于超时时间内发送心跳包,心跳检测步骤:
- 客户端每隔一个时间间隔发生一个探测包给服务器
- 客户端发包时启动一个超时定时器
- 服务器端接收到检测包,应该回应一个包
- 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
- 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
websocket 服务端或客户端异常中断
-
客户端需要断开连接,通过 onclose 关闭连接。
-
服务端再次上线时则需要清除之间存的数据,若不清除,会造成只要请求到服务端的都会被视为离线。