马大波 1 vuosi sitten
sitoutus
44d8266263
7 muutettua tiedostoa jossa 404 lisäystä ja 0 poistoa
  1. 30 0
      .gitignore
  2. 7 0
      index.js
  3. 80 0
      native/zone1.js
  4. 81 0
      native/zone2.js
  5. 12 0
      package.json
  6. 74 0
      stick/zone1.js
  7. 120 0
      stick/zone2.js

+ 30 - 0
.gitignore

@@ -0,0 +1,30 @@
+.cov
+coverage
+node_modules
+.naeindex
+coverage.html
+.monitor
+
+*.min.*.js
+*.min.*.css
+assets.json
+
+# Ignore Mac OS desktop services store
+.DS_Store
+
+# Ignore Windows desktop setting file
+desktop.ini
+
+# Ignore Redis snapshot
+dump.rdb
+
+*.log
+
+.idea
+.vscode
+
+*.sublime-project
+*.sublime-workspace
+*.swp
+
+package-lock.json

+ 7 - 0
index.js

@@ -0,0 +1,7 @@
+// const buf = Buffer.from('999', 'utf-8');
+// console.log(buf.length);
+// const hexString = buf.toString('hex');
+// const hexbuf = Buffer.from(hexString.padStart(8, '0'), 'hex');
+// console.log(hexbuf.toString('utf-8'));
+
+// console.log(Buffer.from([0xcdbf3c]).toString('utf-8'));

+ 80 - 0
native/zone1.js

@@ -0,0 +1,80 @@
+const net = require('node:net');
+const { Transform } = require('node:stream');
+
+const LOCAL_PORT = 1935;
+const REMOTE_PORT = 1936;
+const REMOTE_ADDR = '127.0.0.1';
+
+/**
+ * 将十进制数字转换为4字节的 Buffer
+ * @param {number} number 数字
+ * @param {string} byteOrder 排序
+ * @returns {Buffer}
+ */
+function toFixed4ByteBuffer(number, byteOrder = 'BE') {
+  if (number < 0 || number > 0xFFFFFFFF) {
+      throw new Error('Number must be between 0 and 4294967295');
+  }
+
+  const buffer = Buffer.alloc(4); // 创建一个4字节的 Buffer
+
+  if (byteOrder === 'BE') {
+      buffer.writeUInt32BE(number, 0); // 大端顺序
+  } else if (byteOrder === 'LE') {
+      buffer.writeUInt32LE(number, 0); // 小端顺序
+  } else {
+      throw new Error('Invalid byte order');
+  }
+
+  return buffer;
+}
+
+class CustomTransform extends Transform {
+  _transform(chunk, encoding, callback) {
+    const packet = Buffer.concat([toFixed4ByteBuffer(chunk.length), chunk]);
+    this.push(packet, encoding);
+    callback();
+  }
+}
+
+const getIdentifier = () => {
+  return Buffer.from([0x01, 0x02, 0x03, 0x04]);
+};
+
+function bootstrap() {
+  const proxy = net.createServer();
+
+  proxy.on('connection', (clientSocket) => {
+    console.log('Client connected');
+
+    // 创建一个新的连接到目标服务器
+    const targetSocket = net.createConnection({
+      host: REMOTE_ADDR, // 目标服务器的地址
+      port: REMOTE_PORT // 目标服务器的端口
+    }, () => {
+      console.log('Proxy connected to target server');
+    });
+
+    // 将接收到的数据转发到目标服务器
+    clientSocket.pipe(new CustomTransform()).pipe(targetSocket)
+
+    // 将目标服务器的响应数据转发回客户端
+    targetSocket.on('data', chunk => {
+      clientSocket.write(chunk);
+    });
+
+    // 处理代理与目标服务器的关闭事件
+    clientSocket.on('close', () => {
+      targetSocket.end();
+    });
+    targetSocket.on('close', () => {
+      clientSocket.end();
+    });
+  });
+
+  // 监听本地端口
+  proxy.listen(LOCAL_PORT, () => {
+    console.log('Zone1 proxy server is listening on port ' + LOCAL_PORT);
+  });
+}
+bootstrap();

+ 81 - 0
native/zone2.js

