如何使用 Node 和 C# 通过 TCP 将简单的 UInt 发送到客户端

How to send a simple UInt over TCP to Client with Node and C#

我正在使用 Node 服务器向我在 Unity 中的客户端发送一个简单的 16 位 Int。 Unity客户端有C#客户端脚本连接节点服务器,读取值。

我使用的代码传递了一个字符串,所以我将 Int 转换为一个字符串,然后在客户端再次返回。我宁愿将值作为 UInt16 传递...首先,这样效率更高吗?以及我如何传递它并在客户端转换它?

节点代码如下:

server.on('connection', function(socket) {
    console.log('A new connection has been established.');

    // Now that a TCP connection has been established, the server can send data to
    // the client by writing to its socket.
    
    if (sensor != null){
        sensor.on("change", value => {
            socket.write(value.toString());
        });
    }

    // The server can also receive data from the client by reading from its socket.
    socket.on('data', function(chunk) {
        console.log(`Data received from client: ${chunk.toString()}`);
    });

    // When the client requests to end the TCP connection with the server, the server
    // ends the connection.
    socket.on('end', function() {
        console.log('Closing connection with the client');
    });

    // Don't forget to catch error, for your own sake.
    socket.on('error', function(err) {
        console.log(`Error: ${err}`);
    });
});

这是 Unity 中的 C# 代码:

socketConnection = new TcpClient("localhost", 8080);
            Byte[] bytes = new Byte[1024];
            while (true)
            {
                using (NetworkStream stream = socketConnection.GetStream())
                {
                    int length;
                    // Read incoming stream into byte array
                    while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
                    {
                        var incomingData = new byte[length];

                        //Debug.Log(incomingData.GetValue(0));

                        //int value = Convert.ToUInt16(incomingData);
                        //Debug.Log(value);

                        Array.Copy(bytes, 0, incomingData, 0, length);
                        // Convert byte array to string message.                        
                        string serverMessage = Encoding.ASCII.GetString(incomingData);
                        int value = Int16.Parse(serverMessage);
                        Debug.Log(value);
                    }
                }
            }

我需要做一些重构,但一般来说,这可以通过 String。通过 UInt16 无效。非常感谢任何帮助。

I would rather just pass the value as a UInt16 ... firstly, is this more efficient?

确实如此 - 但使用基于文本的协议而不是二进制协议有很多优点:即,调试和检查基于文本的协议比二进制协议容易得多。 HTTP 是基于文本的,例如 (HTTP/2 二进制协议,但这只是因为 Google 想要 超级优化 这在他们的规模上是有意义的,但对于绝大多数使用基于文本的协议的应用程序来说更有意义)。

and how do I do pass it and convert it on the client?

难度:

  • 在 JavaScript 中使用特定的数字类型更难,因为 JavaScript 只有 number 类型,它甚至不是整数类型。 JavaScript [确实有类型缓冲区][1] 但这是一个高级主题。

  • 在处理多字节二进制整数时,您需要处理多字节值的“网络排序”,也称为 endianness。大多数 C#.NET 环境都是“小端”,但惯例是始终使用大端格式 在线

  • TCP 仅在系统认为最佳时才发送数据包(因为每个 TCP 数据包都有很多开销),但默认情况下发送单个 2 字节值直到系统已经缓冲了很多 2 字节的值——否则你将不得不强制刷新网络堆栈。请注意,使用基于文本的协议也有同样的问题,但至少使用基于文本的协议可以减少概念浪费。

对于发送单个 16 位整数值 - 您确实最好坚持使用文本协议。

...但如果你坚持:

server.on('connection', function(socket) {
    console.log('A new connection has been established.');

    if (sensor != null){
        sensor.on("change", value => {
            
            if( typeof value !== 'number' ) {
                throw new Error( "value must be a number." );
            }
            if( value < 0 || value > 65535 || Math.floor( value ) !== value ) {
                throw new Error( "value must be a 16-bit unsigned integer." );
            }

            const buffer = new Uint8Array( 2 );

            const hi = ( value >> 8 ) & 0xFF;
            const lo = ( value      ) & 0xFF;
            buffer.set( [ hi, lo ] ); // Big-Endian Order.

            const didFlush = socket.write( buffer );
            if( !didFlush )  console.log('Data did not send immediately.');
        });
    }

    // [...]

});
TcpClient tc = new TcpClient("localhost", 8080); // A TcpClient is not a Socket. It encapsulates a Socket.

Byte[] buffer = new Byte[4096]; // Use 4K as a network buffer size.
while (true)
{
    using( NetworkStream stream = tc.GetStream() )
    {
        Int32 read;
        while( ( read = stream.Read( buffer, 0, buffer.Length ) ) != 0 )
        {
            if( read % 2 != 0 )
            {
                // TODO: Handle the case where a 16-bit value is half-sent.
                continue;
            }
            
            // It is highly unlikely that each packet will contain only 2 bytes, instead it's more likely a sequence of 16-bit integers will be sent all-at-once, so we read each of them in 2-byte steps from the buffer:
            for( Int32 idx = 0; idx < read; idx += 2 )
            {
                Byte hi = buffer[ idx + 0 ];
                Byte lo = buffer[ idx + 1 ];

                UInt16 value = ( hi << 8 ) | ( lo );
                Debug.Log( value );
            }   
        } // while
    } // using
}


  [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray