Отправка и получение данных в TCP. Двунаправленная связь

Последнее обновление: 05.11.2022

В прошлой теме была рассмотрена однонаправленная связь между клиентом и сервером: когда либо сервер отправляет данные, а клиент получае, либо, наоборот, клиент отправляет, а сервер получает. Рассмотрим более сложный пример, когда и клиент, и сервер одновременно отправляют и получают данные.

Пример с TcpListener и TcpClient

Определение сервера

Пусть код сервера выглядит следующим образом:

using System.Net;
using System.Net.Sockets;
using System.Text;

var tcpListener = new TcpListener(IPAddress.Any, 8888);
var words = new Dictionary<string, string>()
{
    {"red", "красный" },
    {"blue", "синий" },
    {"green", "зеленый" }
};
try
{
    tcpListener.Start();    // запускаем сервер
    Console.WriteLine("Сервер запущен. Ожидание подключений... ");

    while (true)
    {
        // получаем подключение в виде TcpClient
        using var tcpClient = await tcpListener.AcceptTcpClientAsync();
        // получаем объект NetworkStream для взаимодействия с клиентом
        var stream = tcpClient.GetStream();
        // буфер для входящих данных
        var response = new List<byte> ();
        int bytesRead = 10;
        while(true)
        {
            // считываем данные до конечного символа
            while ((bytesRead = stream.ReadByte()) != '\n')
            {
                // добавляем в буфер
                response.Add((byte)bytesRead);
            }
            var word = Encoding.UTF8.GetString(response.ToArray());

            // если прислан маркер окончания взаимодействия,
            // выходим из цикла и завершаем взаимодействие с клиентом
            if (word == "END") break;

            Console.WriteLine($"Запрошен перевод слова {word}");
            // находим слово в словаре и отправляем обратно клиенту
            if (!words.TryGetValue(word, out var translation)) translation = "не найдено в словаре";
            // добавляем символ окончания сообщения 
            translation += '\n';
            // отправляем перевод слова из словаря
            await stream.WriteAsync(Encoding.UTF8.GetBytes(translation));
            response.Clear();
        }
    }
}
finally
{
    tcpListener.Stop();
}

В данном случае мы предполагаем, что сервер будет выполнять роль своего рода словаря - получать от клиента слово и отправлять обратно его перевод. Для хранения данных определен тестовый словарь words:

var words = new Dictionary<string, string>()
{
    {"red", "красный" },
    {"blue", "синий" },
    {"green", "зеленый" }
};

При получении и обработке запросов сервер применяет простой прокотол из двух правил: каждое отдельное сообщение клиенту и серверу должно оканчиваться символом \n, а маркер окончания подключения должен представлять строку "END". В соответствии с этими правилами сервер сначала считывает запрос сервера и получаем слово, для которого надо выполнить перевод:

while ((bytesRead = stream.ReadByte()) != '\n')
{
    response.Add((byte)bytesRead);
}
var word = Encoding.UTF8.GetString(response.ToArray());

Если отправлена строка "END", выходим из цикла и тем самым прекращаем работу с текущим клиентом:

if (word == "END") break;

Иначе находим в словаре перевод слова (при его наличии) и отправляем клиенту:

if (!words.TryGetValue(word, out var translation)) translation = "не найдено в словаре";
translation += '\n';
await stream.WriteAsync(Encoding.UTF8.GetBytes(translation));

При этом опять же в соответствии с условным протоколом при отправке добавляем к сообщению символ \n, благодаря чему клиент будет знать, что это окончание слова.

Определение клиента

Теперь создадим тестового клиента для выше определенного сервера:

using System.Net.Sockets;
using System.Text;

using TcpClient tcpClient = new TcpClient();
await tcpClient.ConnectAsync("127.0.0.1", 8888);

// слова для отправки для получения перевода
var words = new string[] { "red", "yellow", "blue" };
// получаем NetworkStream для взаимодействия с сервером
var stream = tcpClient.GetStream();

// буфер для входящих данных
var response = new List<byte>();
int bytesRead = 10; // для считывания байтов из потока
foreach (var word in words)
{
    // считыванием строку в массив байт
    // при отправке добавляем маркер завершения сообщения - \n
    byte[] data = Encoding.UTF8.GetBytes(word + '\n');
    // отправляем данные
    await stream.WriteAsync(data);

    // считываем данные до конечного символа
    while ((bytesRead = stream.ReadByte()) != '\n')
    {
        // добавляем в буфер
        response.Add((byte)bytesRead);
    }
    var translation = Encoding.UTF8.GetString(response.ToArray());
    Console.WriteLine($"Слово {word}: {translation}");
    response.Clear();
}

