2015-06-02 5 views
0

Итак, мой клиент для сервера и чата сделан из двух разных учебных пособий по C# TCP. Вы можете признать, что 1, если не оба, и я внедрил свои собственные модификации, чтобы они соответствовали моему собственному стилю. Когда я пробовал оба, они отлично работали с потерей 0, но моя версия имеет ровно 50%. Например: 1. Клиент подключается: полученные данные 2. Клиент посылает текст: Нет данных 3. Клиент посылает текст: Данные, полученные 4. Клиент посылает текст: Нет данных Серверный код выглядит следующим образом :У меня есть серверное приложение, которое получает данные ровно в половине случаев. Почему/как это происходит и как я могу это исправить?

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Net.Sockets; 
using System.Threading; 
using System.Net; 

namespace WindowsFormsApplication2 
{ 
    class Server 
    { 
     private TcpListener tcpListener; 
     private Thread listenThread; 
     public Hashtable clientsList = new Hashtable(); 
     private System.Windows.Forms.TextBox output; 
     private delegate void ObjectDelegate(String text); 
     private ObjectDelegate del; 

     public Server(System.Windows.Forms.TextBox setOut) 
     { 
      this.tcpListener = new TcpListener(IPAddress.Any, 8888); 
      this.listenThread = new Thread(new ThreadStart(ListenForClients)); 
      this.listenThread.IsBackground = true; 
      this.listenThread.Start(); 
      output = setOut; 
      del = new ObjectDelegate(outputTextToServer); 
     } 

     private void ListenForClients() 
     { 
      this.tcpListener.Start(); 
      while (true) 
      { 
       //blocks until a client has connected to the server 
       TcpClient client = this.tcpListener.AcceptTcpClient(); 
       //create a thread to handle communication 
       //with connected client 
       addClient(client); 
       Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm)); 
       clientThread.IsBackground = true; 
       clientThread.Start(client); 
      } 
     } 

     private void HandleClientComm(object client) 
     { 
      TcpClient tcpClient = (TcpClient)client; 
      NetworkStream clientStream = tcpClient.GetStream(); 

      byte[] message = new byte[4096]; 
      int bytesRead; 
      while (true) 
      { 
       bytesRead = 0; 
       try 
       { 
        //blocks until a client sends a message 
        bytesRead = clientStream.Read(message, 0, 4096); 
       } 
       catch 
       { 
        //a socket error has occured 
        break; 
       } 
       if (bytesRead == 0) 
       { 
        //the client has disconnected from the server 
        break; 
       } 
       //message has successfully been received 
       String text = getData(clientStream); 
       del.Invoke(text); //Used for Cross Threading & sending text to server output 
       //if filter(text) 
       sendMessage(tcpClient); 
       //System.Diagnostics.Debug.WriteLine(text); //Spit it out in the console 
      } 

      tcpClient.Close(); 
     } 

     private void outputTextToServer(String text) 
     { 
      if (output.InvokeRequired) 
      { 
       // we then create the delegate again 
       // if you've made it global then you won't need to do this 
       ObjectDelegate method = new ObjectDelegate(outputTextToServer); 
       // we then simply invoke it and return 
       output.Invoke(method, text); 
       return; 
      } 
      output.AppendText(Environment.NewLine + " >> " + text); 
     } 

     private String getData(NetworkStream stream) 
     { 
      int newData; 
      byte[] message = new byte[4096]; 
      ASCIIEncoding encoder = new ASCIIEncoding(); 
      newData = stream.Read(message, 0, 4096); 
      String text = encoder.GetString(message, 0, newData); //Translate it into text 
      text = text.Substring(0, text.IndexOf("$")); //Here comes the money 
      return text; 
     } 

     private void addClient(object client) 
     { 
      TcpClient tcpClient = (TcpClient)client; 
      NetworkStream clientStream = tcpClient.GetStream(); 
      String dataFromClient = getData(clientStream); 
      if (clientsList.Contains(dataFromClient)) 
      { 
       Console.WriteLine(dataFromClient + " Tried to join chat room, but " + dataFromClient + " is already in use"); 
       //broadcast("A doppleganger of " + dataFromClient + " has attempted to join!", dataFromClient, false); 
      } 
      else 
      { 
       clientsList.Add(dataFromClient, tcpClient); 
       //broadcast(dataFromClient + " Joined ", dataFromClient, false); 
       del.Invoke(dataFromClient + " Joined chat room "); 
       //handleClinet client = new handleClinet(); 
       //client.startClient(clientSocket, dataFromClient, clientsList); 
      } 
     } 

     private Boolean connectionAlive(NetworkStream stream) 
     { 
      byte[] message = new byte[4096]; 
      int bytesRead = 0; 
      try 
      { 
       //blocks until a client sends a message 
       bytesRead = stream.Read(message, 0, 4096); 
      } 
      catch 
      { 
       //a socket error has occured 
       return false; 
      } 
      if (bytesRead == 0) 
      { 
       //the client has disconnected from the server 
       //clientsList.Remove 
       return false; 
      } 
      return true; 
     } 

     private void sendMessage(TcpClient client) 
     { 
      NetworkStream clientStream = client.GetStream(); 
      ASCIIEncoding encoder = new ASCIIEncoding(); 
      byte[] buffer = encoder.GetBytes("Hello Client!"); 

      clientStream.Write(buffer, 0, buffer.Length); 
      clientStream.Flush(); 
     } 
    } 
} 

