参照:JAVA基于Netty实现内网穿透功能【设计实践】
代码实现比较清晰,经测试可以正常使用。
本客户端源码通过适配Java服务端源码实现。
// 服务器-客户端 通信报文格式:
// 4字节长度+1字节type+x字节data,长度为数据的总长度=4+1+x
// Uint8Array的每个元素为1个字节
var net = require('net');
const client = new net.Socket();
// 服务器端口
const serverPort = 9000;
// 需要被转发的本地服务的端口
const localPort = 8000;
// 客户端连接服务器
client.connect(serverPort, '127.0.0.1', function () {
client.write(new Uint8Array([0, 0, 0, 5, 1]));
setHeartBeat(client)
});
// 计算报文长度
function getDataLen(data) {
return data[0] * 16 * 16 * + data[1] * 16 * 16 + data[2] * 16 + data[3];;
}
// 16进制字符串转10进制整数
function hex2int(hex) {
if (hex == null || hex == "0") {
return 0;
}
var len = hex.length, a = new Array(len), code;
for (var i = 0; i < len; i++) {
code = hex.charCodeAt(i);
if (48 <= code && code < 58) {
code -= 48;
} else {
code = (code & 0xdf) - 65 + 10;
}
a[i] = code;
}
return a.reduce(function (acc, c) {
acc = 16 * acc + c;
return acc;
}, 0);
}
// 获取16进制字符串的某一位数字
function getNumFromHex(hexStr, index) {
if (index < 0 || index >= hexStr.length) {
return 0;
}
return hexStr[index];
}
// 为报文设置长度属性
function setLen(len, uint8Array) {
// 长度转为16进制
var str16 = len.toString(16);
// 16进制长度,拆成4字节存储
uint8Array[0] = hex2int(getNumFromHex(str16, str16.length - 8) + getNumFromHex(str16, str16.length - 7));
uint8Array[1] = hex2int(getNumFromHex(str16, str16.length - 6) + getNumFromHex(str16, str16.length - 5));
uint8Array[2] = hex2int(getNumFromHex(str16, str16.length - 4) + getNumFromHex(str16, str16.length - 3));
uint8Array[3] = hex2int(getNumFromHex(str16, str16.length - 2) + getNumFromHex(str16, str16.length - 1));
}
// 客户端接收服务器数据
client.on('data', function (data) {
var type = data[4];
var len = getDataLen(data);
if (type == 0) {
return;
}
console.log('新数据长度:' + len);
console.log('新数据type:' + type);
console.log("新数据:" + data.toString());
// 客户端与服务器成功连接
if (type == 1) {
const worker = new net.Socket();
worker.connect(serverPort, '127.0.0.1', function () {
var uu = new Uint8Array(data);
worker.write(uu);
setHeartBeat(worker)
});
worker.on('data', function (data) {
var type = data[4];
// 处理server请求
if (type == 2) {
handleRequest(data, worker);
}
});
worker.on('close', function () {
worker.write(new Uint8Array([0, 0, 0, 5, 9]));
worker.destroy();
});
worker.on('error', function (data) {
console.log('worker error:' + data.toString());
});
}
});
function handleRequest(data, worker) {
const browser = new net.Socket();
browser.connect(localPort, '127.0.0.1', function () {
// printU8(data.subarray(5));
browser.write(new Uint8Array(data.subarray(5)));
});
browser.on('data', function (data) {
// console.log('browser:' + data.toString());
console.log('browserLen:' + data.length);
// 转发内网数据到server
var dataLen = data.length;
var uint8Array = new Uint8Array(dataLen + 5);
// 设置报文长度
setLen(dataLen + 5, uint8Array);
uint8Array[4] = 2;
for (var i = 5; i < uint8Array.length; i++) {
uint8Array[i] = data[i - 5];
}
console.log("worder return data:");
printU8(uint8Array);
worker.write(uint8Array);
});
browser.on('close', function () {
browser.destroy();
});
browser.on('error', function (data) {
console.log('browser error:' + data.toString());
});
}
// 打印Uint8Array对象
function printU8(uint8Array) {
var str = '';
for (var i = 0; i < 5; i++) {
str = str + uint8Array[i] + ",";
}
for (var i = 5; i < uint8Array.length; i++) {
str += String.fromCharCode(uint8Array[i]);
}
console.log("u8:" + str);
}
// 为client配置心跳
function setHeartBeat(socketClient) {
setInterval(function () {
socketClient.write(new Uint8Array([0, 0, 0, 5, 0]));
}, 10000);
}
client.on('error', function (data) {
console.log('error:' + data.toString());
});
client.on('close', function () {
client.write(new Uint8Array([0, 0, 0, 5, 9]));
client.destroy();
});