TcpClient / NetworkStream одновременное чтение и запись в одном экземпляре

#c# #.net-core #tcpclient

#c# #.net-ядро #tcpclient

Вопрос:

Мне нужно связаться с аппаратным обеспечением, которое принимает только 1 TCP-соединение. Но я не могу найти ни одного хорошего примера, где то же самое NetworkStream используется для одновременного чтения и записи.

В документации MSDN говорится следующее:

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

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

Я не уверен, следует ли мне:

  • просто используйте NetworkStream непосредственно для чтения с помощью myNetworkStream.ReadAsync() или используйте StreamReader ,
  • просто используйте NetworkStream непосредственно для записи с myNetworkStream.WriteAsync() или используйте StreamWriter ,
  • непосредственно запускает новый поток с while(true) для чтения при создании экземпляра TcpClient ,
  • сохраняйте глобальную ссылку на мой NetworkStream экземпляр, чтобы я мог писать в любое время…

Я далек от того, чтобы быть экспертом, когда дело доходит до TCP-соединений / сетевого взаимодействия, поэтому определенно есть вещи, которые я не до конца понимаю…

Заранее спасибо 🙂

Комментарии:

1. Это все, что вы можете делать, а можете и не делать. На самом деле это не «один правильный способ сделать это». Ситуация такого рода.

2. Не могли бы вы привести пример того, как использовать один и тот же сетевой поток для одновременного чтения и записи?

Ответ №1:

Помните, что с задачами, связанными с вводом-выводом, нет потока.

Опубликованный вами абзац на самом деле не касается задач, например:

 var client = new TcpClient();
await client.ConnectAsync("example.com", 80);
var stream = client.GetStream();

// start reading from the socket
var rBuf = new byte[2048];
var tRecv = stream.ReadAsync(rBuf, 0, rBuf.Length);

// send data and wait for it to complete
var sBuf = Encoding.UTF8.GetBytes("GET / HTTP/1.1rnHost: example.comrnrn");
await stream.WriteAsync(sBuf, 0, sBuf.Length);

// now await for the data
var bytesRecv = await tRecv;

Console.WriteLine($"Received {bytesRecv} bytes");
  

При работе с базовым потоком не существует двух одновременных потоков, делающих что-то одновременно.

То, о чем говорится в абзаце, делает что-то вроде этого:

 var client = new TcpClient();
await client.ConnectAsync("example.com", 80);
var stream = client.GetStream();

new Thread(() =>
{
    var rBuf = new byte[2048];
    var bytesRecv = stream.Read(rBuf, 0, rBuf.Length);
    Console.WriteLine($"Received {bytesRecv} bytes");
}).Start();

Thread.Sleep(500); // ensure the above thread has started

new Thread(() =>
{
    var sBuf = Encoding.UTF8.GetBytes("GET / HTTP/1.1rnHost: example.comrnrn");
    stream.Write(sBuf, 0, sBuf.Length);
}).Start();

Console.ReadKey(true);
  

В приведенном выше примере у вас есть два разных потока, которые одновременно что-то делают с базовым потоком, и ничего не «взрывается».

Что касается того, какой шаблон вы должны использовать… У вас есть несколько возможностей. У вас есть первый пример выше с задачами (современный подход), или вы могли бы использовать поточный while(true) метод, используя блокирующие вызовы (подход «старой школы»). Или методы Begin... и End... (между старой школой и современными).

Что лучше? Вы выбираете. Лично мне нравится, когда события запускаются при наличии данных, поэтому я склонен использовать методы BeginConnect / EndConnect , BeginReceive / EndReceive и т.д.

Иногда, однако, мне нравится делать while(true) использование ReadAsync() , но использовать CancellationToken для прерывания цикла.

Я не предлагаю использовать while(true) с блокировкой вызовов. Имея замечательные инструменты, которые предоставляет нам TPL, нам больше не нужно запускать общие потоки для выполнения чего-либо.

Комментарии:

1. Звучит неплохо, я полностью за то, чтобы не использовать потоки, однако аппаратное обеспечение, с которым мне нужно взаимодействовать, может непрерывно отправлять данные, как я могу гарантировать, что у меня есть какой-то «процесс», который продолжает получать и вызывать события всякий раз, когда данные получены, при этом все еще имея возможность время от времени отправлять на него что-то, и все это без использования блокировки while (true) в другом потоке?

2. @Seb — вы могли бы запустить Task который находится в while(true) { ReceiveAsync() } и просто запустить событие, когда данные будут готовы. Или используйте BeginReceive метод, который в основном делает то же самое.

3. Задача, которая выполняется некоторое время (верно)? А не наоборот? Некоторое время (истина) в задаче?