и вот мой код клиента

using System; 
using System.Windows.Forms; 
using System.Text; 
using System.Net.Sockets; 
using System.Threading; 



namespace WindowsFormsApplication2 
{ 

    public partial class Form1 : Form 
    { 
     public delegate void newDelegate(); 
     public newDelegate myDelegate; 
     System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient(); 
     NetworkStream serverStream = default(NetworkStream); 
     string readData = null; 

     public Form1() 
     { 
      InitializeComponent(); 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      newMsg(); 
     } 

     private void newMsg() 
     { 
      byte[] outStream = System.Text.Encoding.ASCII.GetBytes(textBox2.Text + "$"); 
      serverStream.Write(outStream, 0, outStream.Length); 
      serverStream.Flush(); 
      textBox2.Text = ""; 
     } 

     private void button2_Click(object sender, EventArgs e) 
     { 
      readData = "Connecting to Chat Server ..."; 
      msg(); 
      clientSocket.Connect(txtIP.Text, int.Parse(txtPort.Text)); 
      serverStream = clientSocket.GetStream(); 

      byte[] outStream = System.Text.Encoding.ASCII.GetBytes(txtName.Text + "$"); 
      serverStream.Write(outStream, 0, outStream.Length); 
      serverStream.Flush(); 

      myDelegate = new newDelegate(disconnect); 
      Thread ctThread = new Thread(getMessage); 
      ctThread.IsBackground = true; 
      ctThread.Start(); 
      button2.Enabled = false; 
     } 

     private void getMessage() 
     { 
      while (true) 
      { 
       serverStream = clientSocket.GetStream(); 
       int buffSize = 0; 
       byte[] inStream = new byte[clientSocket.ReceiveBufferSize]; 
       buffSize = clientSocket.ReceiveBufferSize; 
       try 
       { 
        serverStream.Read(inStream, 0, buffSize); 
        string returndata = System.Text.Encoding.ASCII.GetString(inStream); 
        readData = "" + returndata; 
        msg(); 
       } 
       catch 
       { 
        Invoke(myDelegate); 
        return; 
       } 
      } 
     } 

     private void disconnect() 
     { 
      button2.Enabled = true; 
     } 

     private void msg() 
     { 
      if (this.InvokeRequired) 
       this.Invoke(new MethodInvoker(msg)); 
      else 
       textBox1.AppendText(Environment.NewLine + " >> " + readData); 
      //textBox1.Text = textBox1.Text + Environment.NewLine + " >> " + readData; 
     } 

     private void textBox2_KeyDown(object sender, KeyEventArgs e) 
     { 
      if (e.KeyCode == Keys.Enter) 
      { 
       newMsg(); 
      } 
     } 

     private void cmdHost_Click(object sender, EventArgs e) 
     { 
      Server serv = new Server(txtLog); 
     } 
    } 

} 

Этот код, очевидно, работа продолжается, и извините заранее за неряшливости. Любые другие предложения к коду также приветствуются.

+2

В 'HandleClientComm' вы заполняете буфер полученными данными. Затем вы вызываете 'getData', передавая * stream * в этот метод и заполняете * другой * буфер, игнорируя ценность данных первого буфера. Может ли это быть частью вашей проблемы? –

+1

