当前位置: 首页 > news >正文

身为前端,你不得不懂的一些HTTP知识(附赠3道面试题)

全文阅读大致3分钟,学习本文可以掌握以下知识:

  1. netcat、ss、lsof命令的使用
  2. tcp协议的三次握手和四次挥手
  3. udp协议的基本表现过程以及icmp报文发送的原因
  4. tcpdump、nc命令的使用
  5. 三道关于TCP/IP协议的面试题答案

1、从查看系统端口监听说起

在平时的开发中,出现listen EADDRINUSE: address already in use :::3000这种错误的频率很高,尤其在windows系统下,杀死个进程都杀不彻底。当遇到这种问题的时候,我们第一反应就是查看系统是哪个进程也在监听同样的端口。于是引出了我们要介绍的以下三个命令。

以下三个命令只在类UNI\系统上,系统之间的命令参数有一些细微差异,以系统提示为准,下面说的都是指在linux系统上*

1.1、netstat

netstat命令提供了一些关于网络连接的信息,可以用它来罗列所有监听的TCP端口或UDP端口,以及对应的套接字状态,如下:

netstat -tunlp
  • -t 显示TCP端口
  • -u 显示UDP端口
  • -n 显示IP地址而不是域名
  • -l 只显示正在监听的端口
  • -p 显示监听端口的进程ID

输出大致如下:

Active Internet connections (only servers)Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program nametcp        0      0 0.0.0.0:27017                         0.0.0.0:*               LISTEN      1889/mongodtcp        0      0 0.0.0.0:80                               0.0.0.0:*               LISTEN      786/nginx -g daemontcp        0      0 0.0.0.0:22                               0.0.0.0:*               LISTEN      884/sshdtcp        0      0 0.0.0.0:443                             0.0.0.0:*               LISTEN      786/nginx -g daemontcp6       0      0 :::8080                                      :::*                    LISTEN      23087/nodetcp6       0      0 :::10000                                               :::*                    LISTEN      4988/nodetcp6       0      0 :::80                   :::*                    LISTEN      786/nginx -g daemontcp6       0      0 :::8054                 :::*                    LISTEN      11915/nodeudp        0      0 172.16.179.237:123      0.0.0.0:*                           750/ntpdudp        0      0 127.0.0.1:123           0.0.0.0:*                           750/ntpdudp        0      0 0.0.0.0:123             0.0.0.0:*                           750/ntpdudp6       0      0 :::123                  :::*                                750/ntpd

 

netstat命令如今已经过时了,因为有新的命令替换-ss。

1.2、ss

ss命令没有了netstat的一些特性,不过它暴露出更多的TCP状态并且它更加轻量快速。该命令的选项和netstat大致一样,所以很容易上手:

ss -tunlp

输出大致如下:

Netid  State      Recv-Q Send-Q           Local Address:Port                          Peer Address:Portudp    UNCONN     0      0               172.16.179.237:123                                      *:*                   users:(("ntpd",pid=750,fd=19))udp    UNCONN     0      0                    127.0.0.1:123                                      *:*                   users:(("ntpd",pid=750,fd=18))udp    UNCONN     0      0                            *:123                                      *:*                   users:(("ntpd",pid=750,fd=17))udp    UNCONN     0      0                           :::123                                     :::*                   users:(("ntpd",pid=750,fd=16))tcp    LISTEN     0      128                          *:27017                                    *:*                   users:(("mongod",pid=1889,fd=7))tcp    LISTEN     0      128                          *:80                                       *:*                   users:(("nginx",pid=11173,fd=10),("nginx",pid=786,fd=10))tcp    LISTEN     0      128                          *:22                                       *:*                   users:(("sshd",pid=884,fd=3))tcp    LISTEN     0      128                          *:443                                      *:*                   users:(("nginx",pid=11173,fd=9),("nginx",pid=786,fd=9))tcp    LISTEN     0      128                         :::8080                                    :::*                   users:(("node",pid=23087,fd=10))tcp    LISTEN     0      128                         :::10000                                   :::*                   users:(("node",pid=4988,fd=10))tcp    LISTEN     0      128                         :::80                                      :::*                   users:(("nginx",pid=11173,fd=11),("nginx",pid=786,fd=11))tcp    LISTEN     0      128                         :::8054                                    :::*                   users:(("node",pid=11915,fd=12))

