单工通信
一方只是发送,一方只是接收
const net = require('net');const socket = new net.Socket({});socket.connect({host: '127.0.0.1',port: 4000})socket.write('good morning geekbang');
const net = require('net');const server = net.createServer((socket)=> {socket.on('data', function(buffer) {console.log(buffer, buffer.toString())})});server.listen(4000);
半双工通信
既可以发送也可以接收,但是同一时刻通信中只有一种行为
const net = require('net');// 创建socketconst socket = new net.Socket({});// 连接服务器socket.connect({host: '127.0.0.1',port: 4000});const lessonids = ["136797","136798","136799","136800"]let id = Math.floor(Math.random() * lessonids.length);// 往服务器传数据socket.write(encode(id));socket.on('data', (buffer) => {console.log(buffer.toString())// 接收到数据之后,按照半双工通信的逻辑,马上开始下一次请求id = Math.floor(Math.random() * lessonids.length);socket.write(encode(id));})// 把编码请求包的逻辑封装为一个函数function encode(index) {buffer = Buffer.alloc(4);buffer.writeInt32BE(lessonids[index]);return buffer;}
const net = require('net');// 创建tcp服务器const server = net.createServer((socket) => {socket.on('data', function(buffer) {// 从传来的buffer里读出一个int32const lessonid = buffer.readInt32BE();// 50毫秒后回写数据setTimeout(()=> {socket.write(Buffer.from(data[lessonid]));}, 50)})});// 监听端口启动服务server.listen(4000);const data = {136797: "01 | 课程介绍",136798: "02 | 内容综述",136799: "03 | Node.js是什么?",136800: "04 | Node.js可以用来做什么?",}
全双工通信
同时发送和接收
const net = require('net');const socket = new net.Socket({});socket.connect({host: '127.0.0.1',port: 4000});let id = Math.floor(Math.random() * LESSON_IDS.length);let oldBuffer = null;socket.on('data', (buffer) => {// 把上一次data事件使用残余的buffer接上来if (oldBuffer) {buffer = Buffer.concat([oldBuffer, buffer]);}let completeLength = 0;// 只要还存在可以解成完整包的包长while (completeLength = checkComplete(buffer)) {const package = buffer.slice(0, completeLength);buffer = buffer.slice(completeLength);// 把这个包解成数据和seqconst result = decode(package);console.log(`包${result.seq},返回值是${result.data}`);}// 把残余的buffer记下来oldBuffer = buffer;})let seq = 0;/*** 二进制包编码函数* 在一段rpc调用里,客户端需要经常编码rpc调用时,业务数据的请求包*/function encode(data) {// 正常情况下,这里应该是使用 protobuf 来encode一段代表业务数据的数据包// 为了不要混淆重点,这个例子比较简单,就直接把课程id转buffer发送const body = Buffer.alloc(4);body.writeInt32BE(LESSON_IDS[data.id]);// 一般来说,一个rpc调用的数据包会分为定长的包头和不定长的包体两部分// 包头的作用就是用来记载包的序号和包的长度,以实现全双工通信const header = Buffer.alloc(6);header.writeInt16BE(seq)header.writeInt32BE(body.length, 2);// 包头和包体拼起来发送const buffer = Buffer.concat([header, body])console.log(`包${seq}传输的课程id为${LESSON_IDS[data.id]}`);seq++;return buffer;}/*** 二进制包解码函数* 在一段rpc调用里,客户端需要经常解码rpc调用时,业务数据的返回包*/function decode(buffer) {const header = buffer.slice(0, 6);const seq = header.readInt16BE();const body = buffer.slice(6)return {seq,data: body.toString()}}/*** 检查一段buffer是不是一个完整的数据包。* 具体逻辑是:判断header的bodyLength字段,看看这段buffer是不是长于header和body的总长* 如果是,则返回这个包长,意味着这个请求包是完整的。* 如果不是,则返回0,意味着包还没接收完* @param {} buffer*/function checkComplete(buffer) {if (buffer.length < 6) {return 0;}const bodyLength = buffer.readInt32BE(2);return 6 + bodyLength}for (let k = 0; k < 100; k++) {id = Math.floor(Math.random() * LESSON_IDS.length);socket.write(encode({ id }));}const LESSON_IDS = ["136797","136798","136799","136800","136801","136803","136804","136806","136807","136808","136809","141994","143517","143557","143564","143644","146470","146569","146582"]
const net = require('net');const server = net.createServer((socket) => {let oldBuffer = null;socket.on('data', function (buffer) {// 把上一次data事件使用残余的buffer接上来if (oldBuffer) {buffer = Buffer.concat([oldBuffer, buffer]);}let packageLength = 0;// 只要还存在可以解成完整包的包长while (packageLength = checkComplete(buffer)) {const package = buffer.slice(0, packageLength);buffer = buffer.slice(packageLength);// 把这个包解成数据和seqconst result = decode(package);// 计算得到要返回的结果,并write返回socket.write(encode(LESSON_DATA[result.data], result.seq));}// 把残余的buffer记下来oldBuffer = buffer;})});server.listen(4000);/*** 二进制包编码函数* 在一段rpc调用里,服务端需要经常编码rpc调用时,业务数据的返回包*/function encode(data, seq) {// 正常情况下,这里应该是使用 protobuf 来encode一段代表业务数据的数据包// 为了不要混淆重点,这个例子比较简单,就直接把课程标题转buffer返回const body = Buffer.from(data)// 一般来说,一个rpc调用的数据包会分为定长的包头和不定长的包体两部分// 包头的作用就是用来记载包的序号和包的长度,以实现全双工通信const header = Buffer.alloc(6);header.writeInt16BE(seq)header.writeInt32BE(body.length, 2);const buffer = Buffer.concat([header, body])return buffer;}/*** 二进制包解码函数* 在一段rpc调用里,服务端需要经常解码rpc调用时,业务数据的请求包*/function decode(buffer) {const header = buffer.slice(0, 6);const seq = header.readInt16BE();// 正常情况下,这里应该是使用 protobuf 来decode一段代表业务数据的数据包// 为了不要混淆重点,这个例子比较简单,就直接读一个Int32即可const body = buffer.slice(6).readInt32BE()// 这里把seq和数据返回出去return {seq,data: body}}/*** 检查一段buffer是不是一个完整的数据包。* 具体逻辑是:判断header的bodyLength字段,看看这段buffer是不是长于header和body的总长* 如果是,则返回这个包长,意味着这个请求包是完整的。* 如果不是,则返回0,意味着包还没接收完* @param {} buffer*/function checkComplete(buffer) {if (buffer.length < 6) {return 0;}const bodyLength = buffer.readInt32BE(2);return 6 + bodyLength}// 假数据const LESSON_DATA = {136797: "01 | 课程介绍",136798: "02 | 内容综述",136799: "03 | Node.js是什么?",136800: "04 | Node.js可以用来做什么?",136801: "05 | 课程实战项目介绍",136803: "06 | 什么是技术预研?",136804: "07 | Node.js开发环境安装",136806: "08 | 第一个Node.js程序:石头剪刀布游戏",136807: "09 | 模块:CommonJS规范",136808: "10 | 模块:使用模块规范改造石头剪刀布游戏",136809: "11 | 模块:npm",141994: "12 | 模块:Node.js内置模块",143517: "13 | 异步:非阻塞I/O",143557: "14 | 异步:异步编程之callback",143564: "15 | 异步:事件循环",143644: "16 | 异步:异步编程之Promise",146470: "17 | 异步:异步编程之async/await",146569: "18 | HTTP:什么是HTTP服务器?",146582: "19 | HTTP:简单实现一个HTTP服务器"}