Умм, вы выбрасываете все после '$' (в 'text = text.Substring (0, text.IndexOf (" $ "))'). TCP - это протокол, основанный на потоке, а не основанный на сообщениях, поэтому один «Read» может возвращать данные, состоящие из нескольких «Send's». И на стороне клиента вы игнорируете возвращаемое значение 'Read', поэтому вы не представляете, сколько данных вы действительно могли прочитать. Это не большая проблема, поскольку вы воссоздаете 'byte []' каждую итерацию, но это пустая трата. – Luaan

+0

Все, что сказал, уроки, которые вы использовали, кажутся удивительно хорошими. Я не видел учебник сокета на C#, который правильно обрабатывает выключения, кадрирование сообщений и даже некоторое подобие обработки ошибок. – Luaan

ответ

1

Хорошо, это начинает немного удлиняться.

В коде содержится несколько ошибок. Начиная с кодом сервера:

  • Как отметил Damien, вы пытаетесь прочитать каждый «сообщение» дважды - первый в HandleClientComm, а затем снова в getData. В потоке больше нет исходных данных, поэтому вы просто полностью удаляете одно из считываний (таким образом, потеря 50% -ного пакета)
  • Позже, в getData, вы удаляете все данные в потоке после сначала $. Хотя это, очевидно, попытка обработки кадрирования сообщений (поскольку TCP - это протокол на основе потока, а не протокол на основе сообщений), это глупо - вы выбрасываете данные. Причина, по которой это не показала в вашем тестировании, заключается в том, что 1) Windows обрабатывает локальный TCP совсем иначе, чем удаленный TCP, 2) вам нужно было бы фактически отправить два сообщения достаточно быстро, чтобы они «сливались» вместе в потоке , Это означает, что вы отправляете два сообщения примерно за 200 мс (буферизация TCP по умолчанию) или блокирование чтения.
  • Вы сохраняете Flush в сетевом потоке. Это на самом деле ничего не делает, и даже если бы это было так, вы бы этого не хотели.
  • connectionAlive читает из общего сокета - это всегда плохая идея. Никогда не имеет более одного считывателя - несколько считывателей не работают с потоковыми протоколами. Кажется, вы не используете его в своем примере кода, но остерегайтесь пытаться.
  • Замечено, что clientList.Remove, конечно же, будет сквозным доступом к общему полю. Если вы хотите сделать это так, вам необходимо обеспечить безопасный одновременный доступ - либо с помощью ConcurrentDictionary вместо HashSet, либо по lock вокруг каждой записи и, прочитанной clientList.
  • Вы ожидаете получить все сообщение в одном Read. Это может быть хорошо для простого клиента чата, но в любом случае это плохой TCP - вам нужно прочитать, пока не найдете свой терминатор сообщений. Если я отправлю сообщение достаточно большим, ваш код просто опустится на text.IndexOf("$").

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

Теперь клиент немного проще:

  • Опять же, не Flush поток сети.
  • Не используйте фоновые потоки, если вам это не нужно. Просто убедитесь, что правильно завершены соединения и т. Д.
  • Отсортировать должно фактически отключить. Это не так сложно, просто закройте TcpClient.
  • Что такое readData = "" + returndata Предполагаемый? Это просто глупо.
  • Вы игнорируете возвращаемое значение Read. Это означает, что вы не представляете, сколько байтов данных вы читаете, что означает, что ваша строка returnData содержит сообщение, за которым следуют несколько тысяч символов \0. Единственная причина, по которой вы не видите их в выходе, заключается в том, что большинство Windows использует \0 в качестве ограничителя строк («это имело смысл в то время»). .NET нет.
  • Опять же, Read ожидает всего сообщения сразу. В отличие от сервера, это не будет врезаться клиента, но ваш код будет вести себя по-разному (например, дополнительный \r\n >>, даже если это не отдельное сообщение.

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

В качестве примечания я недавно сделал упрощенный пример сети, который обрабатывает простую систему клиент-сервер чата, используя более современные технологии - например, используя асинхронный ввод-вывод await вместо многопоточности. а не готовый к производству код, но он должен четко показать идеи и намерения (я также рекомендую взглянуть на первый образец «HTTP-подобная связь TCP»). Здесь вы можете найти полный исходный код - Networking Part 2.

+1

У меня возникло соблазн закрыть «простую опечатку», но, увидев весь список здесь, я сейчас удержался. –

+0

Большое спасибо. Я просто положил бит «потеря пакетов» на всякий случай. –