2013-10-18 36 views
1

我從幾個例子中拉出來設置連接到服務器的全雙工C#TCP客戶端。基本概念是客戶端和服務器都發送/接收消息(命令和事件)。因此,我開發了一個FullDuplexSocket類,它公開發送消息到服務器的Send方法以及從服務器接收消息的事件處理程序。除了我似乎無法清除從服務器接收到的消息緩衝區之外,一切正常。每次服務器發送新消息時,套接字中的緩衝區都包含新消息(已讀取)。我可以通過已知的分隔符(/ r/n)分割消息並對其進行跟蹤,但這可能會成爲長時間運行的通信中存儲器問題的根源。 [編輯:將代碼更新爲不再存在緩衝區問題並可正常工作的解決方案]。如何「刷新」TCP客戶端緩衝區?

有沒有人有任何建議。總重寫?代碼如下,希望能幫助其他人。

這裏的FullDuplexSocket類:

using System; 
using System.Text; 
using System.Net.Sockets; 

namespace Utilities 
{ 
    public class StateObject{ 
     public Socket workSocket = null; 
     public const int BUFFER_SIZE = 1024; 
     public byte[] buffer = new byte[BUFFER_SIZE]; 
    } 

    public class FullDuplexSocket : IDisposable 
    { 
     public event NewMessageHandler OnMessageReceived; 
     public delegate void NewMessageHandler(string Message); 
     public event DisconnectHandler OnDisconnect; 
     public delegate void DisconnectHandler(string Reason); 

     private Socket _socket; 
     private bool _useASCII = true; 
     private string _remoteServerIp = ""; 
     private int _port = 0; 

     /// <summary> 
     /// Constructer of a full duplex client socket. The consumer should immedately define 
     /// and event handler for the OnMessageReceived event after construction has completed. 
     /// </summary> 
     /// <param name="RemoteServerIp">The remote Ip address of the server.</param> 
     /// <param name="Port">The port that will used to transfer/receive messages to/from the remote IP.</param> 
     /// <param name="UseASCII">The character type to encode/decode messages. Defaulted to use ASCII, but setting the value to false will encode/decode messages in UNICODE.</param> 
     public FullDuplexSocket(string RemoteServerIp, int Port, bool UseASCII = true) 
     { 
      _useASCII = UseASCII; 
      _remoteServerIp = RemoteServerIp; 
      _port = Port; 

      try //to create the socket and connect 
      { 
       _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
       _socket.Connect(RemoteServerIp, _port); 
      } 
      catch (Exception e) 
      { 
       throw new Exception("Unable to connect to the remote Ip.", e); 
      } 

      try //to listen to the socket 
      { 
       StateObject stateObject = new StateObject(); 
       stateObject.workSocket = _socket; 

       _socket.BeginReceive 
        (
         stateObject.buffer, //Buffer to load in our state object 
         0, //Start at the first position in the byte array 
         StateObject.BUFFER_SIZE, //only load up to the max per read 
         0, //Set socket flags here if necessary 
         new AsyncCallback(ReadFromSocket), //Who to call when data arrives 
         stateObject //state object to use when data arrives 
        ); 
      } 
      catch (Exception e) 
      { 
       throw new Exception("Unable to start listening to the socket.", e); 
      } 
     } 


