通信必要条件
主机之间需要有传输介质(网线,wifi,蓝牙)
主机上必须有网卡设备(调制与解调制,将电压与二进制数据进行转换)
主机之间需要协商网络速率
常见通讯方式
交换机通讯
路由器通讯
建立多台主机互联
定位局域网中的其他主机
通过 Mac 地址来唯一标识一台主机
但是交换机无法满足互联网需求:
交换机的接口数量有上限
局域网存在大量主机会造成广播风暴
明确目标主机 IP 地址
网络层次模型
OSI七层模型:
应用层:用户与网络的接口
表示层:数据加密、转换、压缩
会话层:控制网络连接建立与终止
传输层:控制数据传输可靠性
网络层:确定目标网络
数据链路层:确定目标主机
物理层:各种物理设备和标准
数据从 A 至 B,先封装再解封
TCP协议
TCP 属于传输层协议
TCP 是面向连接的协议
TCP 用于处理实时通信
常见控制字段
SYN = 1 表示请求建立连接
FIN = 1 表示请求断开连接
ACK = 1 表示数据信息确认
三次握手
创建 TCP 通信
Net 模块实现了底层通信接口
通信过程:
创建服务端:接收和回写客户端数据
创建客户端:发送和接收服务端数据
数据传输:内置服务事件和方法读写数据
通信事件:
listening 事件:调用 server.listen 方法之后触发
connection 事件:新的连接建立时触发
close 事件:当 server 关闭时触发
error 事件:当错误出现的时候触发
通信事件 & 方法:
data 事件:当接收到数据的时候触发该事件
write 方法:在 socket 上发送数据,默认是 utf8 编码
end 操作:当 socket 的一端发送 FIN 包时触发,结束可读端
server.js
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)
}
})
client.js
const net = require('net')
const client = net.createConnection({
port: 1234,
host: '127.0.0.1'
})
client.on('connect', () => {
client.write('前端工程')
})
client.on('data', (chunk) => {
console.log(chunk.toString())
})
client.on('error', (err) => {
console.log(err)
})
client.on('close', () => {
console.log('客户端断开连接')
})
TCP 数据粘包
通信包含数据发送端和接收端
发送端累积数据统一发送
接收端缓冲数据之后再消费
所以对于数据的使用就会产生粘包的问题
TCP拥塞机制决定发送时机
client.js
const net = require('net')
const client = net.createConnection({
port: 1234,
host: '127.0.0.1'
})
let dataArr = [
'前端工程2',
'前端工程3',
'前端工程4',
'前端工程5',
]
client.on('connect', () => {
client.write('前端工程1')
for(let i=0; i<dataArr.length; i++) {
(function (val, index) {
setTimeout(() => {
client.write(val)
}, 1000 * (index +1))
})(dataArr[i], i)
}
})
client.on('data', (chunk) => {
console.log(chunk.toString())
})
client.on('error', (err) => {
console.log(err)
})
client.on('close', () => {
console.log('客户端断开连接')
})
数据的封包与拆包
数据传输过程
进行数据编码,获取二进制数据包
按规则拆解数据,获取指定长度的数据
Buffer 数据读写
writelnt16BE:将 value 从指定位置写入
readInt16BE:从指定位置开始读取数据
封包解决粘包
myTransform.js
class MyTransformCode{
constructor() {
this.packageHeaderLen = 4
this.serialNum = 0
this.serialLen = 2
}
// 编码
encode(data, serialNum) {
const body = Buffer.from(data)
// 01 先按照指定的长度来申请一片内存空间做为 header 来使用
const headerBuf = Buffer.alloc(this.packageHeaderLen)
// 02
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 = MyTransformCode
server.js
const net = require('net')
const MyTransform = require('./myTransform.js')
const server = net.createServer()
let overageBuffer = null
let ts = new MyTransform()
server.listen('1234', 'localhost')
server.on('listening', () => {
console.log('服务端运行在 localhost:1234')
})
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)
console.log(ret)
socket.write(ts.encode(ret.body, ret.serialNum))
}
overageBuffer = chunk
})
})
client.js
const net = require('net')
const MyTransform = require('./myTransform.js')
let overageBuffer = null
let ts = new MyTransform()
const client = net.createConnection({
host: 'localhost',
port: 1234
})
client.write(ts.encode('前端工程1'))
client.write(ts.encode('前端工程2'))
client.write(ts.encode('前端工程3'))
client.write(ts.encode('前端工程4'))
client.write(ts.encode('前端工程5'))
client.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)
console.log(ret)
}
overageBuffer = chunk
})
1