Node implements TCP&UDP speed test web page

In the past few days, the team leader asked me to write a mobile phone application on the front end to test the speed and packet loss rate of TCP/UDP. But there is a problem here, TCP/UDP is not supported by H5, so I thought of local service as TCP/UDP and WS, TCP/UDP service is used for testing with remote server, WS pushes data to web pages. The following is the specific implementation.

1. Remote server

1. to achieve

The implementation on the server side is relatively simple. Use the net and dgram modules that come with node to create a TCP/UDP server, and then return the data sent by the client to the client.

The TCP server is created as follows:

const net = require('net');

// 创建TCP server
const tcpServer = net.createServer((client /*net.Socket*/) => {
    
    
  console.log('tcp client connected');
  client.setEncoding('utf-8');
  client.pipe(client); // pipe会将收到的消息复制一份发送出去
  client.on('end', () => {
    
    
    console.log('tcp client disconnected');
  });
});
// 错误处理
tcpServer.on('error', (err) => {
    
    
  console.log(err.message);
  tcpServer.close();
});
// 监听5002端口,可改
tcpServer.listen(5002, () => {
    
    
  const serverInfo = tcpServer.address();
  console.log(`tcp server listening ${
      
      serverInfo.address}:${
      
      serverInfo.port} `);
});

The UDP server is created as follows:

const dgram = require('dgram');
//创建 udp server
const udpServer = dgram.createSocket('udp4');
udpServer.on('listening', function () {
    
    
  const serverInfo = udpServer.address();
  console.log(`udp server linstening ${
      
      serverInfo.address}:${
      
      serverInfo.port}`);
})
//接收消息
udpServer.on('message', function (msg, rinfo) {
    
    
  udpServer.send(msg, 0, msg.length, rinfo.port, rinfo.address); //将接收到的消息返回给客户端
})
//错误处理
udpServer.on('error', function (err) {
    
    
  console.log('some error on udp server.')
  udpServer.close();
})
udpServer.bind(5001); // 绑定端口5001,可改
2. Deployment

The server deployment environment requires node version > 0.6. There are many tutorials on how to install the node environment on the Internet, so I won’t repeat them here.

After installing the node environment, you need to use pm2 to manage the node service, and use it npm install -g pm2to install pm2.

After that is the normal deployment process, use frtp (or other) to upload the project to the server, and pm2 start xxx.js to start the service. After the startup is complete, it looks like this:

insert image description here

You can also use pm2 log xxx to view the print information of the service. as follows:

insert image description here

After ensuring that the service is running, configure the firewall to open ports 5001/udp and 5002/tcp. You need to ensure that the port protocol is correct, otherwise you will not receive messages. There are online tutorials on how to configure, please choose the tutorial of your own linux distribution to view. The configuration of the Alibaba Cloud server is different, and it needs to be manually opened in the Alibaba Cloud console (there is no firewall in the Alibaba Cloud server).

2. Local client

What the client mainly does is to repeatedly send messages to the server, and each message contains sending timestamp information. When receiving the message returned by the server, the link delay and other information are calculated by comparing the sending timestamp with the local timestamp.

1. TCP client implementation
const net = require('net');
let tcpStart = false; // 连接状态
let tcpFreq = 0; // 发包速率
let tcpSize = 0; // 每包大小
let tcpNum = 0; // 每轮数据包数量
let tcpSend = 0; // 已发包数
let tcpRecv = 0; // 已收包数
let tcpDelay = Infinity; // 链路时延
let tcpInterval; // tcp发包定时器
// 创建客户端
const tcpClient = new net.Socket();
tcpClient.setEncoding('utf8');
// 连接服务器,第一个参数为端口号,第二个参数为服务器ip,第三个参数为连接的回调
tcpClient.connect(5002,'xxx.xxx.xxx.xxx',function () {
    
    
  console.log('tcp client connected');
});
// 接收数据事件
tcpClient.on('data',function (data) {
    
    
  const recvMsg = data.toString();
  // 因为tcp协议需要粘包拆包,所以有以下这段。粘包拆包有很多解决方案,由于本人不是研究这个的,就选择了最简单的占位符处理方案。
  const msg = recvMsg.replace(/s/g, ""); // 清除占位符
  for(let i = 0; i < msg.length; i += 6) {
    
     // 拆包
    const startTime = Number.parseInt(msg.slice(i, i+6));
    const endTime = Number.parseInt(new Date().getTime().toString().slice(-6));
    tcpDelay = endTime - startTime;
    tcpRecv++;
  }
});
//监听与服务端连接的错误事件
tcpClient.on('error',function (err) {
    
    
  console.log('tcp client error, error code: ', err.code);
  // 修改连接状态,重置收发数量
  tcpStart = false;
  tcpSend = 0;
  tcpRecv = 0;
  tcpClient.destroy();
})