     /// <summary> 
     /// This will read the bytes from the socket, convert the bytes to a string and fire the OnMessageReceived event. 
     /// If the socket is forcibly closed, the OnDisconnect event will be fired. This happens when the other side of 
     /// the socket connection on the remote Ip is no longer available. 
     /// </summary> 
     /// <param name="asyncResult"></param> 
     public void ReadFromSocket(IAsyncResult asyncResult) 
     { 
      StateObject stateObject = (StateObject)asyncResult.AsyncState; //pull out the state object 
      int bytesReceived = 0; 

      try //to receive the message. 
      { 
       bytesReceived = stateObject.workSocket.EndReceive(asyncResult); 
      } 
      catch (Exception e) //Exception will occur if connection was forcibly closed. 
      { 
       RaiseOnDisconnect(e.Message); 
       return; 
      } 

      if (bytesReceived > 0) 
      { 
       RaiseOnMessageReceived 
        (
         _useASCII ? 
          Encoding.ASCII.GetString(stateObject.buffer, 0, bytesReceived) : 
          Encoding.Unicode.GetString(stateObject.buffer, 0, bytesReceived) 
        ); 

       stateObject.workSocket.BeginReceive 
        (
         stateObject.buffer, //Buffer to load in our state object 
         0, //Start at the first position in the byte array 
         StateObject.BUFFER_SIZE, //only load up to the max per read 
         0, //Set socket flags here if necessary 
         new AsyncCallback(ReadFromSocket), //Who to call when data arrives 
         stateObject //state object to use when data arrives 
        ); 

      } 
      else 
      { 
       stateObject.workSocket.Close(); 
       RaiseOnDisconnect("Socket closed normally."); 
       return; 
      } 
     } 
     /// <summary> 
     /// Broadcast a message to the IP/Port. Consumer should handle any exceptions thrown by the socket. 
     /// </summary> 
     /// <param name="Message">The message to be sent will be encoded using the character set defined during construction.</param> 
     public void Send(string Message) 
     { 
      //all messages are terminated with /r/n 
      Message += Environment.NewLine; 

      byte[] bytesToSend = _useASCII ? 
       Encoding.ASCII.GetBytes(Message) : 
       Encoding.Unicode.GetBytes(Message); 

      int bytesSent = _socket.Send(bytesToSend); 

     } 

     /// <summary> 
     /// Clean up the socket. 
     /// </summary> 
     void IDisposable.Dispose() 
     { 
      try 
      { 
       _socket.Close(); 
       RaiseOnDisconnect("Socket closed via Dispose method."); 
      } 
      catch { } 
      try 
      { 
       _socket.Dispose(); 
      } 
      catch { } 
     } 


     /// <summary> 
     /// This method will gracefully raise any delegated events if they exist. 
     /// </summary> 
     /// <param name="Message"></param> 
     private void RaiseOnMessageReceived(string Message) 
     { 
      try //to raise delegates 
      { 
       OnMessageReceived(Message); 
      } 
      catch { } //when none exist ignore the Object Reference Error 
     } 

     /// <summary> 
     /// This method will gracefully raise any delegated events if they exist. 
     /// </summary> 
     /// <param name="Message"></param> 
     private void RaiseOnDisconnect(string Message) 
     { 
      try //to raise delegates 
      { 
       OnDisconnect(Message); 
      } 
      catch { } //when none exist ignore the Object Reference Error 
     } 

    } 
} 

類的消費者將簡單地做到以下幾點:

using System; 

namespace Utilities 
{ 
    public class SocketConsumer 
    { 
     private FullDuplexSocket _fds; 

     public Consumer() 
     { 
      _fds = new FullDuplexSocket("192.168.1.103", 4555); 

      _fds.OnMessageReceived += fds_OnMessageReceived; 

      _fds.Send("Hello World!"); 
     } 

     void fds_OnMessageReceived(string Message) 
     { 
      Console.WriteLine("Message: {0}", Message); 
     } 
    } 
} 

任何幫助將是巨大的。謝謝!

回答

0

即使緩衝區尚未填滿(如果是bytesRead < count),即表示您要求OnMessageReceived。考慮切換到應用程序的異步部分await。這擺脫了醜陋的回調遞歸。

+0

感謝您的回覆。單步執行代碼,計數似乎設置爲1029990765,並且始終大於bytesRead。不清楚在這裏等待的使用,也許一個例子可能有所幫助?也許我有一些邏輯落後? –

+0

不知道那裏發生了什麼,但爲什麼計數等於〜10億?這是一個錯誤。添加運行時斷言以捕獲發生該情況的最早情況。等待填充不會立即解決問題 - 它會盡可能簡化代碼(根本不需要回調),這樣錯誤就會變得明顯。我也很想把更多的時間投入到意大利麪條的回調中。這不是你的錯 - 這只是回調驅動代碼的本質。 – usr

+0

經過多次迭代,我將問題中的代碼編輯爲可行的解決方案。從遠程服務器接收到每條消息後,緩衝區將被清除,並且一切正常。我無法確定使用Async/await來解決這個問題的方法,所以醜陋的回調仍然存在,但似乎在.NET的Socket類的實現中正確使用。歡迎任何其他意見/替代解決方案。 ...感謝usr ... –