@@ -0,0 +1,81 @@
+const net = require('node:net');
+
+const LOCAL_PORT = 1936;
+const REMOTE_PORT = 1935;
+const REMOTE_ADDR = '39.101.185.102';
+/**
+ * 包头是4字节无符号整数,表示数据包长度
+ */
+const HEADER_LENGTH = 4;
+
+const routeTable = {
+  '01020304': { host: '39.101.185.102', port: 1935 },
+}
+
+function bootstrap() {
+  const proxy = net.createServer();
+
+  proxy.on('connection', (clientSocket) => {
+    console.log('Client connected');
+    let num = 0;
+    let buf = Buffer.alloc(0);
+    let framelen = 0;
+
+    // 创建一个新的连接到目标服务器
+    const targetSocket = net.createConnection({
+      host: REMOTE_ADDR,
+      port: REMOTE_PORT,
+    }, () => {
+      console.log('Proxy connected to target server');
+    });
+
+    // 将接收到的数据转发到目标服务器
+    clientSocket.on('data', chunk => {
+      console.log('接收原始报文:', chunk.toString('hex'));
+      buf = Buffer.concat([buf, chunk]);
+      console.log('buffer length', buf.length);
+      let origin; 
+      const chunklen = chunk.length;
+      if (chunklen < HEADER_LENGTH) return;
+      framelen = framelen || buf.readUInt32BE(0);
+      if (chunklen - HEADER_LENGTH === framelen) {
+        origin = buf.subarray(HEADER_LENGTH, chunklen);
+        buf = Buffer.alloc(0);
+        framelen = 0;
+      } else if (chunklen - HEADER_LENGTH > framelen) {
+        origin = buf.subarray(HEADER_LENGTH, chunklen);
+        buf = buf.subarray(chunklen);
+        if (buf.length >= HEADER_LENGTH) {
+          framelen = buf.readUInt32BE(0);
+        } else {
+          framelen = 0;
+        }
+      }
+      console.log('数据帧长度:', framelen);
+      if (origin) {
+        num += 1;
+        targetSocket.write(origin);
+        console.log('第'+num+'段报文', origin.toString('hex'));
+      }
+    });
+
+    // 将目标服务器的响应数据转发回客户端
+    targetSocket.on('data', chunk => {
+      clientSocket.write(chunk);
+    });
+
+    // 处理代理与目标服务器的关闭事件
+    clientSocket.on('close', () => {
+      targetSocket.end();
+    });
+    targetSocket.on('close', () => {
+      clientSocket.end();
+    });
+  });
+
+  // 监听本地端口
+  proxy.listen(LOCAL_PORT, () => {
+    console.log('Zone2 proxy server is listening on port ' + LOCAL_PORT);
+  });
+}
+bootstrap();

+ 12 - 0
package.json

@@ -0,0 +1,12 @@
+{
+  "name": "tcp-proxy-node",
+  "version": "1.0.0",
+  "description": "TCP Proxy Nodejs",
+  "main": "index.js",
+  "scripts": {},
+  "author": "boz",
+  "license": "TIM",
+  "dependencies": {
+    "@lvgithub/stick": "^3.1.11"
+  }
+}

+ 74 - 0
stick/zone1.js

@@ -0,0 +1,74 @@
+'use strict';
+
+const net = require('node:net');
+
+const LOCAL_PORT = 1935;
+const REMOTE_PORT = 1936;
+const REMOTE_ADDR = '127.0.0.1';
+
+/**
+ * 头长度(Byte)
+ */
+const HEAD_LEN = 4;
+
+/**
+ * 标识长度(Byte)
+ */
+const IDENTIFIER_LEN = 2;
+
+/**
+ * 目标媒体服务器标识
+ * 10:9999 -> 16:270f
+ * 支持2字节对应最大十进制32767
+ */
+const MEDIA_SERVER_IDENTIFIER = 9999;
+
+const proxy = net.createServer(function (socket) {
+    const targetSocket = net.createConnection({
+        host: REMOTE_ADDR,
+        port: REMOTE_PORT,
+    }, function () {
+        console.log('Proxy connected to target server');
+    });
+
+    socket.on('data', function (data) {
+        // 写入标识
+        const identifierBuf = Buffer.alloc(IDENTIFIER_LEN);
+        identifierBuf.writeInt16BE(MEDIA_SERVER_IDENTIFIER, 0);
+        // 写入包头
+        const headBuf = Buffer.alloc(HEAD_LEN);
+        headBuf.writeUInt32BE(data.byteLength + IDENTIFIER_LEN, 0);
+        // 发送包头
+        targetSocket.write(headBuf);
+        // 发送标识
+        targetSocket.write(identifierBuf);
+        // 发送包内容
+        targetSocket.write(data);
+        // console.log(data.toString('hex'));
+    });
+
+    socket.on('close', function () {
+        targetSocket.end();
+        console.log('client disconnected');
+    });
+
+    socket.on('error', function (error) {
+        console.log(`error:客户端异常断开: ${error}`);
+    });
+
+    targetSocket.on('data', function (chunk) {
+        socket.write(chunk);
+    });
+
+    targetSocket.on('close', function () {
+        socket.end();
+    });
+});
+
+proxy.on('error', function (err) {
+    throw err;
+});
+
+proxy.listen(LOCAL_PORT, function () {
+    console.log('Zone1 proxy listening on ' + LOCAL_PORT);
+});

