HTTP简述:
-
http 1.x
1,http/1.0 支持POST HEAD等请求方法,浏览器每次请求都需要与服务器建立一个TCP连接,请求处理完后立即断开TCP连接。
2,http/1.1支持PUT、DELETE等请求方法,采用持久连接,多个请求可以共用同一个TCP连接。
-
http2.0
1,在应用层与传输层之间,增加了一个二进制分帧层。
2,多路复用,可以实现并行发送多个请求。http 1.x版本是长连接,但多个请求发送是串行。
3,可以对浏览器等待的关键的数据流设置优先级,对数据流可以采用不同的优先级策略。
4,更多细节...
-
https http的安全版,运行在安全套接字协议(SSL)或传输层安全协议(TLS)之上,建立了一个信息安全通道,所有的TCP中传输的内容都需要经过加密,保证了数据安全。连接端口是443。TLS后文有简述。
-
tcp传输控制协议:是全双工通信。
1,首部至少20个字节;UDP首部只有8个字节(源端口号+目的端口号+UDP长度+UDP校验和 均是16位)。
2,可靠传输(ARQ:超时自动重传请求;滑动窗口协议,是点对点的通信控制)
3,流量控制(根据接收方窗口缓冲区控制发送方的发送速率)
4,拥塞控制(举例:慢开始:指数增加;拥塞避免:加法线性增加;快恢复+快重传:当发现带宽网络拥塞时,将ssthresh阈值减为拥塞峰值的一半,然后进行快恢复且使用加法增大传输数据包。 ps:通过wireshark抓包一个TCP连接的数据展示如图:
-
tcp三次握手之后TLS握手:(三次握手四次挥手略,老生常谈)
1,客户端会发送一个ClientHello请求,内容包括支持的TLS版本、加密套件以及一个随机数。
2,服务器收到后会响应ServerHello(会包含一个随机数)、下发服务器自己的证书(客户端可根据证书列表check服务器是否可信)和ServerKeyExchange(包含服务器随机生成的一对密钥中的公钥pubkey),然后响应ServerHelloDone。
3,客户端进行ClientKeyChange操作,向服务器发送的内容中,包含公钥pubkey加密后的第三个随机数(预主密钥),以及Change Cipher Spec告诉服务器用商定好的算法和密钥进行加密,然后告诉服务器Encrypted Handshake message。
4,服务器收到后用私钥解密获取到预主密钥并响应Encrypted Handshake message 。
5,基于上述通信,客户端和服务器都具备了相同的两个随机数和预主密钥,然后通过约定好的算法生成最终的会话密钥,此后通过对称密码进行数据传输。
-
数字证书(数字签名):私钥加密公钥解密,文章内容会经过哈希算法参与其中,因此可验证文件完整性。
数字签名的过程:
1,用户将主体信息(包含一串随机数后面作为私钥privatekey)发给CA,CA会利用主体信息生成主体文件的公钥并且进行签名,签完名的文件即是数字证书,返回给用户。 2,用户将文件通过单向散列函数生成128位的摘要,然后用privatekey私钥对此摘要进行加密。
3,当用户将文件,加密的摘要,公钥publickey发送其他用户时。他人可以对文件进行同样的单向散列函数产生的摘要h1和用publicekey对加密后的摘要解密生成的摘要h2进行比对。如果相等,则文件合法未被篡。
客户端端拿到服务器下发的证书时,会对文件信息进行hash算法得到一个数字指纹h1,以及通过用ca公钥(本地保存的)解密签名会得到一个数字指纹h2,如果h1和h2一致,则证书合法。如果是客户端拿到服务器发送的签名证书,则会向CA验证证书的合法性。一般浏览器出厂时会内置了诸多CA机构的数字证书校验方法。 拓展:在区块链的实际使用上,币交易时,input买方需要拿到output卖方的公钥。
客户端与服务器的交互
-
JWT:json web token
1,普通的token:服务端验证token信息要进行数据的查询操作;而JWT验证token信息并不用,在服务端使用密钥校验就可以,无需查询数据库。
2,组成:Header头部(base64后)+Playload负载(base64后)+signature签名
3,工作流程
- 跨域: 服务器响应头设置Cors,在header中配置"Access-Control-Allow-Origin",表示允许访问者能够跨域访问,并且指定允许的域。 客户端请求头header中"Origin"字段,表示发起一个针对跨域资源共享的请求。
- cookie与session :Cookie是基于客户端存储数据到本地,服务端可以返回Cookie交给客户端存储。Session是基于服务器存储数据。 客户端首次向服务端发起建立网络请求后,服务端会建立一个Session,在响应头中配置"SetCookie:JSESSIONID=xxx"以及domain等。客户端收到后会设置Cookie保存JSESSIONID。在Cookie有效期后,在以后的request请求中,都会带上此JSESSIONID,服务端接收到此请求后,会找到之前已经建立的SESSION。
socket与WebSocket
- socket:并非协议,是基于TCP/IP网络封装的API,为了方便TCP或UDP而抽象出来的一层,是位于应用和传输控制层之间的一组接口,socket利用TCP/IP协议建立TCP连接,进行依赖于底层的IP协议,更深层次依赖于数据链路层。 维持长链接。
socket的建立过程如图:
代码演示:
//客户端:
public static final int PORT = 8080;
public static final String HOST = "127.0.0.1";
Socket socket = new Socket(HOST, PORT);
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
os.write("向服务端写数据");
socket.shutdownOutput();
// 读取服务器发送的数据
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
//服务端:
//创建ServerSocket的接口
public ServerSocket(int port) throws IOException {
this(port, 50, null);
}
ServerSocket(int port, int backlog, InetAddress bindAddr);
public InetSocketAddress(InetAddress addr, int port) {
holder = new InetSocketAddressHolder(
null,
addr == null ? InetAddress.anyLocalAddress() : addr,
checkPort(port));
}
ServerSocket server = ServerSocket(PORT);
Socket sk = server.accept();
//读写数据,代码 类上
- webSocket:HTTP、WebSocket等应用层协议,都是基于TCP协议来传输数据的。必须依赖HTTP协议进行一次握手,握手成功后,数据就直接从TCP通信传输,与HTTP无关了。WebSocket API是HTML5标准的一部分,实现了客户端与服务端全双工通信,而HTTP是单向通信。 代码演示:
//代码临摹于某大神,有逻辑改动
//server.js
//终端安装websocket库:npm install websocket
var websocket = require('websocket').server
var http = require('http')
const HOST = 'http://127.0.0.1:8080';
const PORT = 8080;
var httpServer = http.createServer().listen(PORT, function(){
console.log(HOST)
})
var wsServer = new websocket({
httpServer: httpServer,
autoAcceptConnections: false,
})
wsServer.on('request', function (request) {
var connect = request.accept()
connect.on('message', function (message) {
console.log(Date.now());
message.fromServer = 1;
console.log(message)
connect.send(message.utf8Data)
})
//监听关闭连接操作
connection.on("close", function (code, reason) {
var message = {}
message.type = "leave"
message.data = "离开了"
connect.send(JSON.stringify(message))
})
//错误处理
connection.on("error", function (err) {
console.log(err)
})
})
//浏览器端:通过<script>的方式使用WebSocket功能。
<script>
const hostPort = 'ws://127.0.0.1:8080'
var webSocket = new WebSocket(hostPort)
/*readyState一共四种状态
0: 链接还没有建立,正在建立链接
1:链接建立
2:链接正在关闭
3:链接已经关闭
*/
webSocket.onopen = function(){
console.log(webSocket.readyState)
}
function send(){
var text = document.getElementById('text').value
webSocket.send(text)
}
webSocket.onclose = function () {
console.log("websocket close")
}
//实时监听服务器推送到客户端的事件
webSocket.onmessage = function (msg) {
console.log(Date.now());
showMessage(msg.data, msg.type)
}
//在页面中现实聊天内容
function showMessage(str, type) {
var div = document.createElement('div')
div.innerHTML = str;
if (type == "enter") {
div.style.color = "blue";
} else if(type == "leave") {
div.style.color = "red"
} else if(type == "message") {
div.style.color = "black"
}
document.body.appendChild(div)
}
</script>
WebRTC技术是H5标准之一,它通过简单的API为浏览器和移动端应用程序提供了实时通信的功能
- WebRTC框架
- WebRTC通话原理:
1,媒体协商:简而言之,就是对通信两端能够支持的编解码格式取交集,例如A端支持VP8、H264,B端支持H264,H265,那么为保证AB两端通信正常,则选H264...。SDP(Session Description Protocol)用于描述上述这类信息。视频通讯双方必须先交换SDP信息,这个过程叫媒体协商。
2,网络协商:通信两端要了解对方的网络情况,以确保找到一条相互通讯的链路。理想的场景是两端都是私有公网IP,可以直接peer2Peer连接。但真实的情况,终端都是在局域网中,需要NAT(Network Address Translation:网络地址转换)。为解决WebRTC这些问题,引出了STUN和TURN。
(1)STUN(Session Traversal Utilities for NAT, NAT会话穿越应用程序)是一种网络协议,它允许位于NAT后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后,以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间创建UDP通信。这样就能给无法在公网环境下的视频通信设备分配公网IP以建立通话连接。简而言之:STUN是告知我对方的公网IP地址+端口。
媒体流传输按照P2P的方式,STUN并不是每次都能成功地为需要NAT的通信端分配IP地址,P2P地方式在多人视频通话中,更是严重依赖本地的带宽。TURN能够很好的解决这个问题。
(2)TURN(Traversal Using Relays around NAT)是STUN/RFC5389的一个拓展。主要添加了Relay功能。如果终端在NAT之后,那么在特定的情况下,有可能使得终端无法和其对等端进行直接的通信,这时就需要公网的服务器作为一个中继,对来往的数据进行转发,这个转发协议就被定义为TURN。这种方式的带块由服务器端解决。
(3)在WebRTC中用来描述网络信息的术语叫candidate,也叫网络协商。
3,信令系统(信令服务端)
(1)信令服务端包含交互媒体信息sdp和网络信息candidate,以及房间管理和人员进出等;
(2)信令发送过程:
//信令集合
//主动加入房间
const SIGNAL_TYPE_JOIN = "join"
//告知加入者对方是谁
const SIGNAL_TYPE_RESP_JOIN = "resp-join"
//主动离开房间
const SIGNAL_TYPE_LEAVE = "leave"
//有人加入房间 通知已经在房间的人
const SIGNAL_TYPE_NEW_PEER = "new-peer"
//有人离开房间 通知已经在房间的人
const SIGNAL_TYPE_PEER_LEAVE = "peer-leave"
//发送offer给对端peer
const SIGNAL_TYPE_OFFER = "offer"
//发送offer给对端peer
const SIGNAL_TYPE_ANSWER = "answer"
//发送candidate给对端peer
const SIGNAL_TYPE_CANDIDATE = "candidate"
// 初始化本地媒体流:
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
// 打开本地媒体流
var localVideo = document.querySelector('#localVideo');
localVideo.srcObject = stream; // 显示画面
localStream = stream; // 保存本地流的句柄
//DoOffer:
1,创建RTCPeerConnecttion对象并设置onicecandidate监听事件。监听事件中会触发 candidate信令。见下一代码块
2,写入本地session描述
3,发送offer信令
//handleRemoteOffer:
1,写入远端session描述
//DoAnswer:
1,写入本地session描述,并发送answer信令。
//HandleRemoteAnswer:
写入本地session描述
RTCPeerConnection音视频通话的核心类
//临摹代码拿来主义(建议整个信令系统临摹一遍,细节印象更深刻)
function createPeerConnection() {
var defaultConfiguration = {
bundlePolicy: "max-bundle",
rtcpMuxPolicy: "require",
iceTransportPolicy:"all",//relay 或者
// 修改ice数组测试效果,需要进行封装
iceServers: [
{
"urls": [
"turn:![]()192.168.0.1:61313?transport=udp",
"turn:![]()192.168.0.1:61313?transport=tcp" // 可以插入多个进行备选
],
"username": "KinnoWang_CN",
"credential": "There will be more surprises here, Beyond foresight"
},
{
"urls": [
"stun:![]()192.168.0.1:61313"
]
}
]
};
pc = new RTCPeerConnection(defaultConfiguration);
pc.onicecandidate = handleIceCandidate;
pc.ontrack = handleRemoteStreamAdd;
pc.onconnectionstatechange = handleConnectionStateChange;
pc.oniceconnectionstatechange = handleIceConnectionStateChange
localStream.getTracks().forEach((track) => pc.addTrack(track, localStream)); // 把本地流设置给RTCPeerConnection
}
function handleRemoteStreamAdd(event) {
remoteStream = event.streams[0]
remoteVideo.srcObject = remoteStream;
}
ps:在使用模拟服务器软件XShell(最新版)时,会提示安装错误,需要修改注册表,修改后重启会ok。