2016-03-25 3 views
0

У меня серьезная проблема с полуприкрытием TcpClient. То, что я пытаюсь сделать, это:Half-закрытие TcpClient

На клиенте:

  1. Отправить сообщение
  2. Shutdown основной разъем для отправки
  3. Получить ответ
  4. Shutdown основной разъем для чтения (или, на данный момент, просто закройте его)

На сервере:

  1. Получить сообщение
  2. Shutdown основной разъем для чтения
  3. отправить ответ
  4. Shutdown основной разъем для записи (или, в этот момент, просто закройте его)

Однако , после шага 2 на клиенте или на сервере я не могу использовать поток TcpClient. Вот очень упрощенный вариант моего кода (без асинхронных вызовов, обработки и очистки, а также с помощью StreamReader и StreamWriter вместо XmlSerializer):

 //initialize the connection between the server and the client 
     var listener = new TcpListener(IPAddress.Any, 13546); 
     listener.Start(); 
     var client = new TcpClient("127.0.0.1", 13546); 
     var server = listener.AcceptTcpClient(); 
     listener.Stop(); 

     //CLIENT: send the message 
     var cwriter = new StreamWriter(client.GetStream()); 

     cwriter.Write("client's message"); 
     cwriter.Flush(); 

     client.Client.Shutdown(SocketShutdown.Send); 

     //SERVER: receive the message 
     string msg; 
     var sreader = new StreamReader(server.GetStream()); 

     msg = sreader.ReadToEnd(); 

     server.Client.Shutdown(SocketShutdown.Receive); 

     //SERVER: send a response 
     //Here the code fails on server.GetStream() - 
     //InvalidOperationException, apparently the whole connection is closed now 

     var swriter = new StreamWriter(server.GetStream()); 

     swriter.Write(msg + " with server's response"); 
     swriter.Flush(); 

     server.Client.Shutdown(SocketShutdown.Send); 

     //CLIENT: receive the message 
     var creader = new StreamReader(client.GetStream()); 

     var response = creader.ReadToEnd(); 

     client.Client.Shutdown(SocketShutdown.Receive); 

Есть ли способ сделать это без использования сокеты ? Я что-то не понимаю?

+0

Что означает отказ? Переход к сырым сокетам кажется преждевременным и произвольным. Скорее, исправить ошибку, и вы хороши. – usr

+1

'StreamWriter' берет на себя ответственность за переданный ему поток. Когда он собирает мусор, основной поток также удаляется. Вы бы четко это заметили, если бы вы разместили «StreamWriter», как и предполагалось (что означает «IDisposable»). Вы можете использовать перегрузку конструктора, которая оставляет поток открытым. В качестве альтернативы, не беспокойтесь ни о чем из этого - если вам нужно получить доступ к базовому сокету 'TcpClient', вы обычно делаете это неправильно. Сокет действительно не нужно отключать вручную с помощью логики прерывания. –

+0

@JeroenMostert хороший улов, но проблема не должна легко проявляться. Есть ли у SR даже финализатор? Это не должно. Кроме того, выключение может сигнализировать о завершении потока на удаленную сторону. Я думаю, что закрытие приема не делает много, да. – usr

ответ

0

Проблема в том, что ReadToEnd считывает данные до конца потока. Выдавая Client.Shutdown, вы фактически закрываете гнездо, что делает невозможным его повторное использование (по крайней мере, в случае TCPClient). Вот код GetStream()

public NetworkStream GetStream() { 
    if(Logging.On)Logging.Enter(Logging.Sockets, this, "GetStream", ""); 
    if (m_CleanedUp){ 
     throw new ObjectDisposedException(this.GetType().FullName);   
    } 
    if (!Client.Connected) { 
     throw new InvalidOperationException(SR.GetString(SR.net_notconnected)); 
    } 
    if (m_DataStream==null) { 
     m_DataStream = new NetworkStream(Client, true); 
    } 
    if(Logging.On)Logging.Exit(Logging.Sockets, this, "GetStream", m_DataStream); 
    return m_DataStream; 
} 

Как вы можете видеть, встретились ошибка из-за закрытый сокет.

EDIT: Это смешно странно, но я думаю, что я нашел причину, почему он не работает должным образом. Причина в том, что Shutdown всегда устанавливает флаги для всего сокета как отключенные. Хотя мы на самом деле не закрываем его таким образом! Если мы сохраним поток в начале метода, мы не столкнемся с этой проблемой, так как проблема заключается в методе GetStream, который проверяет состояние сокета. Но мы, вероятно, сталкиваемся с некоторыми ошибками, когда другой код проверяет состояние сокета.

+0

Значит, невозможно сделать половину с TcpClient? Это позор. – Noctiphobia

+0

Возможно полублокировать соединение. Открытый вопрос: почему сокет полностью закрыт, если код кажется закрытым. Кроме того, вы можете просто позвонить GetStream один раз в начале, что приводит к появлению чистого кода. @Noctiphobia, пожалуйста, сделайте это и обновите код. – usr

+0

@Noctiphobia ok Я переписал код, и кажется, что я могу отправлять данные туда и обратно без каких-либо проблем. В любом случае было бы безопаснее обрабатывать сокеты явно, если вы хотите повторно использовать его для двусторонней связи. IMO – greenshade