1.3、lsof

lsof是一个强大的命令行工具,提供了进程打开的文件的一些信息。因为在Linux,一切皆文件。所以一个打开的套接字也可以认为是一个文件。

罗列所有监听的TCP端口:

lsof -nP -iTCP -sTCP:LISTEN
  • -n 不要转换端口号为端口名称
  • -p 不要解析域名,显示其IP地址
  • -iTCP -sTCP:LISTEN 显示TCP状态为LISTEN的网络文件

输出如下:

COMMAND     PID        USER         FD          TYPE         DEVICE SIZE/OFF NODE NAMEnginx               786         root        9u            IPv4           13574      0t0  TCP *:443 (LISTEN)nginx               786         root       10u            IPv4           13575      0t0  TCP *:80 (LISTEN)nginx               786         root       11u            IPv6           13576      0t0  TCP *:80 (LISTEN)sshd                884         root        3u              IPv4           14458      0t0  TCP *:22 (LISTEN)mongod         1889         root       7u              IPv4           21178      0t0  TCP *:27017 (LISTEN)node               4988         root      10u            IPv6           40123      0t0  TCP *:10000 (LISTEN)nginx              11173 www-data    9u              IPv4           13574      0t0  TCP *:443 (LISTEN)nginx             11173 www-data   10u              IPv4           13575      0t0  TCP *:80 (LISTEN)nginx             11173 www-data   11u              IPv6           13576      0t0  TCP *:80 (LISTEN)node              11915     root         12u              IPv6         7200966      0t0  TCP *:8054 (LISTEN)node              23087     root         10u             IPv6         5497007      0t0  TCP *:8080 (LISTEN)

查找指定端口可以这样:lsof -nP -iTCP:8054 -sTCP:LISTEN

COMMAND   PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAMEnode    11915 root   12u  IPv6 7200966      0t0  TCP *:8054 (LISTEN)

好了,三个命令介绍到此为止。这个时候问一下大家一个问题:

上述过滤的状态都是LISTEN,那么TCP有多少种状态?状态与状态之间的变化是怎样的?你能从某个状态中就能推断出当前TCP连接处于什么阶段吗?

这个问题你自己心中有数的话,可以跳过下一小节

2、TCP状态的转移

下图是从wiki上引用的TCP状态转移图:

身为前端,你不得不懂的一些HTTP知识(附赠3道面试题)

图片来自:wiki

看着有点复杂,我们将其拆分成最热门的两个步骤:三次握手、四次挥手。后面附赠面试题答案哦~

2.1、三次握手

身为前端,你不得不懂的一些HTTP知识(附赠3道面试题)

图片来自:wiki

  • 客户端向服务器发送TCP连接请求数据包,客户端状态从CLOSED变为SYN_SENT,其中包含主机A的初始序列号seq(A)=x。(其中报文中同步标志位SYN=1,ACK=0,表示这是一个TCP连接请求数据报文;序号seq=x,表明传输数据时的第一个数据字节的序号是x);
  • 服务端收到请求后,会发回连接确认数据包。服务端状态从LISTEN变为SYN_RECEIVED,(其中确认报文段中,标识位SYN=1,ACK=1,表示这是一个TCP连接响应数据报文,并含服务端的初始序列号seq(B)=y,以及服务端对客户端初始序列号的确认号ack(B)=seq(A)+1=x+1)
  • 客户端收到服务端的确认报文后,还需作出Ack(此时这个数据包可以携带数据报文了),即发送一个序列号seq(A)=x+1;确认号为ack(A)=y+1的报文,此时客户端状态转为ESTABLISHED,服务端收到这个ACK后,状态也转为ESTABLISHED;

2.2、面试题:为什么需要三次握手?

