Отправка пакетов по сети TCP - PullRequest
0 голосов
/ 20 января 2019

Я пытаюсь отправить данные между клиентами, при отправке данных через tcp это поток вместо функции «один отправка - один прием». поэтому я написал небольшую оболочку для сетевого потока. Это работает, но я что-то упустил? это правильный способ решить мою проблему?

Мое знание сети не так уж велико.

(Мои асинхронные знания также очень ограничены, я впервые использую это, поэтому, если с ним что-то не так, пожалуйста, дайте мне знать.)

Для полноты я также включил свой тестовый код:

Main:

    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);
    }
}

1 Ответ

0 голосов
/ 20 января 2019

Вообще, это хорошая идея. Вы создали простой протокол, который записывает префикс длины перед данными, чтобы вы знали, сколько читать. Это очень распространено, просто и эффективно.

Вы можете значительно упростить код. BinaryReader имеет методы для чтения и записи целых чисел и байтовых массивов. Вы можете прочитать байтовый массив точного размера. BinaryReader делает цикл за вас.

Если вы не хотите использовать BinaryReader в образовательных целях, сделайте себе вспомогательную функцию, которая считывает точное количество байтов.

Я бы основывал TcpMessageStream на Stream, а не на TcpClient. Это делает его более общим.

Асинхронное использование правильное.

Шаблон утилизации, который вы использовали, здесь бесполезен. Финализатор ничего не делает, и никогда не будет наследников. Нет необходимости в этом логическом флаге disposed. Напишите обычный void Dispose() метод, который просто делает то, что нужно.

В целом это очень хорошая работа. Сетевой код очень сложный.

Если это часть реального приложения, вообще не пишите свой собственный сетевой протокол. Попробуйте использовать HTTP или веб-сокеты. Поднимитесь как можно выше в стеке абстракций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...