The TCP protocol itself is a streaming protocol. In order to ensure the connection rate and quality, TCP will glue small packets when the buffer remains, and split large packets to speed up the transmission rate, so it needs to be dealt with. For specific reasons and solutions, please refer to: https://www.jianshu.com/p/9e3cefc21ca9.

2. UDP client implementation
const dgram = require('dgram');
//创建 udp server
const udpClient = dgram.createSocket('udp4');
udpClient.bind(5678); // 绑定端口
let udpStart = false; // 连接状态
let udpFreq = 0; // 发包速率
let udpSize = 0; // 每包大小
let udpNum = 0; // 每轮数据包数量
let udpSend = 0; // 已发包数
let udpRecv = 0; // 已收包数
let udpDelay = Infinity; // 链路时延
let udpInterval; // udp发包定时器
//接收消息
udpClient.on('message', function (msg, rinfo) {
    
    
  // 截取时间戳,计算链路时延
  const startTime = Number.parseInt(msg.toString("utf8", 0, 6));
  const endTime = Number.parseInt(new Date().getTime().toString().slice(-6));
  udpDelay = endTime - startTime;
  udpRecv++;
})
//错误处理
udpClient.on('error', function (err) {
    
    
  console.log('some error on udp client.')
  // 修改链路状态,重置收发数量
  udpStart = false;
  udpSend = 0;
  udpRecv = 0;
  udpClient.close();
})
3. Send packets
setInterval(() => {
    
    
  // tcp定时器
  if (tcpStart && !tcpInterval) {
    
    
    tcpInterval = setInterval(() => {
    
    
      const buffer = Buffer.alloc(tcpSize, "s");
      const startTime = new Date().getTime().toString().slice(-6);
      buffer.write(startTime);
      tcpClient.write(buffer);
      tcpSend++;
    }, 1000 / tcpNum);
  } else if (tcpInterval) {
    
    
    clearInterval(tcpInterval);
    tcpInterval = undefined;
  }
  // udp定时器
  if (udpStart && !udpInterval) {
    
    
    udpInterval = setInterval(() => {
    
    
      const buffer = Buffer.alloc(udpSize);
      const startTime = new Date().getTime().toString().slice(-6);
      buffer.write(startTime);
      udpClient.send(buffer, 0, buffer.length, 5001, "xxx.xxx.xxx.xxx");
      udpSend++;
    }, 1000 / udpNum);
  } else if (udpInterval) {
    
    
    clearInterval(udpInterval);
    udpInterval = undefined;
  }
}, 1000)
4. Front-end data push

Since the functions to be implemented are relatively simple, I chose the smallest ws library. If you have other needs, you can also choose socket.io.

const WebSocketServer = require('ws').Server;
wsServer = new WebSocketServer({
    
     port: 8081 });//服务端口8181
wsServer.on('connection', function (ws) {
    
    
  console.log('WS connected');
  ws.on('message', function (message) {
    
    
    const cmd = JSON.parse(message.toString());
    // 若收到tcp开始发送的消息,开始发包并计算包大小、每轮数量等
    if (cmd.topic === "tcpStart") {
    
    
      tcpStart = true;
      tcpFreq = cmd.freq;
      tcpSize = Math.min(tcpFreq, 1024);
      tcpNum = Math.ceil(tcpFreq / tcpSize);
      console.log(tcpStart, tcpSize, tcpNum);
    } else if (cmd.topic === "tcpEnd") {
    
    
      // 结束发包并在5秒后重置收发数量(避免链路时延导致的收数据包统计不到的问题)
      tcpStart = false;
      setTimeout(() => {
    
    
        tcpSend = 0;
        tcpRecv = 0;
      }, 5000);
    }
    // udp同tcp
    if (cmd.topic === "udpStart") {
    
    
      udpStart = true;
      udpFreq = cmd.freq;
      udpSize = Math.min(udpFreq, 1024);
      udpNum = Math.ceil(udpFreq / udpSize);
      console.log(udpStart, udpSize, udpNum);
    } else if (cmd.topic === "udpEnd") {
    
    
      udpStart = false;
      setTimeout(() => {
    
    
        udpSend = 0;
        udpRecv = 0;
      }, 5000);
    }
  });
  // 每秒向前端推送实时数据
  setInterval(() => {
    
    
    const message = {
    
    };
    if (tcpStart || tcpSend) {
    
    
      message.tcp = {
    
     send: tcpSend, recv: tcpRecv, size: tcpSize, delay: tcpDelay + 'ms' };
    }
    if (udpStart || udpSend) {
    
    
      message.udp = {
    
     send: udpSend, recv: udpRecv, size: udpSize, delay: udpDelay + 'ms' };
    }
    ws.send(JSON.stringify(message));
  }, 1000)
});

