node网络通信

网络通信 一、网络通信
1.网络通信基本原理 通信必要条件:

主机之间需要有传输介质(数据信号的传输) 主机上必须有网卡设备(数据信号的调制与解调制) 主机之间需要协商网络速率

图示:
node网络通信
文章图片

2.网络通信方式
交换机通讯 路由器通讯

图示:(利用Mac地址,定位主机)
node网络通信
文章图片

缺点:
交换机的接口数量有上限 局域网存在大量主机会造成广播风暴(任意一条消息都需要其它机器接收,再确认受否有效)

路由器使用(连接局域网)图示:
node网络通信
文章图片

3.网络层次模型 OSI七层模型:
node网络通信
文章图片

数据的封装与解封装(TCP/IP五层协议模式):
node网络通信
文章图片

参考:https://blog.csdn.net/weixin_...
4.tcp协议 tcp报文结构图解:
node网络通信
文章图片

常见控制字段:
node网络通信
文章图片

三次握手图解:
node网络通信
文章图片

四次挥手图解:
node网络通信
文章图片

为什么挥手中,中间两次不能合并呢?
一个服务端会服务于多个客户端,服务端接收到消息后,不一定能即使将结果及时传回

TCP协议总结:
TCP处于传输层,基于端口,面向连接 主机之间要想通信需要先建立双向数据通道 TCP的握手与挥手本质上都是四次

二、创建TCP通信
Net模块实现了底层通信接口

通信过程:
创建服务端:接收和回写客户端数据 创建客户端:发送和接收服务端数据 数据传输:内置服务事件和方法读写数据

核心内置通信事件:
listening事件:调用server.listen方法之后触发 connection事件:新的连接建立时触发 close事件:当server关闭时触发 error事件:当错误出现的时候触发

通信事件&方法:
data事件:当接收到数据的时候触发该事件 write方法:在socket上发送数据,默认是UTF8编码 end操作:当socket的一端发送FIN包时触发,结束可读端

