【问题标题】:Sending packets over tcp network通过 tcp 网络发送数据包
【发布时间】:2019-01-20 11:48:59
【问题描述】:

我正在尝试在客户端之间发送数据,当通过 tcp 发送数据时,它是一个流,而不是“一发一收”功能。因此我为网络流写了一个小包装器。它有效,但我错过了什么吗?这是解决我的问题的正确方法吗?

我的网络知识不是很好。

(我的async知识也很有限,第一次用这个,如果有什么不对的地方请指教。)

为了完整起见,我还包括了我的测试代码:

主要:

    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        HandlerServer();
        HandleClients();

        while (true)
            Thread.Sleep(100);
    }

处理函数:

    private static async void HandleClients()
    {
        using (TcpClient client = new TcpClient())
        {
            await client.ConnectAsync(IPAddress.Parse("127.0.0.1"), 8888);
            using (TcpMessageStream stream = new TcpMessageStream(client))
            {
                while (true)
                    await stream.SendAsync(Encoding.ASCII.GetBytes(Console.ReadLine()));
            }
        }
    }

    private async static void HandlerServer()
    {
        TcpListener server = new TcpListener(IPAddress.Any, 8888);

        server.Start();

        using (TcpClient cl = await server.AcceptTcpClientAsync())
        using (TcpMessageStream stream = new TcpMessageStream(cl))
        {
            Console.WriteLine("Client connected!!");
            while (true)
                Console.WriteLine("Received: " + Encoding.ASCII.GetString(await stream.ReceiveAsync()));
        }
    }

创建的类:

class TcpMessageStream : IDisposable
{
    NetworkStream _stream;
    public TcpMessageStream(TcpClient tcpClient)
    {
        _stream = tcpClient.GetStream();
    }

    public async Task SendAsync(byte[] data)
    {
        byte[] prefix = BitConverter.GetBytes(data.LongLength);
        await _stream.WriteAsync(prefix);
        await _stream.WriteAsync(data);
    }

    public async Task<byte[]> ReceiveAsync()
    {
        byte[] buffer = new byte[8];
        int bytesRead = 0;
        do
        {
            bytesRead += await _stream.ReadAsync(buffer, bytesRead, 8 - bytesRead);

        } while (bytesRead < 8);

        var dataLength = BitConverter.ToInt64(buffer);

        byte[] messageBuffer = new byte[dataLength];

        bytesRead = 0;
        do
        {
            bytesRead += await _stream.ReadAsync(messageBuffer, bytesRead, messageBuffer.Length - bytesRead);

        } while (bytesRead < messageBuffer.Length);

        return messageBuffer;
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                _stream.Dispose();
            }

            // Clean up unmanaged resources here.
            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~TcpMessageStream()
    {
        Dispose(false);
    }
}

【问题讨论】:

  • 异步代码实际上不在单独的线程上运行。因此,您的 while 循环将阻塞当前线程,并且任何其他“异步”代码将无法运行。考虑启动一个线程来处理客户端和服务器代码。
  • @JessedeWit 但是,while 循环中的“等待”不是将“线程”返回给等待时需要执行的其他代码吗?
  • 理论上是的。但是它遇到的其他代码将无法执行,因为当前线程已被占用/工作。 Console.Readline() 也会阻塞当前线程,这意味着只有当您向客户端发送数据包时,您的线程才会空闲。您的服务器端代码可能没问题,因为ReceiveAsync 将释放当前线程。
  • @JessedeWit 啊,我明白了,谢谢!我会在我的实际项目中这样做。但理论上如果Console.Readline() 也是异步的,那么这个示例代码就可以了吗?
  • 是的,我认为应该。 (如果您使用 await Task.Delay() 而不是 Thread.Sleep(),这也是阻塞的)请注意,您将无法捕获任何异常,但是您可能希望在异步函数中添加一些异常处理。

标签: c# networking packet


【解决方案1】:

一般来说,这是个好主意。您已经创建了一个简单的协议,该协议在数据之前写入一个长度前缀,以便您知道要读取多少。这是非常常见、简单且高效的。

您可以大大简化代码。 BinaryReader 具有读写整数和字节数组的方法。您可以读取精确大小的字节数组。 BinaryReader 为你做循环。

如果您不想将BinaryReader 用于教育目的,那么请让自己成为一个读取确切字节数的辅助函数。

我会将TcpMessageStream 建立在Stream 上,而不是TcpClient。这使它更通用。

异步使用是正确的。

您使用的 dispose 模式在这里没有用处。终结器什么都不做,也永远不会有任何继承者。不需要这个布尔值disposed 标志。编写一个普通的void Dispose() 方法,它只做需要的事情。

总而言之,这是一个非常好的工作。网络代码非常难。

如果这是实际应用程序的一部分,请考虑完全不要编写自己的网络协议。尝试使用 HTTP 或 websockets。在抽象堆栈中尽可能高。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-17
    • 1970-01-01
    • 2013-09-16
    • 2018-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多