Обмен данными по сети в C# (протокол TCP)

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

В .NET за работу с TCP отвечают три класса из пространства имён System.Net.Sockets.

  • Socket — обеспечивает базовый функционал TCP и UDP сокетов. В прикладных целях рекомендуется вместо класса Socket использовать классы TcpListener, TcpClient или UdpClient, которые построены на его основе;
  • TcpListener — этот класс обеспечивает функционал TCP сервера;
  • TcpClient – этот класс работает как TCP клиент. С его помощью осуществляется передача данных от клиента серверу и, как ни странно наоборот.

Рассмотрим пример сетевого взаимодействия по протоколу TCP на основе классов TcpListenet и TcpClient.

Клиентская часть

Клиентская часть использует только класс TcpClient.

По протоколу TCP мы можем передавать только двоичные данные. Поэтому после инициализации объекта класса TcpClient нужно подготовить массив байт для чтения и записи двоичных данных, а также открыть поток для передачи.

В примере ниже показан подготовительный этап для передачи данных (в массив байт преобразуется строковое сообщение передаваемое на сервер):

TcpClient client = new TcpClient(address, port);
Byte[] data = Encoding.UTF8.GetBytes(outMessage);
NetworkStream stream = client.GetStream();

То же самое, но для приёма данных:

TcpClient client = new TcpClient(address, port);
Byte[] data = new Byte[256];
NetworkStream stream = client.GetStream();

Отправка данных на сервер производится при помощи метода Write.

stream.Write(data, 0, data.Length);

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

С получением данных гораздо сложнее. Мы не знаем их истинного объёма и потому вынуждены считывать их в цикле до тех пор пока они не закончатся.

Ниже приведён пример получения данных от сервера на примере строки.

string responseData = String.Empty;
StringBuilder completeMessage = new StringBuilder();
int numberOfBytesRead = 0;
do
{
    numberOfBytesRead = stream.Read(readingData, 0,readingData.Length);
    completeMessage.AppendFormat("{0}", Encoding.UTF8.GetString(readingData, 0, numberOfBytesRead));
}
while (stream.DataAvailable);
responseData = completeMessage.ToString();

До тех пор пока в потоке есть данные (свойство DataAvailable равно true) происходит поэтапное формирование поступившей с сервера строки при помощи класса StringBuilder теми данными, что были прочитаны в буфер в ходе текущей итерации. После завершения прочтения всех данных из потока возвращается готовое строковое сообщение.

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

stream.Close();
client.Close();

Далее представлен пример, который иллюстрирует весь процесс обмена данными с сервером целиком.

Для реализации TCP сервера, на потребуется уже два класса TcpListener (для прослушивания определённого порта и управления подключения клиентов) и TcpClient (для обмена данными с подключенными клиентами).

Конструктор класса TcpListenet принимает два параметра IP адрес и номер порта.

IPAddress localAddr = IPAddress.Parse("127.0.0.1");
int port = 8888;
TcpListener server = new TcpListener(localAddr, port);

Обратит внимание, что в отличие от TcpClient здесь IP адроес представлен объектом класса IPAddress из пространства имён System.Net.

Запуск сервера в работу осужествляется при помощи метода Start.

server.Start();

После вызова метода Start запускается бесконечный цикл, в котором объект TcpListener будет ожидать подключения клиента и как только оно произойдёт при помощи метода AcceptTcpClient будет создан объект TcpClient, который позволяет производить обмен данными с подключившимся клиентом как это было описано для клиентской части.

TcpClient client = server.AcceptTcpClient();

Ниже приведён полный пример реализации TCP сервера, который получает от клиентов строковые сообщения и отправляет им ответы также в виде строки.

// Инициализация
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
int port = 8888;
TcpListener server = new TcpListener(localAddr, port);
// Запуск в работу
server.Start();
// Бесконечный цикл
while (true)
{
    try
    {
        // Подключение клиента
        TcpClient client = server.AcceptTcpClient();
        NetworkStream stream = client.GetStream();
        // Обмен данными
        try
        {
            if (stream.CanRead)
            {
                byte[] myReadBuffer = new byte[1024];
                StringBuilder myCompleteMessage = new StringBuilder();
                int numberOfBytesRead = 0;
                do
                {
                    numberOfBytesRead = stream.Read(myReadBuffer, 0, myReadBuffer.Length);
                    myCompleteMessage.AppendFormat("{0}", Encoding.UTF8.GetString(myReadBuffer, 0, numberOfBytesRead));
                }
                while (stream.DataAvailable);
                Byte[] responseData = Encoding.UTF8.GetBytes("УСПЕШНО!");
                stream.Write(responseData, 0, responseData.Length);
            }
        }
        finally
        {
                stream.Close();
                client.Close();
        }
    }
    catch
    {
        server.Stop();
        break;
    }
}

Обратите внимание, что, в отличие от клиентской части, на сервере вначале принимается входящее сообщение и лишь после этого отправляется ответ (обратный порядок действий).

Также стоит обратить внимание на то, что в случае возникновения исключения, работа сервера останавливается и бесконечный цикл завершается, чтобы в случае критической ошибки программа не «зависла».

Сетевое взаимодействие и работа программы

Обмен данными по сети может занимать значительное время. В свою очередь ожидание подключения клиента на сервере может вообще продолжаться неопределённо долго. Всё это может привести к тому, что программа окажется недоступной («зависнет»).

Чтобы этого избежать настоятельно рекомендуется работать с сетью в отельном потоке.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *