网络基础知识
OSI7层协议模型
- 物理层 : 两台机器之间的bit流传输,负责将0101…的原始比特流转换为对应的高低电信号,数模转换与模数转换。单位是bit,设备有网卡网线
- 数据链路层: 在比特流传输的过程中,可能存在错传、数据不完整等情况,数据链路层定义了如何格式化数据,以及对物理介质的访问。通常还提供错误检测和纠正,以提高数据传输的可靠性。单位是帧,设备有交换机。
- 网络层: 将网络地址翻译成物理地址,决定数据路由路径。通过综合考虑发送优先权、网络拥塞程度、服务质量、可选路由的花费,来决定从一个网络节点A到另一个网络节点B的最佳路径。单位是数据包,设备有路由器,协议有IP。
- 传输层: 解决了网络间的数据传输问题,对大文件切割成段落(segment),指定序号方便接收端按正确的顺序重组数据。单位是数据片segment,协议有TCP、UDP。
- 会话层: 自动收发包、自动寻址,建立和管理应用程序之间的通讯。
- 表示层: 信息的语法语义解析,加密解密,压缩解压。
- 应用层: 规定了收发信息的请求/响应头、请求/响应体,协议有HTTP
OSI模型并没有提供具体的实现,只是一个具有指导意义的用于构建网络框架的参考模型。OSI的事实标准是TCP/IP4层协议。
TCP/IP 协议族
- 应用层: 对应[应用层、表示层、会话层] head -> HTTP数据
- 传输层:对应[传输层] head -> TCP首部 -> HTTP数据
- 网络层:对应[网络层] head -> IP首部 -> TCP首部 -> HTTP数据
- 链路层:对应[数据链路层、物理层] head -> 以太网首部 -> IP首部 -> TCP首部 -> HTTP数据
TCP
TCP ( transfer control protocol ) 简介
由于基于IP协议的网络层为了确保不独占通信线路,所以被设计成无状态、无连接的不安全的通信。仅讲上层传递的数据切分成更小的数据单元,通过路由发送到对端,不做收发顺序和数据完整性校验。
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。将应用层的数据流分割成报文段发送给目标节点的TCP层。 通过给每个报文段一个序号(sequence number)保证接收端按序处理,同时通过ACK确认送达,如果在RTT超时时间内未收到确认,则默认为丢失,重传。TCP在收发端通过奇偶校验和来验证数据完整性。
TCP 报文格式
-
源端口(source port) :
-
**目标端口(destination port) **: ip地址在网络层标识,通过ip和端口唯一标示两个通信的进程(也叫socket套接字)
-
序号(sequence number) : tcp传输的每个字节都按顺序编号,每个报文的序号 = 上一个报文的序号 + 上一个报文的数据长度
-
**确认号(acknowledgement number) **: 是期望收到对端下一个报文的第一个数据字节序号。例 B收到A的报文的序号是101,数据长度时200,则回复的确认号就是501。
-
偏移(offset) : 标明数据和起始处的距离
-
保留域(reserve): 保留域 默认置0
-
控制位(TCP flags) :
— URG :紧急指针标志
— ACK :确认序号标志
— PSH :push标志 指明接收端接收数据后应直接交由应用程序 而不是进入缓冲队列
— RST :重置连接标志 由于主机崩溃或其他原因出现错误的连接,或 拒绝非法报文段 非法连接请求
— SYN :同步序号,用于建立连接过程。
—— syn=1 and ack=0 表示没有使用捎带的确认域
—— syn=1 and ack=1 表示捎带一个确认域
— FIN :finish标志,用于释放链接。为1时表示发送方没有数据发送,关闭数据流。
-
滑动窗口(window) : 标识发送端接收端的缓存大小,以此控制发送端发送速率,达到流量控制的目的
-
校验和(checksum) :对报文头和报文数据以16位求和 在收发两端校验数据一致性
-
紧急指针(urgent pointer) :当URG位为1时生效 标明报文中紧急字段的字节数
-
**可选项(TCP options) **:
TCP的三次握手
当应用程序想通过TCP与另一个应用程序通信时,会发送一个通信请求,这个请求会被发送到一个确切的通信地址,在双方握手之后,TCP将在两个应用程序之间建立一个全双工的通信,这个通信将占用两个应用之间的通信线路,知道一方或双方关闭。
全双工(Full-Duplex Transmissions): 是指交换机在发送数据的同时也能够接收数据
,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的交换机都支持全双工。即A给B发送信息时,B也可以给A发送信息
三次握手流程
- 初始时,客户端和服务端处于关闭状态
- 服务端创建传输控制块,时刻保持准备接收其他连接请求,服务端进入监听状态。
- 客户端创建传输控制块,向服务器发出连接请求,控制位的**同步标志(SYN)**置为1,**序号(sequence number)置为x。此时客户端处于同步已发送(SYN-SENT)**状态。此次的tcp报文消耗一个序号,且不能携带传输数据。这就是tcp的第一次握手。
- 当服务端收到连接请求,且同意连接,则会返回一个确认报文。控制位的**同步标志(SYN)**置为1,**确认标志(ACK)**置为1,**确认号(acknowledge number)**为x+1(由于第一次握手时,客户端序号为x,且消耗了一个序号,所以将ack置为x+1,指示客户端下次发送数据的起始序号),**序号(sequence number)为任意正整数y,此时服务端处于同步已收到(SYN-RCVD)**状态。此报文同样消耗一个序号,且不能携带数据。这就是tcp的第二次握手。
- 当客户端收到服务端的确认报文后,再次发送一个确认报文,控制位的**确认标志(ACK)**置为1,确认号(acknowledge number)置为y+1(由于之前服务端响应的确认报文的序号为y,且响应消耗一个序号,所以客户端将ack置为y+1,指示服务端下次发送数据的起始位置),序号置为x+1。此时客户端处于已建立连接状态(ESTAB-LISHED),当服务端收到这个报文,也处于已建立连接状态,双方可以进行通信。这就是tcp的第三次握手。
为什么需要三次握手才能建立连接?
为了初始化序号(sequence number)的初始值,作为后续数据处理的序号,以保证上层处理数据时,不会因为网络原因而乱序。即TCP会使用这个序号来拼接数据。
首次握手的隐患—SYN超时
- 问题的原因分析
服务端收到客户端发送的连接请求SYN之后,会发送一个SYN-ACK响应给客户端,如果客户端拒收此响应,服务端会不断尝试直到超时,在linux环境中默认会发起5此请求,每次请求间隔为上次间隔的2倍。即一共会等待1+2+4+8+16+32=63秒。 - 可能会导致服务器收到SYN Flood攻击
如果客户端恶意发送SYN请求后下线,最终导致服务器SYN队列塞满,无法响应正常的请求。linux下提供了tcp_syncookies参数(根source ip和destination ip和timestamp构造),在SYN队列满了之后,服务端会发送syn cookie,正常连接的客户端请求会回发这个cookie,这样即使syn队列满的情况下,依然可以建立连接。
保活机制
如果客户端由于故障未收到服务端响应报文。服务端尝试向对方发送保活报文,如果未收到响应则继续发送。
直至达到配置的最大保活探测数,此时判定对端为不可达状态,中断连接。
TCP的4次挥手
4次挥手流程
- 第一次挥手:客户端发送FIN请求,结束标志置为1,序号u为最后一次发送数据序号+长度,用来关闭客户端到服务端的数据传送,此时客户端处于终止等待1(FIN-WAIT-1)状态。
- 第二次挥手:服务端接收到客户端的FIN请求,向客户端发送一个ACK确认响应,确认标志置为1,序号v,确认号为u+1。此时服务端进入关闭等待(CLOSE-WAIT)状态,同时通知上层应用,服务端在关闭等待状态可能还会发送一些数据。客户端收到服务端的ACK响应,进入终止等待2(FIN-WAIT-2)状态,此时依然可以继续接受服务端发送的数据。
- 第三次挥手:服务端发送FIN,通知客户端关闭数据传输,FIN=1,ACK=1,seq=w,ack=u+1。此时服务器进入最后确认(LAST-ACK)状态。
- 第四次挥手:客户端收到FIN后,进入时间等待(TIME_WAIT)状态,同时发送一个确认请求,ACK=1,seq=u+1,ack=w+1。 服务端收到请求后直接进入关闭状态。客户端在等待2MSL时间之后进入关闭状态。
为什么会有TIME_WATI状态
- 确保对端有足够时间收到ACK包
- 避免新旧连接混淆
MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为tcp报文(segment)是ip数据报(datagram)的数据部分,具体称谓请参见《数据在网络各层中的称呼》一文,而ip头中有一个TTL域,TTL是time to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个ip数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。
2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。
为什么需要四次回收才能断开连接
因为tcp连接是全双工的,所以要求通信两端必须各自发送FIN报文和ACK报文。
用户数据包UDP
UDP报文结构
- 源端口
- 目标端口
- 数据长度
- 校验和
UDP特点
- 面向非连接
- 不维护连接状态,支持广播相同的数据
- 数据包报头只有8字节,开销小
- 吞吐量只受限于数据生成速率 传输速率,机器性能影响
- 尽最大能力交付,但不保证可靠交付,不需要维持复杂的连接状态表
TCP和UDP区别
TCP | UDP |
---|---|
面向连接 | 无连接 |
可靠,三次握手确认 重传机制 | 不可靠 |
数据包有序,利用序号保证报文的序列交付 | 无序 |
传输速度慢 | 速度快 |
重量级 数据头较大 | 轻量级 数据头较小 |
TCP的滑动窗口
RTT和RTO
- RTT(Round Trip Time): 发送一个数据报到收到ACK响应所花费的时间。
- RTO(Retransmission Time Out): 重传间隔时间
HTTP(HyperText Transfer Protocol) 超文本传输协议
简介
HTTP属于应用层协议,基于请求和响应的无状态的协议,基于TCP的连接方式
主要特点
- C/S架构模式
浏览器作为客户端,通过URL像web服务器发起请求。web服务端根据请求,向客户端浏览器发送响应信息 - 简单快速
通过GET POST…等方法和只当URL - 灵活
可以发送任意类型数据,content type指定数据类型 - 无状态
客户端和服务端不会保存每次交互的信息,所以无法基于之前的信息处理当前的连接。
HTTP请求报文
HTTP响应报文
请求/响应的步骤
- 客户端和服务端建立TCP连接
- 客户端发起HTTP请求
- 服务端接受请求并返回HTTP响应
- 释放TCP连接
- 客户端解析响应内容
Q: 在浏览器地址栏输入URL,按下回车之后经历的流程
- DNS解析
浏览器逐层根据DNS缓存解析URL得到对应的IP地址,得到IP直接返回不再继续。
DNS缓存:浏览器缓存 - 系统缓存 - 路由器缓存 - IPS缓存 - 顶级域名缓存 - 根域名缓存 - 建立TCP连接
基于IP地址和端口号建立客户端与服务端的TCP连接。经过3次握手(1.SYN 2.SYN-ACK 3.ACK) - 发起HTTP请求
应用层: 建立HTTP请求报文
传输层:把报文分割成数据包segment,添加TCP首部信息(端口、序号、确认号、状态标志…)
网络层:将TCP数据包作为数据,封装成IP数据包,添加IP首部信息(IP版本、长度、状态标志、IP地址…) - 服务器处理请求并发回响应
- 断开TCP连接
4次挥手(1.FIN 2.ACK 3.FIN-ACK 4.ACK) - 客户端浏览器解析响应
HTTP状态码
类型
- 1xx: 请求已接收,正在处理
- 2xx: 请求成功处理完成
- 3xx: 请求重定向
- 4xx: 客户端错误
- 5xx: 服务端错误
常见的状态码
- 200 OK: 正产返回信息
- 400 Bad Request: 客户端请求语法错误,不能被服务端解析
- 401 Unauthorized: 请求未经授权
- 403 Forbidden: 请求已接收,服务器拒绝处理
- 404 Not Found: 请求资源不存在
- **500 Internal Server Error:**服务端内部错误
- 503 Server Unavailable: 服务器暂时无可用
Q:GET请求和POST请求的区别
GET | POST |
---|---|
请求参数追加在URL后面,以?开始每个参数之间以&分隔 | 请求参数放在请求报文的请求体中,以Key: Value 形式 |
浏览器对url长度有限制 | 无限制 |
对数据库操作符合一致性和安全性 | 不符合 |
请求可以被缓存,可以被保存为浏览器书签 | 不能 |
Cookie和Session
Cookie简介
- 由服务端创建,发送给客户端,客户端以文本的形式存储
- 客户端发送请求时,携带存储的cookie信息
- 服务端接收到请求,解析cookie,并据此产生对应的行为以响应本次请求
Cookie设置和发送的过程
Session简介
- 服务端机制,基于散列表保存会话信息
- 解析请求中是否包含session id,存在直接检索并操作。不存在则创建一个新的session,并关联响应的session id;
- 在响应中将session id发送给客户端保存
Session的实现方式
- 基于Cookie实现
- 通过URL回写
即服务端发送给浏览器页面的所有链接中,都附带jsession id参数。此时客户端点击任意链接,都会附带jsession id。
tomcat的session实现方式是一开始同时使用cookie和url回写,当发现客户端支持cookie则使用cookie,并停止使用url回写。如果cookie被禁用,则使用url回写。
Q:Cookie和Session的区别
Cookie | Session |
---|---|
存放在浏览器 | 存放在服务器 |
不安全 | 安全 |
占用客户端资源 | 占用服务端资源 |
HTTP和HTTPS
HTTPS(HyperText Transfer Protocol Security),是一种以计算机安全网络通信为目的的传输协议。通过在HTTP和TCP之间加入SSL或者TLS协议,保证通信的安全性。
SSL(Security Socket Layer),为网络通信提供安全及数据完整性的一种安全协议,是操作系统对外的API,在3.0版本后更名为TLS。SSL采用身份验证和数据加密保证网络通信的安全和数据完整性。
加密的方式
- 对称加密:加密解密使用同一个密钥,安全性低,效率高。
- 非对称加密:加密解密使用不同密钥,安全性高,效率低。
加密的过程
- 浏览器发送支持的加密算法
- 服务器选择某个支持的加密算法,以证书的形式发回浏览器
证书包含信息:颁发机构 有效期 公钥 所有者 签名 - 浏览器验证证书合法性,生成随机密码并使用证书中的公钥加密(作为后续通信过程中对称加密的公钥),同时使用约定好的加密算法加密握手信息,并通过哈希算法生成消息摘要(确保数据完整性),并将这些消息发回服务器。
- 服务器使用私钥解密出公钥密码,使用公钥密码解密握手信息,并验证哈希后的消息摘要。然后加密新的握手信息发送回浏览器。
- 浏览器解密响应信息,并验真,之后通过浏览器生成的密码作为公钥,使用对称加密进行数据交互。
Q:HTTP和HTTPS的区别
HTTPS | HTTP |
---|---|
需要证书 | 不需要 |
数据密文传输 | 明文传输 |
默认端口443 | 默认端口80 |
有状态 | 无状态 |
更安全 基于HTTP+SSL+认证+完整性保护 | 不安全 |
Socket
进程唯一标识,本地可以通过PID唯一标识,网络通信中可以通过IP地址和端口号唯一标识。
Socket是对TCP/IP协议的抽象,是操作系统对外开放的接口。
Socket通信流程
Socket相关面试题
TCP socket
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(65432);
while (true){
Socket socket = serverSocket.accept();
new LengthCalculatorTask(socket).start();
}
}
}
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 65432);
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write("hello world".getBytes());
byte[] buffer = new byte[1024];
int len;
len = in.read(buffer);
System.out.println(new String(buffer, 0, len));
in.close();
out.close();
socket.close();
}
}
public class LengthCalculatorTask extends Thread {
private Socket socket;
public LengthCalculatorTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
int len;
byte[] buffer = new byte[1024];
len = in.read(buffer);
String s = new String(buffer, 0, len);
System.out.println(s);
out.write(String.valueOf(s.length()).getBytes());
in.close();
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UDP Socket
public class UDPServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(65432);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
byte[] data = packet.getData();
String message = new String(data, 0, packet.getLength());
System.out.println(message);
byte[] responseContent = String.valueOf(message.length()).getBytes();
DatagramPacket responsePacket = new DatagramPacket(responseContent, responseContent.length, packet.getAddress(), packet.getPort());
socket.send(responsePacket);
}
}
public class UDPClient {
public static void main(String[] args) throws Exception {
InetAddress address = InetAddress.getByName("127.0.0.1");
DatagramSocket socket = new DatagramSocket();
byte[] buffer = "hello world".getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, 65432);
socket.send(packet);
byte[] data = new byte[1024];
DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
socket.receive(receivedPacket);
System.out.println(new String(receivedPacket.getData(), 0, receivedPacket.getLength()));
}
}