1 网络基础知识
1.1 OSI七层参考模型
全称:开放系统互连参考模型(Open System Interconnection Reference Model,OSI/RM)
补充:为什么要有一个个层:
物理层:010101比特流,设备之间原始数据的传输,数模转换(发送端)和模数转换(接收端)
-》传输过程可能出现错码和误码?-------又问题,引入下一层:数据链路层
数据链路层:将原始比特流转换成逻辑传输符号,提供纠错编码,格式变为帧
-》出现多个网络节点,应该选择哪个节点?
网络层:通过路由选择,选择最佳路径,数据格式是IP数据包
-》某次连接传输的数据可能很大,会出现错传、漏传
传输层:将数据拆分成段,提供维护连接的机制(流量控制、拥塞控制)
-》对用户来说每次都要使用传输层拆分,再使用网络层寻路很麻烦
会话层:提供建立连接和维护连接的机制,方便用户使用
-》不同操作系统之间的网络传输如何互通?linux/windows
表示层:提供编码转换、加解密、解压缩
-》接收方不知道发送发发送的是什么东西、数据有多长?
应用层:规定发送方和接收方必须使用固定长度的消息头,并且消息头固定格式,并标明长度等信息。
1.物理层
设备之间原始数据的传输,数据格式:比特流。
2.数据链路层
原始比特流转换成逻辑传输数据,建立相邻结点之间的数据链路,mac地址寻址,数据格式:帧
3.网络层
将数据链路层提供的帧组成数据包,通过路由算法提供最佳传输路径,数据格式:IP数据包
数据链路层解决同一网络节点间的传输,网络层解决不同子网间的传输。(IP是网络层)
4.传输层
数据格式:也称作数据包(packets)(TCP的数据单元称为段(segments)UDP的数据单元称为数据报(datagrams))
拆分数据包,提供端对端不同主机用户进程数据传输,提供可靠或不可靠传输及流量控制,是连接通信子网和资源子网的桥梁。
5.会话层
不参与具体的传输,提供建立和维护应用间通信的机制。
6.表示层
数据的表示方式(格式处理及编码转换)和特定功能(加解密,解压缩)的实现
7.应用层
为用户提供服务,为操作系统或者应用程序提供访问网络的的接口
应用层协议的代表包括:Telnet、FTP、HTTP、SNMP等
1.2 TCP/IP四层结构
1.链路层:
包括物理层和数据链路层,是通过MAC地址传输数据的
2.网络层:
包括多种协议
IP协议:通过路由选择将数据封装后交给链路层。
ICMP协议(Internet Control Message Protocol):用于主机和路由器直接传递控制消息,常用的ping就是用这个协议。
ARP协议(Address Resolution Protocol):是正向地址解析协议,通过IP查找mac地址。
RARP协议:是反向地址解析协议,通过mac地址查找IP。
3.传输层
TCP协议:传输控制协议,是面向连接的、可靠的、基于IP的传输层协议
UDP协议:用户数据报协议,提供面向事物的、简单、不可靠的信息传输协议
4.应用层
FTP协议:文件传输协议,用于文件的上传下载。
Telnet协议:用户远程登录服务。
DNS(Domain Name System)协议:域名解析协议,提供域名到IP的解析。
SMTP(Simple Mail Transfer Protocol)协议:简单的邮件传送协议,用于控制信件的发送中转。
NFS协议:网络文件系统,用于不同主机间文件共享。
HTTP协议:超文本传输协议,用于实现互联网访问功能。
1.3 OSI七层协议和TCP/IP协议之间的关系
链路层 | 物理层 数据链路层 |
网络层 | 网络层 |
传输层 | 传输层 |
应用层 | 会话层 表示层 应用层 |
- OSI更注重通信模块必要的功能是什么
- TCP/IP更注重实现协议应该开发哪种程序
2. TCP的三次握手和四次挥手
2.1 TCP报文头结构定义
其中:
seq是32位序号,ack是32位确认序号
- URG:紧急指针标志
- ACK:确认序号标志
- PSH:push标志
- RST:重置连接标志
- SYN:同步序号,用于建立连接过程
- FIN:finsh标志,用于释放连接
2.2 TCP三次握手
初始时,服务器端处于LISTEN监听状态
第一次握手:客户端发送SYN包到服务器(SYN标志位为1,seq=x)并进入SYN-SENT状态,等待服务器确认;
第二次握手:服务端确认客户端的SYN包(ACK标志位为1,ack=x+1),同时自己也发送一个SYN包(SYN标志位为1,seq=y),并且进入到SYN-RCVD状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送一个确认包(ACK标志位为1,ack=y+1),此包发送完毕,客户端和服务器端进入ESTABLISHED状态,完成三次握手。
为什么需要三次握手才能建立起链接
为了初始化Sequence Number的初始值
首次握手的隐患--SYN超时
可能出现的问题及解决方案:
问题起因 :
- Server收到Client的SYN,回复SYN+ACK的时候未收到ACK确认。
- 则Server不断重试直至超时,Linux默认等待63秒(1+2+4+8+16+32)才断开连接。
SYN超时可能会带来恶意代码攻击,应对SYN Flood的防护措施是:
- SYN队列满后,通过tcp_syncookies参数回发SYN Cookie
- 若为正常连接则Client会回发SYN Cookie,直接建立连接。
建立连接后,Client出现故障怎么办?
保活机制:
- 向对方发送保活探测报文,如果未收到响应则继续发送,
- 尝试次数达到保活探测次数仍未收到响应则中断连接
2.3 TCP的四次挥手
第一次挥手:客户端发送一个FIN包(FIN=1,seq=u),用来关闭客户端到服务端的数据传送,之后客户端进入FIN_WAIT-1状态;
第二次挥手:服务端接收到FIN后,发送一个ACK给客户端,确认序号ack为收到序号seq+1(与SYN相同,一个FIN占用一个序号),服务端进入到CLOSED_WAIT状态,客户端进入到FIN-WAIT-2状态;
第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,并且服务端进入LAST_ACK状态;
第四次挥手:客户端收到FIN后,客户端进入TIME_WAIT状态,接着发送一个ACK给服务端,确认序号ack为收到序号seq+1.紧接着服务器端进入CLOSED状态,完成四次挥手
为什么会有TIME_WAIT状态?
1.确保有足够的时间让对方收到ACK包
2.避免新旧混淆
( 假设最终的ACK丢失,服务端将重发FIN,客户端必须维护TCP状态信息以便可以重发最终的ACK,否则会发送RST,结果主机认为发生错误。TCP实现必须可靠地终止连接的两个方向(全双工关闭),客户端必须进入 TIME_WAIT 状态,因为客户端可能面临重发最终ACK的情形。)
注:TIME_WAIT:
表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN WAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)
为什么需要四次挥手才能断开连接
因为全双工,发送方和接收方都需要FIN报文和ACK报文
服务器出现大量CLOSE_WAIT状态的原因
对方关闭socket连接,我方忙于读或写,没有及时关闭连接
- 检查代码,特别是释放资源的代码
- 检查配置,特别是处理请求的线程配置
3 UDP
3.1 UDP报文头结构
英文:
中文:
概括:
- 由16位源端口号
- 16位目的端口号
- 16位UDP长度
- 16为UDP检验和
- 以及 数据 组成。
3.2 UDP特点:
1. 面向非连接的
2.不维护连接状态,支持同时向多个客户端传输相同的消息
3.数据包报头只有8个字节,额外开销比较小
4.吞吐量只受限于生成速率、传输速率以及及其性能
5.尽最大努力交付,不保证可靠交付,不需要维持复杂的链接状态表
6.面向报文,不对应用程序提交的报文信息进行拆分或者合并
关键词:非连接、多发、报头小、吞吐量、最大努力交付、面向报文
3.3 TCP和UDP的区别
1.面向连接VS非连接
2.可靠性
3.有序性
4.量级
5.速度
4 TCP的滑动窗口
4.1 基本概念:
4.1.1 RTT和RTO:
RTT:(Round Trip Time)发送一个数据包收到对应的ACK,所花费的时间
RTO:(Retransmission TimeOut)重传时间间隔
4.2 TCP使用滑动窗口做流量控制与乱序重排的目的
- 保证TCP的可靠性
- 保证TCP的流控特性
->TCP报文头中的Window即指滑动窗口
4.3 滑动窗口数据的计算:
以下均为从左到右序号逐渐递增
发送端:
LastByteAcked:已经发送并且收到回复的最后一个序号位置
LastByteSent:已经发送但还没有时候到回复的最后一个序号位置
LastByteWritter:已经准备好的但还没发送的最后一个序号位置
接收端:
LastByteRead:收到并已经回复的最后一个位置
NextByteExcepted:收到的连续最大的sequence的位置(已经收到但还没有发送回复ACK)
LastByteRcvd:已收到的最后一个字节的位置
计算:
AdvertisedWindow = MaxRcvBuffer - (LastByteRcvd - LastByteRead)
EffectiveWindow = AdvertisedWindow - (LastByteSendt - LastByteAcked)其中:AdvertisedWindow(接收方还可以处理的量)、MaxRcvBuffer(接收方能接收的最大数据量)、
EffectiveWindow(窗口内剩余可发送数据大小)
4.4 滑动窗口的基本原理
4.4.1 TCP发送方:
包含四个状态:发送并收到回复、发送还没收到回复、还没发送但是可发送、还没发送并且不可发送
(1)假设起始状态:[32~45][46~51]共同构成了滑动窗口,其中前一部分是已发送的还没收到确认的,后一部分是可发送还没发的。
(2)假设发送的片段[32~36],发送端收到接收端的ACK回复,那么滑动窗口即可向右移动5个字节
(3)此时可以将[46~51]发送出去,并且[52~56]由不可发送变为可发送,整个滑动窗口向右移动了5个字节的位置
4.4.2 TCP接收方:
包含三个状态:收到并且已回复、还没收到并且允许发送、还没收到也不允许发送
注:
1. 由于ACK直接由TCP栈回复,默认没有应用延迟,不存在已接收但未回复的状态
2.未接收但是可以接收的这段空间称为接收窗口
3。应用会根据自身处理能力的变化,通过本端TCP接收窗口大小的控制来实现对端的发送窗口进行流量限制
5 HTTP
HTTP:Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议;HTTP是基于TCP/IP通信协议来传递数据的。
5.1 主要特点:
HTTP是应用层协议
1.支持服务端/客户端两种模式
2.简单快速:
客户向服务器请求服务时,只需传送请求方法和路径。请求的方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的服务规模小,因而通信速度很快。
3.灵活
允许传输任意类型的数据对象。
4.无连接
限制每次连接只处理一个请求
5.无状态
对事物处理没有记忆能力
5.2 报文结构
5.3 请求响应的步骤
1.客户端连接到服务端
2.发送HTTP请求
3.服务器接收请求并返回HTTP响应
4.释放TCP连接
5.客户端浏览器解析HTTP内容
在浏览器地址栏中键入URL,按下回车之后经历的流程:
1.DNS解析
2.TCP连接
3.客户端发送HTTP请求
4.服务端接收HTTP请求并返回HTTP报文
5.浏览器解析渲染页面
6.连接结束
5.4 HTTP状态码:
5.4.1 五种可能的取值:
1XX:指示信息,表示已经收到请求,继续处理
2XX:成功--表示请求已经被成功接收、理解、接受
3XX:重定向--要完成请求必须进行进一步操作
4XX:客户端错误--请求有语法错误或者无法实现
5XX:服务端错误--服务器未能实现合法的请求
5.4.2 常见的状态码:
200 OK:正常返回信息
400 Bad Request : 客户端请求有语法错误,不能被服务器所理解
401 Unauthorized : 请求未经授权(这个状态代码必须和WW-Authenticate报头域一起使用)
403 Forbidden : 服务器收到请求,但是拒绝提供服务
404 Not Found : 请求资源不存在,eg,输入了错误的URL
500 Internal Server Error : 服务器发生不可预期的错误
503 Server Unavaliable : 服务器当前不能处理客户端的请求
5.5 Get请求和POST请求的区别
三个层面:
- HTTP报文层面:GET将请求放在URL,POST放在报文体中
- 数据库层面:GET符合幂等性和安全性,POST不符合
- 其他层面:GET可以被缓存、被存储,POST不行
注:HTTP 幂等方法,是指无论调用多少次都不会有不同结果的 HTTP 方法。不管你调用一次,还是调用一百次,一千次,结果都是相同的。
5.6 Cookie和Session的区别
1.Cookie是什么?
(1)是由服务器发给客户端的特殊信息,以文本的形式存放在客户端
(2)客户端再次请求的时候,会把Cookie回发
(3)服务器接收到后,会解析Cookie生成与客户端相对应的内容
Cookie的发送过程如图:
2.Session是什么?
(1)服务器端的机制,在服务器上保存的信息
(2)解析客户端请求并操作session id,按需保存状态信息
Session的实现方式
(1)使用Cookie来实现
(2)使用URL回写来实现
Cookie和Seesion的区别
(1)Cookie数据存放在客户的浏览器上,Session数据存放在服务器上
(2)Session相对于Cookie更安全
(3)若考虑减轻服务器负担,应该使用Cookie
6 HTTP和HTTPS
6.1 简介
HTTP:Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议;HTTP是基于TCP/IP通信协议来传递数据的。
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,你也可以说:HTTPS = HTTP + SSL
6.2 HTTP和HTTPS的区别:
1.HTTP默认端口为80,HTTPS默认端口为43;
2.HTTP不安全,HTTPS安全;(HTTPS=HTTP+加密+认证+完整性保护)
3.HTTP明文传输,HTTPS密文传输;
4.HTTP无需证书,HTTPS需要CA申请证书
5.HTTP的URL以http://开头,HTTPS的URL以https://开头
6.在OSI网络模型中,HTTP工作与应用层,而HTTPS工作在传输层
7. HTTPS = HTTP + 加密 + 认证 + 完整性保护,比HTTP安全
6.3 SSL是什么?
SSL(Security Sockets Layer)
1.为网络通信提供安全及数据完整性的一种安全协议
2.是操作系统对外的API,SSL3.0后更名为TLS
3.采用身份验证和数据加密保证网络通信的安全和数据的完整性
加密的方式
- 对称加密:加密和解密都使用同一个密钥
- 非对称加密:加密使用的密钥和解密使用的密钥是不相同的
- 哈希算法:将任意长度的信息转换为固定长度的值,算法不可逆
- 数字签名:证明某个消息或者文件是某人发出/认同的
6.4 HTTPS数据传输流程
1.浏览器将支持的加密算法信息发送给服务器
2.服务器选择一套浏览器支持的加密算法,以证书的形式回发浏览器
3.浏览器验证证书合法性,并结合证书公钥加密信息发送给浏览器
4.服务器使用私钥解密信息,验证哈希,加密响应消息回发浏览器
5.浏览器解密响应消息,并对消息进行验证,之后进行加密交互数据
6.5 HTTPS真的安全吗?
1.浏览器默认填充http://,请求需要进行跳转,有被劫持的风险
2.可以使用HSTS(HTTP Strict Transport Security)优化
7 Socket
7.1 简介
Socket(套接字)是TCP/IP协议的抽象,是操作系统对外开放的接口
7.2 Socket通信流程
server端
- 1.socket() 创建socket
- 2.bind() 绑定socket和端口号
- 3.listen() 监听该端口号
- 4.accept() 接收来自客户端的连接请求
- 5.recd() 从socket中读取字符
- 6.close() 关闭socket
client端
- 1.socket() 创建socket
- 2.connect() 连接指定计算机的端口
- 3.send() 向socket中写入指定信息
- 4.close() 关闭socket
7.3 常见面试题:
1.编写一个简易的web server,有客户端与服务器端,客户向服务器发送一个字符串,服务器收到该字符串后将其打印到命令行上,然后向客户端返回该字符串的长度,最后,客户端输出服务端返回的该字符串的长度,分别用TCP和UDP两种方式去实现。
TCP实现如下
(1)服务器端:TCPServer
package IMUHERO;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws Exception {
//创建socket,并将socket绑定到65000端口
ServerSocket ss = new ServerSocket(65000);
//死循环,使得socket一直等待并处理客户端发送过来的请求
while (true) {
//监听65000端口,直到客户端返回连接信息后才返回
Socket socket = ss.accept();//侦听并接受到此套接字的连接
//获取客户端的请求信息后,执行相关业务逻辑
new LengthCalculator(socket).start();
}
}
}
(2) 客户端:TCPClient
package IMUHERO;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws Exception {
//创建socket,并指定连接的是本机的端口号为65000的服务器socket
Socket socket = new Socket("127.0.0.1", 65000);
//获取输出流
OutputStream os = socket.getOutputStream();
//获取输入流
InputStream is = socket.getInputStream();
//将要传递给server的字符串参数转换成byte数组,并将数组写入到输出流中
os.write(new String("hello world").getBytes());
int ch = 0;
byte[] buff = new byte[1024];
//buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
ch = is.read(buff);
/**
* API详解:read(byte[] b) :
* 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数
**/
//将接收流的byte数组转换成字符串,这里是从服务端回发回来的字符串参数的长度
String content = new String(buff, 0, ch);
/**
* API详解:String shuzu=new String(byt, 0, len);
* new String(bytes, offset, length)
* bytes为要解译的字符串;
* offset为要解译的第一个索引,比如从0开始就是从字符串bytes的第一个字符开始;
* length为要解译的字符串bytes的长度
* **/
System.out.println(content);
//不要忘记关闭输入输出流以及socket
is.close();
os.close();
socket.close();
}
}
(3)辅助函数:
package IMUHERO;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class LengthCalculator extends Thread {
//以socket为成员变量
private Socket socket;
public LengthCalculator(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//获取socket的输出流
OutputStream os = socket.getOutputStream();
//获取socket的输入流
InputStream is = socket.getInputStream();
int ch = 0;
byte[] buff = new byte[1024];
//buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
ch = is.read(buff);//API文档见客户端代码
//将接收流的byte数组转换成字符串,这里获取的内容是客户端发送过来的字符串参数
String content = new String(buff, 0, ch);//API文档见客户端代码
System.out.println(content);
//往输出流里写入获得的字符串的长度,回发给客户端
os.write(String.valueOf(content.length()).getBytes());
//不要忘记关闭输入输出流以及socket
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
先运行server端进行监听,然后运行client端发送数据,执行结果如下:
UDP实现如下:
服务器端:UDPServe
package IMUHERO;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket( 65001);
//初始化一个空的数据报,并接收输入流
byte[] buff = new byte[100];
DatagramPacket receivePacket = new DatagramPacket(buff,buff.length);
socket.receive(receivePacket);
//解析数据
byte[]data=receivePacket.getData();
String content = new String(data,0,receivePacket.getLength());
System.out.println(content);//打印解析的字符串
//发送数据
byte[] sendContent = String.valueOf(content.length()).getBytes();
InetAddress address = receivePacket.getAddress();
int port = receivePacket.getPort();
DatagramPacket sendPacket = new DatagramPacket(sendContent,sendContent.length,address,port);
socket.send(sendPacket);
}
}
客户端:UDPClient:
package IMUHERO;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClient {
public static void main(String[] args) throws Exception {
//初始化socket
DatagramSocket socket = new DatagramSocket();
//初始化数据
byte [] buff = "Hello world".getBytes();
//发送数据
//将ip地址封装成InetAddress对象
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 65001;
DatagramPacket sendPacket = new DatagramPacket(buff,buff.length,address,port);
socket.send(sendPacket);
//接收数据
byte[]data=new byte[100];
//封装成一个数据报
DatagramPacket recevicePacket = new DatagramPacket(data,data.length);
socket.receive(recevicePacket);
byte[]receiveData = recevicePacket.getData();
String content = new String(receiveData,0,receiveData.length);
System.out.println(content);
}
}
结果