网络通信方式
网络层次模型
OSI 七层模型
- 应用层: 用户与网络的接口
- 表示层: 数据加密,转换,压缩
- 会话层: 控制网络连接建立与中止
- 传输层: 控制数据传输可靠性
- 网络层: 确定目标网络
- 数据链路层: 确定目标主机
- 物理层: 各种网络屋里设备
TCP 四层模型
数据封装与解封装
TCP 三次握手与四次挥手
TCP 协议
- TCP 属于传输层协议
- TCP 是面向连接的协议
- TCP 用于处理实时通信
本质上请求连接或请求断开都是四次连接,但是请求断开中服务端请求不能合并成一条.
常见控制字段
- SYN=1: 请求建立连接
- FIN=1: 请求断开连接
- ACK=1: 数据信息的确认
本来应该是两次一来一回,共四次,但是服务端这里可以将请求和确认合并发送,就是三次握手连接.
断开连接:
变成四次是因为确保服务端已经将数据全部传输完毕,所以不能进行合并.
创建 TCP 通信
通信过程
- 创建服务端: 接收和诙谐客户端数据
- 创建客户端: 发送接收服务端数据
- 数据传输: 内置服务事件和方法读写数据
通信事件
- listening 事件: 调用 server.listen 方法后触发
- connection 事件: 新的连接建立时触发
- close 事件: 当 server 关闭时触发
- error 事件: 当错误出现时触发
通信事件&方法
- data 事件: 当接收到数据时触发
- write 方法: 在 socket 上发送数据,默认 UTF-8
- end 操作: 当 socket 的一端发送 FIN 包时触发,结束可读端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const net = require("net");
const server = net.createServer(); const PORT = 1234; const HOST = "localhost";
server.listen(PORT, HOST);
server.on("listening", () => { console.log(`服务端已开启在 ${HOST}: ${PORT}`); });
server.on("connection", (socket) => { socket.on("data", (chunk) => { const msg = chunk.toString(); console.log(msg);
socket.write(Buffer.from("您好" + msg)); }); });
server.on("close", () => { console.log("服务端关闭"); }); server.on("error", (err) => { if (err.code === "EADDRINUSE") { console.log("地址占用"); } else { console.log(err); } });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const net = require("net");
const client = net.createConnection({ port: 1234, host: "127.0.0.1", });
client.on("connect", () => { client.write("edu"); });
client.on("data", (chunk) => { console.log(chunk.toString()); });
client.on("close", () => { console.log("客户端断开连接"); }); client.on("error", (err) => { console.log(err); });
|
TCP 数据粘包
发送端和接收端都是等待缓冲数据之后再消费.
出现问题的原因: 发送间隔太短导致数据堆积.
数据的封包和拆包
将消息头拆分为序列号和消息长度
数据传输过程
- 进行数据编码,获取二进制数据包
- 按规则拆解数据,获取指定长度数据
Buffer 数据读写
- writeInt16BE: 将 value 从指定位置写入
- readInt16BE: 从指定位置开始读取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class myTransform { constructor() { this.packageHeaderLen = 4; this.serialNum = 0; this.serialLen = 2; }
encode(data, serialNum) { const body = Buffer.from(data); const headerBuf = Buffer.alloc(this.packageHeaderLen); headerBuf.writeInt16BE(serialNum || this.serialNum); headerBuf.writeInt16BE(body.length, this.serialLen); if (serialNum === undefined) { this.serialNum++; } return Buffer.concat(headerBuf, body); }
decode(buffer) { const headerBuf = buffer.slice(0, this.packageHeaderLen); const bodyBuf = buffer.slice(this.packageHeaderLen);
return { serialNum: headerBuf.readInt16BE(), bodyLength: headerBuf.readInt16BE(this.serialLen), body: bodyBuf.toString(), }; }
getPackageLen(buffer) { if (buffer.length < this.packageHeaderLen) { return 0; } else { return this.packageHeaderLen + buffer.readInt16BE(this.serialLen); } } }
module.exports = myTransform;
|
数据粘包解决
利用上面的编解码工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const myTransform = require("./myTransform.js");
let overageBuffer = null; let ts = new myTransform();
server.on("connection", (socket) => { socket.on("data", (chunk) => { if (overageBuffer) { chunk = Buffer.concat([overageBuffer, chunk]); }
let packageLen = 0; while ((packageLen = ts.getPackageLen(chunk))) { const packageCon = chunk.slice(0, packageLen); chunk = chunk.slice(packageLen);
const ret = ts.decode(packageCon); socket.write(ret.body, ret.serialNum); } overageBuffer = chunk; }); });
|
Http 协议
获取 http 请求信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const http = require("http"); const url = require("url");
const server = http.createServer((req, res) => { const { pathname, query } = url.parse(req.url, true); console.log(req.method); console.log(req.httpVersion); console.log(req.headers); let arr = []; req.on("data", (data) => { arr.push(data); });
req.on("end", () => { console.log(Buffer.concat(arr).toString()); }); });
server.listen(1234, () => { console.log("server start ..."); });
|
设置 http 响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const http = require("http"); const url = require("url");
const server = http.createServer((req, res) => {
res.statusCode = 200; res.setHeader("Content-type", "text/html;charset=utf-8"); res.end("你好"); });
server.listen(1234, () => { console.log("server start ..."); });
|
客户端代理
相当于服务端,客户端与不同端口域名协议的服务端通信有跨域,那就采用客户端代理,它与服务端就不存在跨域.