2016-03-18 6 views
4

В настоящее время я работаю над простой чат-программой клиентского сервера (хотел получить информацию о взаимодействии с клиентским сервером на C#). Он работает до сих пор, кроме как правильно отключиться от сервера.Проблемы с закрытием TcpClient и NetworkStream

На клиенте я использую этот код здесь, чтобы закрыть соединение:

client.Client.Disconnect(false); // client is the TcpClient 
client.Close(); 

На сервере имеется резьбовая петля ждет сообщений от клиента:

private void StartChat() 
{ 
    int requestCount = 0; 
    byte[] bytesFrom = new byte[10025]; 
    string dataFromClient = null; 
    string rCount = null; 

    while (true) 
    { 
     try 
     { 
      requestCount++; 

      NetworkStream stream = tcpClient.GetStream(); 

      int bufferSize = (int)tcpClient.ReceiveBufferSize; 
      if (bufferSize > bytesFrom.Length) 
      { 
       bufferSize = bytesFrom.Length; 
      } 

      stream.Read(bytesFrom, 0, bufferSize); 
      dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom); 
      dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$")); 
      rCount = Convert.ToString(requestCount); 

      string message = client.Name + " says: " + dataFromClient; 
      program.Broadcast(message); 

     } 
     catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException) 
     { 
      program.UserDisconnected(client); 
      break; 
     } 
     catch(ArgumentOutOfRangeException ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
      break; 
     } 
     catch(Exception ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
      break; 
     } 
    } 

Если клиент отключается с кодом, показанным выше, функция все время извлекает поток и производит такой выход:

\0\0\0\0\0\0\0 [and so on]

В этом случае ArgumentOutOfRangeException будет выброшен, потому что нет индекса $. Я добавил break, чтобы избежать и бесконечного выполнения цикла.

Удивительно, но ObjectDisposedException не будет выбрасываться. Также не будет выбрано значение System.IO.IOException, но он должен быть закрыт, поэтому соединение было отклонено.

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

Итак, как я могу определить, отключен ли клиент или недоступен? И как я закрываю соединение с клиентом, чтобы закрыть соединение?

Благодарим за помощь!

Update:

private void StartChat() 
{ 
    int requestCount = 0; 
    byte[] bytesFrom = new byte[10025]; 
    string dataFromClient = null; 
    string rCount = null; 

    while (true) 
    { 
     try 
     { 
      requestCount++; 

      NetworkStream stream = tcpClient.GetStream(); 
      stream.ReadTimeout = 4000; 

      int bufferSize = (int)tcpClient.ReceiveBufferSize; 
      if (bufferSize > bytesFrom.Length) 
      { 
       bufferSize = bytesFrom.Length; 
      } 


      // Wait for a client message. If no message is recieved within the ReadTimeout a IOException will be thrown 
      try 
      { 
       int bytesRead = stream.Read(bytesFrom, 0, bufferSize); 
       stream.Flush(); 

       if (bytesRead == 0) 
       { 
        throw new System.IO.IOException("Connection seems to be refused or closed."); 
       } 
      } 
      catch (System.IO.IOException) 
      { 
       byte[] ping = System.Text.Encoding.UTF8.GetBytes("%"); 
       stream.WriteTimeout = 1; 

       stream.Write(ping, 0, ping.Length); 
       continue; 
      } 


      dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom); 
      dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$")); 
      rCount = Convert.ToString(requestCount); 

      string message = client.Name + " says: " + dataFromClient; 
      program.Broadcast(message); 

     } 
     catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException) 
     { 
      Debug.WriteLine(ex.ToString()); 
      program.UserDisconnected(client); 
      break; 
     } 
     catch(ArgumentOutOfRangeException ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
     } 
     catch(Exception ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
      break; 
     } 
    } 
} 

ответ

3

Вы должны проверить возвращаемое значение stream.Read - она ​​возвращает количество байтов фактически чтения.

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

int bytes = stream.Read(bytesFrom, 0, bufferSize); 
if (bytes == 0) 
{ 
    // client has disconnected 
    break; 
} 

dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom, 0, bytes); 

В ответ на ваш недавний комментарий, три вещи, которые могут произойти, когда клиент прекращает соединение:

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

В этой последней ситуации сервер будет по-прежнему действовать так, как если бы было активное соединение, пока он не попытается отправить данные, которые, очевидно, потерпят неудачу. Вот почему многие протоколы работают с таймаутом соединения и/или механизмом keep-alive.

+0

Означает ли это, что вы не можете обнаружить, что клиент ушел? Глядя на документ, он должен вызывать исключение ObjectDispoedException или, по крайней мере, исключение IOEx, потому что он больше не может читать от него. – chris579

+0

Отключение _graceful_ не является исключительным случаем и обнаруживается 'stream.Read()' return 0. Вы действительно должны есть обработчик исключений, но я сомневаюсь, что «ObjectDisposedException» будет выброшен, если вы не разместите его самостоятельно, а затем попытайтесь его использовать. –

+0

Вы правы, DisposedException не будет выбрасываться, если этот объект все еще отсутствует и не удаляется сборщиком мусора или вручную удаляется. Однако огромное спасибо, это сработало! Я отвечу на ваш ответ как можно скорее. Редактировать: Если я закрою клиент, цикл не закончится и останется, пока приложение сервера не будет закрыто. Как я могу это исправить? – chris579

1

int bytesRead = stream.Read (...); if (bytesRead == 0) // клиент disconneted

+0

Ну, вы должны замедлить ход. Посмотрите на ответ выше;) – chris579

+0

ya ... Я знаю, что здесь достаточно разработчиков, возможно, сайт может сообщить человеку, когда есть новый комментарий/ответ ... и исправить разбиение на страницы, пока они на нем : p – ABuckau

+0

Да :) Есть еще одна проблема, ожидающая рассмотрения моего комментария в первом ответе. Может быть, вы можете мне помочь с этим;) – chris579