此题需要从两个点回答:

  • 首要原因是为了解决客户端多次发起请求的问题,你想想看,在网络状况不好的情况下,客户端发起一个连接请求没收到响应的话会继续发送请求,如果最先发送的请求到服务端了,在用两次握手的前提下,服务端就会用这个已经过期的请求的序列号建立连接,而客户端却认为这个序列号是过期的,就会忽略掉,这样双方造成了很大的误解。而如果用三次握手的话,客户端就还有机会告诉服务端你的这个响应是过期的还是正常的,如果是过期的就可以发送RST消息告诉服务端断掉这个连接,如果不是的话,就返回ACK建立连接。
  • 第二个原因是为了同步双方的序列号,两次握手是做不到同步双方的序列号的。

关于第一个原因可以参考下图(截图自RFC793的3.4节):

身为前端,你不得不懂的一些HTTP知识(附赠3道面试题)

 

2.3、四次挥手

身为前端,你不得不懂的一些HTTP知识(附赠3道面试题)

 

  • 第一次挥手(FIN=1,seq=x) 假设客户端想要关闭连接,客户端发送一个FIN标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。发送完毕后,客户端进入FIN_WAIT_1状态
  • 第二次挥手(ACK=1,ACKnum=x+1) 服务器端确认客户端的FIN包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。 发送完毕后,服务器端进入CLOSE_WAIT状态,客户端接收到这个确认包之后,进入FIN_WAIT_2状态,等待服务器端关闭连接。
  • 第三次挥手(FIN=1,seq=y) 服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN置为1。发送完毕后,服务器端进入LAST_ACK状态,等待来自客户端的最后一个ACK。
  • 第四次挥手(ACK=1,ACKnum=y+1) 客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入TIME_WAIT状态,等待可能出现的要求重传的ACK包。 服务器端接收到这个确认包之后,关闭连接,进入CLOSED状态。 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的ACK,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入CLOSED状态。

为什么是2MSL?因为TCP/IP协议规定了超过这个时间的数据包都是会被废弃掉的,也就是一个数据包在网络中存活的最大时间

2.4、面试题:为什么需要四次挥手?

答:第二次和第三次无法整合起来变成三次挥手是因为服务端接收到FIN报文之后,手上可能还有数据需要发送给客户端,所以ACK和FIN不能同时发送。

3、UDP协议探析

探究UDP我们使用netcat这个工具,我们先用netcat来新建一个UDP服务器:

nc -u -l 0.0.0.0 3000

然后使用nc来新建一个客户端:

nc -u -p 3001 localhost 3000
  • -u 指定udp协议
  • -l 指定监听的端口和ip
  • -p 指定客户端的源端口

我们还需要使用tcpdump工具来dump数据包,或者可以使用wireshark来抓包:

─$ sudo tcpdump -ni lo0 'udp port 3001 or icmp'                                                       1 ↵Password:tcpdump: data link type PKTAPtcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on pktap, link-type PKTAP (Apple DLT_PKTAP), capture size 262144 bytes
  • -n 不用将Ip地址解析为域名
  • -i 指定抓包的网卡,我们这里指定抓的是回环口

因为UDP是无连接的,所以这会看不到任何的数据包

但是真的完全没有“连接”吗?其实并不是完全正确的,至少在客户端这边有这么一个连接存在,

我们使用上面提到的命令lsof:

╰─$ lsof -nP -iUDP | grep 3000nc        60738 linxiaowu    3u  IPv4 0x8ea59d14b13d38bf      0t0  UDP *:3000nc        60744 linxiaowu    6u  IPv4 0x8ea59d14b13d208f      0t0  UDP 127.0.0.1:3001->127.0.0.1:3000

从上面可以看出,客户端已经有了连接的概念,服务端还没有意识有这么一个连接存在。接着我们从客户端发送一条消息:hi,此时我们再使用lsof可以看到服务端也有此连接了:

❯ lsof -nP -iUDP | grep 3000nc        60738 linxiaowu    3u  IPv4 0x8ea59d14b13d38bf      0t0  UDP 127.0.0.1:3000->127.0.0.1:3001nc        60744 linxiaowu    6u  IPv4 0x8ea59d14b13d208f      0t0  UDP 127.0.0.1:3001->127.0.0.1:3000