3. Front-end page

Since the functions of the front-end page are relatively simple, the native implementation is used. The specific implementation is as follows (JS):

window.onload = function() {
    
    
  // 状态初始化
  let tcpOnMessage = false;
  let udpOnMessage = false;

  // 获取展示元素DOM节点
  const tcpFreqDOM = document.getElementById("tcpFreq");
  const tcpSendDOM = document.getElementById("tcpSend");
  const tcpRecvDOM = document.getElementById("tcpRecv");
  const tcpSizeDOM = document.getElementById("tcpSize");
  const tcpDelayDOM = document.getElementById("tcpDelay");
  const udpFreqDOM = document.getElementById("udpFreq");
  const udpSendDOM = document.getElementById("udpSend");
  const udpRecvDOM = document.getElementById("udpRecv");
  const udpSizeDOM = document.getElementById("udpSize");
  const udpDelayDOM = document.getElementById("udpDelay");

  // 创建WS连接
  const ws = new WebSocket("ws://localhost:8081");
  ws.onopen = function (e) {
    
    
    console.log("ws client connected");
  }
  ws.onmessage = function (e) {
    
    
    const data = JSON.parse(e.data);
    // 接收数据时若仍处于收发状态,渲染至页面
    if (tcpOnMessage) {
    
    
      tcpSendDOM.innerText = data.tcp.send;
      tcpRecvDOM.innerText = data.tcp.recv;
      tcpSizeDOM.innerText = data.tcp.size;
      tcpDelayDOM.innerText = data.tcp.delay;
    }
    if (udpOnMessage) {
    
    
      udpSendDOM.innerText = data.udp.send;
      udpRecvDOM.innerText = data.udp.recv;
      udpSizeDOM.innerText = data.udp.size;
      udpDelayDOM.innerText = data.udp.delay;
    }
  }

  // 获取用户输入的测试速率
  let tcpFreq = 0;
  tcpFreqDOM.addEventListener("input", (event) => {
    
    
    tcpFreq = event.target.value;
  })
  let udpFreq = 0;
  udpFreqDOM.addEventListener("input", (event) => {
    
    
    udpFreq = event.target.value;
  })

  // 获取button DOM
  const tcpStartDOM = document.getElementById("tcpStart");
  const tcpEndDOM = document.getElementById("tcpEnd");
  const udpStartDOM = document.getElementById("udpStart");
  const udpEndDOM = document.getElementById("udpEnd");

  // 开始测试时发送开始事件和测试速率
  tcpStartDOM.addEventListener("click", () => {
    
    
    if (tcpFreq) {
    
    
      tcpOnMessage = true;
      ws.send(JSON.stringify({
    
    
        topic: "tcpStart",
        freq: tcpFreq
      }));
    } else {
    
    
      window.alert("请输入tcp测试速率")
    }
  })
  tcpEndDOM.addEventListener("click", () => {
    
    
    ws.send(JSON.stringify({
    
    
      topic: "tcpEnd"
    }));
    tcpStartDOM.disabled = true;
    tcpEndDOM.disabled = true;
    tcpEndDOM.innerText = "正在统计...";
    // 5s后真正更改传输状态,避免链路时延导致的不统计
    setTimeout(() => {
    
    
      tcpStartDOM.disabled = false;
      tcpEndDOM.disabled = false;
      tcpEndDOM.innerText = "结束测试";
      tcpOnMessage = false;
      window.alert("tcp测试已停止");
    }, 5000);
  })
  udpStartDOM.addEventListener("click", () => {
    
    
    if (udpFreq) {
    
    
      udpOnMessage = true;
      ws.send(JSON.stringify({
    
    
        topic: "udpStart",
        freq: udpFreq
      }));
    } else {
    
    
      window.alert("请输入udp测试速率");
    }
  })
  udpEndDOM.addEventListener("click", () => {
    
    
    ws.send(JSON.stringify({
    
    
      topic: "udpEnd"
    }));
    udpStartDOM.disabled = true;
    udpEndDOM.disabled = true;
    udpEndDOM.innerText = "正在统计...";
    setTimeout(() => {
    
    
      udpStartDOM.disabled = false;
      udpEndDOM.disabled = false;
      udpEndDOM.innerText = "结束测试";
      udpOnMessage = false;
      window.alert("udp测试已停止");
    }, 5000);
  })
}

At this point, the entire TCP/UDP speed measurement system is completed, and the running results are as follows:

During sending and receiving:

insert image description here

Sending and receiving ends:

insert image description here

Guess you like

Origin blog.csdn.net/qq_41575208/article/details/119637400