基础知识
OSI七层模型
第一层 物理层
- 主要定义了物理设备的标准,比如说网线的类型、光纤的接口类型
- 进行数模转换与模数转换
- 传输的数据叫比特
- 网卡就是工作在物理层
第二层 数据链路层
- 数据链路层定义了如何格式化数据以进行传输、控制如何对物理介质的访问
- 提供数据错误检测和纠正以确保数据传输的可靠性
- 数据链路层将比特数据组成了帧
- 交换机工作在数据链路层,对帧进行解码,并根据帧中包含的信息把数据发送到正确的接收方
第三层 网络层
- 主要功能是将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方
- 网络层通过综合考虑发送优先权、网络拥塞程度、服务质量以及可选路由的花费来决定从一个网络中结点A到另一个网络中结点B的最佳路径
- 路由器属于网络层
- 网络层的数据称为数据报
- IP协议也是在网络层
第四层 传输层
- 传输层解决了主机间的数据传输
- 传输协议同时进行流量控制或是基于接收方可接收数据的快慢程度规定适当的发送速率,除此之外,传输层按照网络能处理的最大尺寸将较长的数据包进行强制分割,例如,以太坊无法接收大于1500字节的数据包,发送方结点的传输层将数据分割成较小的数据片,同时对每一数据片安排一序列号以便数据到达接收方结点的传输层时能以正确的顺序重组,该过程即称为排序
- 传输层中的重要协议:TCP、UDP
第五层 会话层
第六层 表示层
- 解决不同系统之间通信语法的问题
- 在表示层数据将按照网络能理解的方案进行格式化,这种格式化也因所使用的网络的类型不同而不同
第七层 应用层
- 应用层规定发送方和接收方必须使用一个固定长度的消息头,消息头必须使用某种固定的组成,而且消息头里必须记录消息体的长度等等,以方便接收方能够正确的解析发送方发送的数据
- 应用层旨在让我们更方便的应用从网络中接收到的数据。
- 应用层中的重要协议:Http
TCP/IP概念层模型
说说TCP的三次握手
传输控制协议TCP简介
- 面向连接的、可靠的、基于字节流的传输层通信协议
- 将应用层的数据流分割成报文段并发送给目标节点的TCP层
- 数据包都有序号,对方收到则发送ACK确认,未收到则重传
- 使用奇偶校验和来检验数据在传输过程中是否有误(发送和接受时都要计算校验和)
TCP报文头
Source Port | Destination Port (源端口、目的端口)(4字节)
Sequence Number 报文段的序号(4字节)
Acknowledgment Number 期望收到对方报文的第一个字节的序号 (4字节)
Offset(数据偏移,因为头部有可选字段,长度不固定,它指出TCP报文段的数据距离TCP报文的起始处有多远) | Reserved(保留域,保留以后使用,目前都标为0) | TCP Flags | Window(滑动窗口的大小,用来告知发送端/接收端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制)
- TCP Flags
- URG:紧急指针标志
- ACK:确认序号标志
- PSH:push标志
- RST:重置连接标志
- SYN:同步序号,用于建立连接过程
- FIN:finish标志,用于释放连接
Checksum (检验和) 此校验和是对整个的TCP报文段包括TCP头部和TCP数据以十六位进行计算所得,由发送端进行计算和存储并由接收端进行验证 | Urgent Pointer(紧急指针)只有当TCP Flags中的URG为1 时才有效,指出本报文段中的紧急数据的字节数
TCP Options(variable、length、optional) 定义一些其他的可选参数
TCP三次握手流程
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接
- 第一次握手:建立连接时,客户端发送SYN包(
syn = j )到服务器,并进入SYN_SEND状态,等待服务器确认 - 第二次握手:服务器收到SYN包,必须确认客户的SYN(
ack = j + 1 ),同时自己也发送一个SYN包(syn = k ),即SYN + ACK包,此时服务器进入SYN_RECV状态; - 第三次握手:客户端收到服务器的SYN + ACK包,向服务器发送确认包ACK(
ack = k + 1 ),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
为什么需要三次握手才能建立起连接
通信的双方要互相通知对方自己的初始化的Sequence Number值,这个值要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序,即TCP会用这个序号来拼接数据,因此,在服务器回发它的Sequence Number(即第二次握手)之后,客户端还需要发送确认报文给服务器,告知服务器说客户端已经收到你的初始化Sequence Number了
首次握手的隐患
起因分析
Server 收到 Client 的 SYN ,回复 SYN-ACK的时候未收到ACK确认
Server 不断重试直至超时, Linux默认等到63秒才断开连接(1s + 2s + 4s + 8s + 16s + 32s,重试五次,五次过后等32s,如果还没有收到回应,那么就结束,断开连接)
- 针对SYN Flood的防护措施
- SYN队列满后,通过 tcp_syncookies参数回发SYN Cookie(源地址端口、目标地址端口和时间戳)
- 若为正常连接则Client会回发 SYN Cookie,直接建立接连
建立连接后,Client出现故障怎么办
- 保活机制
- 向对方发送保活探测报文,如果未收到响应则继续发送
- 尝试次数达到保活探测数仍未收到响应则中断连接
谈谈TCP的四次挥手
TCP 采用四次挥手来释放连接
- 第一次挥手:Client 发送一个 FIN ,用来关闭 Client 到 Server 的数据传送,Client进入 FIN_WAIT_1 状态;
- 第二次挥手:Server 收到FIN后,发送一个ACK给Client,确认序号为收到序号 + 1(与SYN相同,一个FIN占用一个序号),Server进入 CLOSE_WAIT状态;
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态;
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态(等待2MSL再CLOSED),接着发送一个ACK给Server,确认序号为收到序号 + 1,Server进入CLOSED状态,完成四次挥手。
为什么会有TIME_WAIT状态(为什么会再等待2MSL才关闭连接)
- 确保有足够的时间让对方收到ACK包
- 避免新旧连接混淆
为什么需要四次挥手才能断开连接
- 因为TCP是全双工的,发送方和接收方都需要FIN报文和ACK报文,也就是说,发送方和接收方各自需两次挥手
服务器出现大量CLOSED_WAIT状态的原因
对方关闭socket连接,我方忙于读或写,没有及时关闭连接
- 程序里有BUG,检查代码,特别是释放资源的代码
- 检查配置,特别是处理请求的线程配置
UDP
UDP的报文结构
Source Port 源端口 | Destination Port 目标端口
length 数据报长度 | Checksum 奇偶校验值
data octets…(optional) 用户数据
UDP的特点
- 面向非连接
- 不维护状态,支持同时向多个客户端传输相同的消息
- 数据包报头只有8个字节,额外开销较小
- 吞吐量只受限于数据生成速率、传输速率以及机器性能
- 尽最大努力交付,不保证可靠交付,不需要维持复杂的链接状态表
- 面向报文,不对应用程序提交的报文信息进行拆分或者合并
TCP和UDP的区别
- 面向连接 VS 无连接
- 可靠性:TCP较可靠,利用三次握手确认和重传机制提供了可靠行保证,而UDP则可能会丢失,不能确认到底有没有被接收
- 有序性:TCP利用序列号保证了消息报的顺序交付,到达时可能无序,但TCP最终会排序;而UDP不具备有序性
- 速度:TCP速度较慢,因为要创建连接,保证消息的可靠性和有序性,需要做额外的很多事情;而UDP则更适合对速度比较敏感的应用,比如说在线视频媒体,电视广播等
- 量级:TCP属于重量级,UDP属于轻量级,体现在源数据的头大小,TCP是20个字节,而UDP是8个字节
TCP的滑动窗口
HTTP
属于应用层,HTTP协议是基于请求和响应模式的无状态的应用层的协议,常基于TCP的连接方式
- 超文本传输协议HTTP主要特点
- 支持客户/服务器模式
- 简单快速
- 灵活:HTTP允许传输任意类型的数据对象,正在传输的类型由
<Content-Type> 加以标记 - 无连接:限制每次连接只处理一个请求;HTTP1.1起,默认使用长连接,即服务器需要等待一定时间后才能断开连接,以保证连接特性。HTTP1.1默认开启
keep-alive ,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点 - 无状态:协议对事务处理没有记忆能力,缺少状态意味着如果后续处理需要前段信息,则必须被重传,这样可能导致每次连接传送的数据量增大,另一方面,在服务器不需要先前信息时,应答较快
HTTP请求结构
HTTP响应结构
请求/响应的步骤
- 客户端连接到Web服务器
- 发送HTTP请求
- 服务器接受请求并返回HTTP响应
- 释放TCP连接:若连接模式为CLOESED,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若连接模式为
keep-alive ,则该连接会保持一段时间,在该时间内会继续接收请求 - 客户端浏览器解析HTML内容
在浏览器地址栏键入URL,按下回车之后经历的流程
- DNS解析
浏览器会依据URL逐层查询DNS服务器缓存,解析URL中的域名所对应的IP地址,DNS缓存从近到远依次是浏览器缓存、系统缓存、路由器缓存、IPS服务器缓存以及域名服务器缓存、顶级域名服务器缓存。从那个缓存找到对应的IP,则直接返回,不再查询后面的缓存
- TCP连接
找到IP地址之后,会根据IP地址和对应端口与服务器建立TCP连接(三次握手)
- 发送HTTP请求
之后,浏览器会发出读取文件的HTTP请求,该请求将发送给服务器
- 服务器处理请求并返回HTTP报文
紧接着,服务器对浏览器请求做出响应并把对应的带有HTML文本的HTTP响应报文发送给浏览器
- 浏览器解析渲染页面
浏览器收到了HTML并在显示窗口内渲染它
- 连接结束
说说常见的HTTP状态码
五种可能的取值
- 1XX:指示信息–表示请求已接收,继续处理
- 2XX:成功–表示请求已被成功接收、理解、接受
- 3XX:重定向–要完成请求必须进行更进一步的操作
- 4XX:客户端错误–请求有语法错误或请求无法实现
- 5XX:服务端错误–服务端未能实现合法的请求
常见状态码
- 200 OK:正常返回信息
- 400 Bad Request:客户端请求有语法错误,不能被服务器所理解
- 401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
- 403 Forbidden:服务器收到请求,但是拒绝提供服务
- 404 Not Found:请求资源不存在;eg:输入了错误的URL
- 500 Internal Server Error:服务器发生不可预期的错误
- 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
GET请求和POST请求的区别
从三个层面来解答
- HTTP报文层面:GET将请求信息放在URL后面,请求信息和URL之间以
? 隔开,请求信息的格式为键值对;POST将请求信息放在报文体中,想获得请求信息,必须解析报文;(安全性上两者并没有太多的区别,具体解决传输过程中的安全问题,还要靠HTTPS),由于GET中的请求信息放置在URL中,因此是有长度限制的(URL本身没有长度限制,但浏览器会对URL长度做出限制),POST中的请求信息是放置在报文体中的,因此对数据长度是没有限制的。 - 数据库层面:GET符合幂等性和安全性,POST不符合(GET请求是做查询操作的,因此不会改变数据库中原有的数据,而POST请求会往数据库中提交数据,因此不符合幂等性和安全性)
- 幂等性:对数据库的一次操作或多次操作获得的结果是一致的
- 安全性:对数据库的操作没有改变数据库中的数据
- 其他层面:GET可以被缓存、被存储,而POST不行
- GET请求会保存在浏览器的浏览记录中,以GET请求的URL能够保存为浏览器书签,而POST不具备上述功能,缓存也是GET请求被广泛应用的根本,在现代网络上,每天产生的请求数目是巨大的,并且其中绝大部分请求均为只读请求,如果所有这些请求都要交由Web服务器处理这无疑是巨大的资源浪费,因为GET请求是幂等的、安全的,因此绝大部分GET请求,通常超过90%都直接被CDN缓存了,这能大大减少Web服务器的负担,而POST是非幂等的、有副作用的操作,所以必须交由Web服务器处理
Cookie和Session的区别
因为HTTP是无状态的,也就意味着我们每次访问有登陆需求的页面时,都要不厌其烦的输入账号和密码,现实生活中并没有出现这样的情况,因为我们引入了某些机制,让HTTP"具备’'了状态,其中的两个便是Cookie和Session
Cookie简介
Cookie技术是客户端的解决方案
- 是由服务器发给客户端的特殊信息(由服务器发回客户端时Cookie存放在HTTP响应头中),以文本的形式存放在客户端
- 客户端再次请求的时候,会把Cookie回发(存放在HTTP请求头中)
- 服务器接收到后,会解析Cookie生成与客户端相对应的内容.
Cookie的设置以及发送过程
Session简介
- 服务器端的机制,在服务器上保存的信息(类似于散列表的结构来保存)
- 解析客户端请求并操作session id,按需保存状态信息
- 当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识,称为session id,如果已包含一个session id,则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用,如果检索不到,就新建一个,如果客户端请求不包含session id,则为此客户端创建一个session,并且生成一个与此session相关的session id,此id将会在本次响应中回发给客户端进行保存
Session的实现方式
Cookie和Session的区别
- Cookie数据存放在客户的浏览器上,Session数据存放在服务器上
- Session相对于Cookie更安全
- 若考虑减轻服务器负担,应当使用Cookie
HTTP和HTTPS的区别
HTTPS简介
- HTTPS是一种以计算机网络安全通信为目的的传输协议
SSL (Security Sockets Layer,安全套接层)
- 为网络通信推提供安全及数据完整性的一种安全协议
- 是操作系统对外的API,SSL3.0后更名为TLS(SSL位于TCP与各应用层之间)
- 采用身份验证和数据加密保证网络通信的安全和数据的完整性
加密的方式
- 对称加密:加密和解密都使用同一个密钥
- 非对称加密:加密使用的密钥和解密使用的密钥是不相同的(公钥和私钥)
- 哈希算法:将任意长度的信息转换为固定长度的值,算法不可逆
- 数字签名:证明,某个消息或者文件是某人发出/认同的
在实际的执行中,人们发现,仅使用其中的某种加密方式并不能满足生产要求,要么非对称加密性能过低,要么对称密钥容易泄露,以此,HTTPS使用的是证书配合各种加密手段的方式做出了一套相对安全的组合拳
HTTPS数据传输流程
HTTPS在进行数据传输之前,会与网站服务器和Web浏览器进行一次握手,在握手时,确定双方的加密密码信息,具体流程如下
- 浏览器将支持的加密算法信息发送给服务器
- 服务器选择一套浏览器支持的加密算法,以证书的形式回发浏览器
- 浏览器验证证书合法性,并结合证书公钥加密信息发送给服务器 (如果证书受到浏览器信任,则在浏览器地址栏会有标志显示,否则,就会显示不受信的标识)
- 服务器使用私钥解密信息,验证哈希,加密响应消息回发浏览器
- 浏览器解密响应消息,并对消息进行验真,之后进行加密交互数据
HTTP和HTTPS的区别
- HTTPS需要到CA申请证书,HTTP不需要
- HTTPS密文传输,HTTP明文传输
- 连接方式不同,HTTPS默认使用443端口,HTTP使用80端口
- HTTPS = HTTP + 加密 + 认证 + 完整性保护,较HTTP安全;(SSL是有状态的)
HTTPS真的很安全吗
那到未必
- 浏览器默认填充
http:// ,请求需要进行跳转,有被劫持的风险 - 可以使用HSTS(HTTP Strict Transport Security)优化,(目前正在推行中,并未成为主流)
Socket
Socket简介
IP地址 + 协议 + 端口号可以唯一标识网络中的一个进程,能够唯一标识网络中进程后,就可以利用Socket进行通信了
Socket通信流程
Socket相关的面试题
TCP实现
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(65000);
while (true) {
Socket socket = ss.accept();
new LengthCalculator(socket).start();
}
}
}
public class LengthCalculator extends Thread{
private Socket socket;
public LengthCalculator(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
int ch = 0;
byte[] buff = new byte[1024];
ch = is.read(buff);
String content = new String(buff, 0, ch);
System.out.println(content);
os.write(String.valueOf(content.length()).getBytes());
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 65000);
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
os.write(new String("hello world").getBytes());
int ch = 0;
byte[] buff = new byte[1024];
ch = is.read(buff);
String content = new String(buff, 0, ch);
System.out.println(content);
is.close();
os.close();
socket.close();
}
}
UDP实现
public class UDPServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(65001);
byte[] buff = new byte[100];
DatagramPacket packet = new DatagramPacket(buff, buff.length);
socket.receive(packet);
byte[] data = packet.getData();
String content = new String(data, 0, packet.getLength());
System.out.println(content);
byte[] sendedContent = String.valueOf(content.length()).getBytes();
DatagramPacket packetToClient = new DatagramPacket(sendedContent,
sendedContent.length, packet.getAddress(), packet.getPort());
socket.send(packetToClient);
}
}
public class UDPClient {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
byte[] buff = "Hello world".getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
DatagramPacket packet = new DatagramPacket(buff, buff.length, address, 65001);
socket.send(packet);
byte[] data = new byte[100];
DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
socket.receive(receivedPacket);
String content = new String(receivedPacket.getData(), 0,
receivedPacket.getLength());
System.out.println(content);
}
}
|