所以从这里可以看到UDP的连接完全建立是在第一个数据包发送之后。tcpdump可以看到数据包:

17:18:17.419352 IP 127.0.0.1.3001 > 127.0.0.1.3000: UDP, length 3

这个时候我们关掉服务器,如果是TCP,那么会有一系列的协商报文发送出去,而UDP就不会,再看端口:

lsof -nP -iUDP | grep 3000nc        60744 linxiaowu    6u  IPv4 0x8ea59d14b13d208f      0t0  UDP 127.0.0.1:3001->127.0.0.1:3000

客户端此时并不知道服务器down掉了,接着我们从客户端发送消息hi?,此时netcat命令会自动退出,这个时候,它才知道连接断开了,并且我们发现有个ICMP报文从服务端发送出来:

身为前端,你不得不懂的一些HTTP知识(附赠3道面试题)

 

ICMP报文提示端口不可达,也就是服务端的端口关掉监听了。

根据TCP/IP协议的规定,如果对应的服务不可用,那么系统内核根据协议类型发送对应的响应报文,对于UDP应该发送一个“端口不可达”的ICMP报文,对于TCP应该发送一个TCP RST消息

所以UDP的连接断开会延迟到其中一方发送报文收到端口不可达的时候:

17:22:05.710012 IP 127.0.0.1.3001 > 127.0.0.1.3000: UDP, length 417:22:05.710047 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 3000 unreachable, length 36

3.1、面试题:为什么DNS使用UDP协议?

这个问题其实是个伪命题。使用udp协议是以前旧有的规范定义的,现在的RFC是将TCP协议也一起写进去的。因为以前的网络带宽不高,使用UDP协议会比TCP协议的数据包小很多,并且以前的DNS包体一般都很小,很少超过512字节的,但是现在的DNS支持Ipv6、https,包体也变大了,这个时候如果还是使用udp协议,很容易因为mtu之类的限制导致传输失败,因为tcp可以分包传输,所以对于大的包体,就大部分都是使用tcp协议。

相关文章:

  • web前端30个项目列表,学完即可上手做项目
  • 还在羡慕程序员工资高吗?看完这篇前端学习计划,你也可以拿高薪
  • 到达瓶颈的前端业务员思考总结,药到病除,方可突破
  • Nginx热升级流程,看这篇就够了
  • 全栈必经Nginx,不懂 Nginx 的前端不是好前端
  • Webpack实战(一):Webpack打包工具安装及参数配置
  • 前端解决跨域的九种方法
  • 2020年从基础到进阶,测试你有多了解 JavaScript,刷新你的知识!
  • 【面试需要】掌握JavaScript中的this,call,apply的原理
  • 基于ApiBoot的前后分离演示脚手架诞生了
  • Web的大趋所向:java+前端强强组合(不得分离!)
  • 2020年---最新「前端学习体系与前端概述」(面经必读)
  • 前端程序员进阶必备 | 教你配置免费又好用的抓包代理神器
  • 最全Html标签Meta介绍,全面总结,学HTML这一篇够了
  • 「译」5 个奇怪的只会在 JS 里才发生的趣事
  • @angular/forms 源码解析之双向绑定
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • iOS 系统授权开发
  • Javascript基础之Array数组API
  • mongo索引构建
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 记一次用 NodeJs 实现模拟登录的思路
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 异常机制详解
  • 正则与JS中的正则
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • Hibernate主键生成策略及选择
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • (1)SpringCloud 整合Python
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (10)STL算法之搜索(二) 二分查找
  • (11)MATLAB PCA+SVM 人脸识别
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (二)JAVA使用POI操作excel
  • (附源码)spring boot儿童教育管理系统 毕业设计 281442
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (七)理解angular中的module和injector,即依赖注入
  • (三)终结任务
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)ORM
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • .java 9 找不到符号_java找不到符号
  • .NET开发人员必知的八个网站
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹
  • [3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略
  • [C# 开发技巧]实现属于自己的截图工具
  • [C++]打开新世界的大门之C++入门
  • [CERC2017]Cumulative Code