// отправляем маркер завершения подключения - END
await stream.WriteAsync(Encoding.UTF8.GetBytes("END\n"));
Console.WriteLine("Все сообщения отправлены");

Здесь для теста определяем массив из трех слов, перевод которых мы собираемся получить:

var words = new string[] { "red", "yellow", "blue" };

Пробегаемся по этому массиву и отправляем каждое слово серверу:

foreach (var word in words)
{
    // при отправке добавляем маркер завершения сообщения - \n
    byte[] data = Encoding.UTF8.GetBytes(word + '\n');
    await stream.WriteAsync(data);

Опять же в соответствии с принятым нами протоколом каждое слово завершается символом \n.

Затем считываем ответ сервера и получаем перевод слова:

while ((bytesRead = stream.ReadByte()) != '\n')
{   
    response.Add((byte)bytesRead);
}
var translation = Encoding.UTF8.GetString(response.ToArray());

Для завершения посылаем сервер маркер окончания подключения:

await stream.WriteAsync(Encoding.UTF8.GetBytes("END\n"));

Запустим сервер и клиент. При получении запросов консоль сервера отобразит запрошенные слова на перевод:

Сервер запущен. Ожидание подключений...
Запрошен перевод слова red
Запрошен перевод слова yellow
Запрошен перевод слова blue

А консоль клиента отобразит полученный от сервера перевод запрошенных слов:

Слово red: красный
Слово yellow: не найдено в словаре
Слово blue: синий
Все сообщения отправлены

Пример на сокетах

Тот же пример только на чистых сокетах с использованием класса Socket. Код сервера:

using System.Net;
using System.Net.Sockets;
using System.Text;

using Socket tcpListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var words = new Dictionary<string, string>()
{
    { "red", "красный" },
    { "blue", "синий" },
    { "green", "зеленый" },
};
try
{
    tcpListener.Bind(new IPEndPoint(IPAddress.Any, 8888));
    tcpListener.Listen();    // запускаем сервер
    Console.WriteLine("Сервер запущен. Ожидание подключений... ");

    while (true)
    {
        // получаем подключение в виде TcpClient
        using var tcpClient = await tcpListener.AcceptAsync();

        // буфер для накопления входящих данных
        var response = new List<byte>();
        // буфер для считывания одного байта
        var bytesRead = new byte[1];
        while (true)
        {
            // считываем данные до конечного символа
            while (true)
            {
                var count = tcpClient.Receive(bytesRead);
                // смотрим, если считанный байт представляет конечный символ, выходим
                if (count == 0 || bytesRead[0] == '\n') break;
                // иначе добавляем в буфер
                response.Add(bytesRead[0]);
            }
            var word = Encoding.UTF8.GetString(response.ToArray());
            // если прислан маркер окончания взаимодействия,
            // выходим из цикла и завершаем взаимодействие с клиентом
            if (word == "END") break;

            Console.WriteLine($"Запрошен перевод слова {word}");
            // находим слово в словаре и отправляем обратно клиенту
            if (!words.TryGetValue(word, out var translation)) translation = "не найдено в словаре";
            // добавляем символ окончания сообщения 
            translation += '\n';
            // отправляем перевод слова из словаря
            await tcpClient.SendAsync(Encoding.UTF8.GetBytes(translation));
            response.Clear();
        }
    }
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

И код клиента:

using System.Net.Sockets;
using System.Text;

// слова для отправки для получения перевода
var words = new string[] { "red", "yellow", "blue" };

using var tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
    await tcpClient.ConnectAsync("127.0.0.1", 8888);
    // буфер для входящих данных
    var response = new List<byte>();
    foreach (var word in words)
    {
        // считыванием строку в массив байт
        // при отправке добавляем маркер завершения сообщения - \n
        byte[] data = Encoding.UTF8.GetBytes(word + '\n');
        // отправляем данные
        await tcpClient.SendAsync(data);

        // буфер для считывания одного байта
        var bytesRead = new byte[1];
        // считываем данные до конечного символа
        while (true)
        {
            var count = tcpClient.Receive(bytesRead);
            // смотрим, если считанный байт представляет конечный символ, выходим
            if (count == 0 || bytesRead[0] == '\n') break;
            // иначе добавляем в буфер
            response.Add(bytesRead[0]);
        }
        var translation = Encoding.UTF8.GetString(response.ToArray());
        Console.WriteLine($"Слово {word}: {translation}");
        response.Clear();
    }

    // отправляем маркер завершения подключения - END
    await tcpClient.SendAsync(Encoding.UTF8.GetBytes("END\n"));
    Console.WriteLine("Все сообщения отправлены");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850