1.net模块使用 node中tcp连接的创建与通信:
server:
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}`); }); // 就收消息,回写消息socket 双工流 server.on('connection',(socket)=>{ console.log('有客户端连接'); socket.on('data',(chunk)=>{ const msg = chunk.toString(); console.log('client:'+msg); // 回写消息 socket.write(Buffer.from('hello client')); }) }); server.on('close',()=>{ console.log('服务端已经关闭'); })server.on('error',(err)=>{ console.log('服务端发生错误',err); })

client:
const net = require('net'); const client = net.createConnection({ port: 1234, host: '127.0.0.1' })client.on('connect', () => { client.write('hello server'); })client.on('data',(chunk)=>{ console.log('server:'+chunk.toString()); }); client.on('err',()=>{ console.log('客户端发生错误'); }); client.on('close',()=>{ console.log('客户端已经关闭'); })

2.TCP粘包问题 问题展示:
client代码:
client.on('connect', () => { client.write('hello server'); client.write('hello server'); client.write('hello server'); client.write('hello server'); })

server代码:
server.on('connection',(socket)=>{ console.log('有客户端连接'); socket.on('data',(chunk)=>{ const msg = chunk.toString(); console.log('client:'+msg); // 回写消息 socket.write(Buffer.from('hello client')); }) });

结果展示:
node网络通信
文章图片

'client:'打印了一次,出现了粘包问题,
1.间断发送数据 client代码:
let dataArr = [ 'hello server', 'hello server-2', 'hello server-3', 'hello server-4', ]client.on('connect', () => { for (let i = 0; i < dataArr.length; i++) { (function (val, index) { setTimeout(() => { client.write(val) }, 1000 * (index + 1)) })(dataArr[i], i) }})

正确效果:
node网络通信
文章图片

2.数据的封包与拆包 约定包的结构:
node网络通信
文章图片

数据传输过程:
进行数据编码,获取二进制数据包 按规则拆解数据,获取指定长度的数据

Buffer数据读写:
writeInt16BE:将value从指定位置写入 readInt16BE:从指定位置开始读取数据

封包与拆包类实现:
class MyTransformCode { constructor() { this.packageHeaderLen = 4; this.serialNum = 0; this.serialLen = 2; }// 编码 encode(data, serialNum) { let body = Buffer.from(data); // 01 先按照指定的长度申请一个缓冲区 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) { let 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); } } }

使用:
const MyTransform = require('./03-myTransform'); let ts = new MyTransform(); let str1 = '江江学习'; let encodeBuf = ts.encode(str1,1); console.log(ts.decode(encodeBuf)); let len = ts.getPackageLen(encodeBuf); console.log(len)

封包解决粘包:
// server server.on('connection', (socket) => { console.log('有客户端连接'); socket.on('data', (chunk) => { if (overageBuffer) { chunk = Buffer.concat([overageBuffer, chunk]); overageBuffer = null; } 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 client.on('connect', () => { client.write(ts.encode('拉钩教育')); client.write(ts.encode('拉钩教育')); client.write(ts.encode('拉钩教育')); client.write(ts.encode('拉钩教育')); client.write(ts.encode('拉钩教育')); client.write(ts.encode('拉钩教育')); })client.on('data', (chunk) => { if (overageBuffer) { chunk = Buffer.concat([overageBuffer, chunk]); overageBuffer = null; } 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; });

三、Http协议
1.使用http模块 开启一个http服务器:
const http = require('http'); let server = http.createServer((req,res)=>{ res.end('hello world'); }); server.listen(1234,()=>{ console.log('server is listening 1234'); })

获取http请求信息:
const http = require('http'); const url = require('url'); const server = http.createServer((req,res)=>{// 请求路径 let {pathname,query} = url.parse(req.url,true); console.log('pahtinfo:',pathname,'----',query)// 请求方法 console.log('method:',req.method); // 版本号 console.log('httpVersion:',req.httpVersion); // 请求头 console.log('headers:',req.headers); let arr = [] // 请求体 req.on('data',(data)=>{ arr.push(data); }); req.on('end',()=>{ console.log('body:',Buffer.concat(arr).toString()) })res.end('hello client') }); server.listen(1234,()=>{ console.log('server is listening 1234'); })

设置http响应:
const http = require('http'); const server = http.createServer((req,res)=>{ console.log('request enter'); // 设置响应状态码 res.statusCode = 302; // 设置响应头信息 res.setHeader('Content-Type','text/html; charset=utf-8'); // res.write('ok'); res.end('江江'); }); server.listen(1234,()=>{ console.log('servet is listening 1234'); })

2.客户端代理 agent-server代码:
const http = require('http'); const url = require('url'); const qeurystring = require('querystring'); const server = http.createServer((req, res) => { let { pathname, query } = url.parse(req.url, true); console.log(pathname, '---', query); let arr = []; req.on('data', (data) => { arr.push(data); }) req.on('end', () => { let obj = Buffer.concat(arr).toString(); if(req.headers['content-type'] == 'application/json'){ let ret = JSON.parse(obj); ret.add = 'add'; res.end(JSON.stringify(ret)); }else if(req.headers['content-type'] == 'application/x-www-form-urlencoded'){ let ret = qeurystring.parse(obj); res.end(JSON.stringify(ret)); }})}) server.listen(1234, () => { console.log('server is listening 1234'); })

agent-client代码:
const http = require('http'); let options = { host: 'localhost', port: 1234, path: '/', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', } }let req = http.request(options, (res) => { let arr = []; res.on('data',(chunk)=>{ arr.push(chunk); }) res.on('end',()=>{ console.log(Buffer.concat(arr).toString()); }) })// req.end(`{"name":"zhangsan"}`) req.end(`a=1&b=2`)

3.代理客户端跨域 外部服务器代码:
const http = require('http'); const server = http.createServer((req, res) => { let arr = []; req.on('data',(data)=>{ arr.push(data); }) req.on('end',()=>{ console.log(Buffer.concat(arr).toString()); res.end('外部服务i器端数据'); }) })server.listen(1234, () => { console.log('外部服务端启动了'); })

【node网络通信】代理客户服务器:
const http = require('http'); let options = { host: 'localhost', port: 1234, path: '/', method: 'POST' } let server = http.createServer((request, response) => { let req = http.request(options, (res) => { let arr = []; res.on('data', (data) => { arr.push(data); }) res.on('end', () => { let ret = Buffer.concat(arr).toString(); response.setHeader('Content-Type', 'text/html; charset=utf-8'); console.log('ret', ret) response.end(ret) }) }) req.end('hello world'); })server.listen(2345, () => { console.log('本地服务端已经启动'); })

    推荐阅读