+ 120 - 0
stick/zone2.js

@@ -0,0 +1,120 @@
+'use strict';
+
+/**
+ * 2个字节是16位(二进制),共能表示2^16=65536个数。但由于数是有符号的,最高位用作符号位,所以只有低15位表示绝对值。
+ * 整型在内存中以补码存储,能表示的最大正整数是2^15-1=32767。
+ */
+
+const net = require('node:net');
+const { Stick, MaxBodyLen } = require('@lvgithub/stick');
+
+const stick = new Stick(1024).setReadIntBE('32');
+stick.setMaxBodyLen(MaxBodyLen['2048M']);
+
+const LOCAL_PORT = 1936;
+
+/**
+ * 头长度(Byte)
+ */
+const HEAD_LEN = 4;
+
+/**
+ * 标识长度(Byte)
+ */
+const IDENTIFIER_LEN = 2;
+
+/**
+ * 媒体服务器映射
+ */
+const MEDIA_SERVER_MAP = new Map([
+    ['9999', { port: 1935, host: '192.168.66.73' }],
+]);
+
+/**
+ * 媒体服务器连接池
+ */
+const MEDIA_SERVER_POOLS = new Map();
+
+/**
+ * 初始化多媒体服务器连接池
+ */
+for (let [key, value] of MEDIA_SERVER_MAP) {
+    const socket = net.createConnection({ host: value.host, port: value.port });
+    MEDIA_SERVER_POOLS.set(key, socket);
+}
+
+/**
+ * 关闭所有目标服务器连接
+ */
+const closeAllTargetSocket = function () {
+    for (let [, value] of MEDIA_SERVER_POOLS) {
+        value.end();
+    }
+};
+
+const proxy = net.createServer(function (socket) {
+    socket.on('data', function (data) {
+        stick.putData(data);
+    });
+
+    stick.onData(function (data) {
+        const totalHeadLen = HEAD_LEN + IDENTIFIER_LEN;
+
+        // 解析包头长度
+        const head = Buffer.alloc(HEAD_LEN);
+        data.copy(head, 0, 0, HEAD_LEN);
+        const dataLen = head.readInt32BE() - IDENTIFIER_LEN;
+
+        // 解析标识
+        const identifier = Buffer.alloc(IDENTIFIER_LEN);
+        data.copy(identifier, 0, HEAD_LEN, totalHeadLen);
+
+        // 解析数据包内容
+        const body = Buffer.alloc(dataLen);
+        data.copy(body, 0, totalHeadLen, head.readInt32BE() + HEAD_LEN);
+
+        const mediaId = identifier.readInt16BE() + '';
+        console.log('data length: ' + dataLen);
+        console.log('identifier content: ' + mediaId);
+        // console.log('body content: ' + body.toString('hex'));
+
+        if (MEDIA_SERVER_POOLS.has(mediaId)) {
+            const mediaServer = MEDIA_SERVER_MAP.get(mediaId);
+            console.log('matched media server: %s:%s', mediaServer.host, mediaServer.port);
+            const targetSocket = MEDIA_SERVER_POOLS.get(mediaId);
+            /** 测试代码START: 正式环境为一个代理网关对应多个媒体服务  */
+            if (targetSocket.listeners('data').length === 0) {
+                targetSocket.on('data', function (chunk) {
+                    // TODO 向客户端回写需要实现推送一样的逻辑
+                    socket.write(chunk);
+                });
+            }
+            if (targetSocket.listeners('close').length === 0) {
+                targetSocket.on('close', function () {
+                    socket.end();
+                });
+            }
+            /** 测试代码END */
+            targetSocket.write(body);
+        } else {
+            console.log('not match media server');
+        }
+    });
+
+    socket.on('close', function () {
+        closeAllTargetSocket();
+        console.log('client disconnected');
+    });
+
+    socket.on('error', function (error) {
+        console.log(`error:客户端异常断开: ${error}`);
+    });
+});
+
+proxy.on('error', function (err) {
+    throw err;
+});
+
+proxy.listen(LOCAL_PORT, function () {
+    console.log('Zone2 proxy listening on ' + LOCAL_PORT);
+});