2012-12-19 162 views
1

這是用於MoSync應用程序和外部DLL之間的本地通信,MoSync不允許使用第三部分DLL,這就是爲什麼我必須實現此橋接軟件而不是使用簡單調用的原因到一個DLL,我必須從XML轉換到DLL消息格式,並再次轉換爲XML。我知道這是一件愚蠢的事情,不幸的是沒有靈活性來改變架構。起初我以爲只有一個請求,所以我有Sync com,但現在我發現可以有多個請求,所以我需要再次實現Async。EndRead引發IO異常

我有一個是從時間拋出來時的異常,因爲我是新來的C#我無法找到內存泄漏......也許一對更訓練有素的眼睛可以發現問題

SOURCE代碼:

我寫了下面的代碼,我對C#和套接字很陌生,所以也許我犯了一些只有經驗豐富的眼睛才能察覺的大錯誤。這是用於Windows Mobile 6.1設備,所以我試圖避免使用很多線程。

using System; 

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

namespace SmartDevice_Server 
{ 
    //ClientConnection saves connection information is used to keep context in Async and Event calls 
    public class ClientConnection : EventArgs 
    { 
     public NetworkStream NetworkStream { get; private set; } 
     public byte[] Data { get; private set; } 
     public int byteReadCount { get; set; } 

     public ClientConnection(NetworkStream networkStream, byte[] data) 
     { 
      NetworkStream = networkStream; 
      Data = data; 
     } 
    } 

    //MySocket - Is a server that listens for events and triggers Events upon Request Completion 
    public class MySocketTCP 
    { 
     #region Class Members 
     TcpListener myTcpListener; 
     TcpClient myTcpClient; 
     NetworkStream myNetworkStream; 

     const string localHost = "127.0.0.1"; 
     IPAddress myAddress = IPAddress.Parse(localHost); 
     int myPortNumber = 58889; 
     byte[] myData; 

     int bytesReadCount; 
     const int MIN_REQUEST_STRING_SIZE = 10; 

     int TimeStart; 

     //Event 
     public event socketReadCompleteHandler socketReadCompleteEvent; 
     public EventArgs eventArguments = null; 
     public delegate void socketReadCompleteHandler(MySocketTCP myTcpSocket, ClientConnection eventArguments); 

     #endregion 

     //Constructor 
     public MySocketTCP() 
     { 
      Init(); 
     } 

     //Constructor overloaded to receive IPAdress Host, and Port number 
     public MySocketTCP(IPAddress hostAddress, int portNumber) 
     { 
      myAddress = hostAddress; 
      myPortNumber = portNumber; 

      Init(); 
     } 

     //Initializes the TCPListner 
     public void Init() 
     { 
      try 
      { 
       myTcpListener = new TcpListener(myAddress, myPortNumber); 

       //myNetworkStream = myTcpClient.GetStream(); 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 
     } 

     /*TODO_Listener_Timer: After you accept a connection you wait for data to be Read indefinitely 
     *Possible solution: Use a timeout to close the socket connection. 
     *Check WIKI, TODOS 
     * */ 
     //Listens Asynchronously to Clients, class a recieveMessageHandler to process the read 
     public void ListenAsync() 
     { 
      myTcpListener.Start(); 

      while (true) 
      { 
       //blocks until a client has connected to the server 
       myTcpClient = myTcpListener.AcceptTcpClient(); 

       var client = new ClientConnection(myTcpClient.GetStream(), new byte[myTcpClient.ReceiveBufferSize]); 

       // Capture the specific client and pass it to the receive handler 
       client.NetworkStream.BeginRead(client.Data, 0, client.Data.Length, r => receiveMessageHandler(r, client), null); 
      } 
     } 

     //Callback is used to Process the request Asynchronously, triggers socketReadCompleteEvent 
     public void receiveMessageHandler(IAsyncResult asyncResult, ClientConnection clientInstance) 
     { 
      bytesReadCount = 0; 

      lock (clientInstance.NetworkStream) 
      { 
       try 
       { 
        bytesReadCount = clientInstance.NetworkStream.EndRead(asyncResult); 
        clientInstance.byteReadCount = bytesReadCount; 
       } 
       catch (Exception exc) 
       { 
        throw exc; 
       } 
      } 

      if (bytesReadCount < MIN_REQUEST_STRING_SIZE) 
      { 
       //Could not read form client. 
       Debug.WriteLine("NO DATA READ"); 
      } 
      else 
      { 
       if (socketReadCompleteEvent != null) 
       { 
        socketReadCompleteEvent(this, clientInstance); 
       } 
      } 
     } 

     //Reads the request, uses the ClientConnection for context 
     public string ReadAsync(ClientConnection connObj) 
     { 
      int bytesReadCount = connObj.byteReadCount; 
      byte[] myData = connObj.Data; 

      string xmlMessage; 

      try 
      { 
       xmlMessage = Encoding.ASCII.GetString(myData, 0, bytesReadCount); 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 

      return xmlMessage; 
     } 

     //Deprecated 
     public string Read() 
     { 
      string xmlMessage; 

      try 
      { 
       xmlMessage = Encoding.ASCII.GetString(myData, 0, bytesReadCount); 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 

      return xmlMessage; 
     } 

     //Deprecated 
     public void Write(byte[] outBytes) 
     { 
      try 
      { 
       myNetworkStream.Write(outBytes, 0, outBytes.Length); 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 
     } 

     //Deprecated 
     public void Write(string outMessage) 
     { 
      byte[] outBytes = Encoding.ASCII.GetBytes(outMessage); 

      try 
      { 
       myNetworkStream.Write(outBytes, 0, outBytes.Length); 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 

      int TimeEnd = Environment.TickCount; 
      int TimeResult = TimeEnd - TimeStart; 
     } 

     //Is used to send the message to the correct socket 
     public void WriteAsync(ClientConnection connObj, string outMessage) 
     { 
      byte[] outBytes = Encoding.ASCII.GetBytes(outMessage); 

      try 
      { 
       connObj.NetworkStream.Write(outBytes, 0, outBytes.Length); 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 

      int TimeEnd = Environment.TickCount; 
      int TimeResult = TimeEnd - TimeStart; 
     } 

     //Closes the client 
     public void Close() 
     { 
      //myNetworkStream.Close(); 
      try 
      { 
       myTcpClient.Close(); 
      } 
      catch (Exception ex) 
      { 

       throw ex; 
      } 
     } 
    } 
} 
+0

你是什麼意思「3個請求」 ?你期望讀的三條消息? –

+0

正確,三條消息一個接一個地發送到服務器。 – Astronaut

+0

您每次通過while循環創建一個新的TCP客戶端。讀完數據後需要處置或關閉它。此外,爲什麼每次他們想要發送數據時都要求客戶端進程重新連接?這需要TCP層的大量開銷。 – user957902

回答

6

最可能的問題是,您希望爲客戶端做的三個「寫入」執行正好三個「讀取」。

這是一個錯誤的假設,因爲TCP套接字是字節流並且不保留您的應用程序消息邊界。服務器可能會使用客戶端發送的三條「消息」中的一條,兩條或十七條讀取消息。

您需要以某種方式告訴服務器消息在字節流中的結束位置。通常的選擇是固定長度的消息,分隔符,指示有效載荷長度的消息頭,XML等自描述形式。

因此,您繼續從流中讀取數據,直到您有完整的處理消息,但在同時您可能會將下一條消息的一部分讀入緩衝區。

+0

我正在關閉請求之間的NetworkStream,這是否不清除緩衝區? – Astronaut

+1

我認爲關閉流也會關閉底層套接字,所以這是另一個問題。 –

+0

我已經更新了代碼。收到和發送的消息都是簡短且預定義的XML。我用它來讓兩個應用程序相互通信。客戶端使用XML操作和服務器進程發出請求,然後用XML結果進行響應......在某個時間點只能執行一個操作。 – Astronaut

3

我覺得這裏的問題是,你只持有單一NetworkStreammyNetworkStream)正因爲如此,如果第二個客戶端連接的第一個已發送的數據之前,你接受環將覆蓋myNetworkStream與流的第二個連接。當第一個客戶端然後發送一些數據的receiveMessageHandler將拜會EndRead第二連接的NetworkStream(這是存儲在myNetworkStream第二個客戶端連接時),但是從客戶端讀取的asyncResult傳遞。這會導致您指出的異常。具體來說,當我測試它時,我收到以下消息:

無法從傳輸連接讀取數據:IAsyncResult對象未從該類的相應異步方法返回。 參數名稱:asyncResult。

嘗試做如下改變:

// Create a class to hold details related to a client connection 
public class ClientConnection 
{ 
    public ClientConnection(NetworkStream networkStream, byte[] data) 
    { 
     NetworkStream = networkStream; 
     Data = data; 
    } 

    public NetworkStream NetworkStream { get; private set; } 
    public byte[] Data { get; private set; } 
} 

public void Listen() 
{ 
    myTcpListener.Start(); 

    while (true) 
    { 
     //blocks until a client has connected to the server 
     myTcpClient = myTcpListener.AcceptTcpClient(); 

     var client = new ClientConnection(myTcpClient.GetStream(), new byte[myTcpClient.ReceiveBufferSize]); 

     // Capture the specific client and pass it to the receive handler 
     client.NetworkStream.BeginRead(client.Data, 0, client.Data.Length, r => receiveMessageHandler(r, client), null); 
    } 
} 

public void receiveMessageHandler(IAsyncResult asyncResult, ClientConnection client) 
{ 
    var byteReadCount = client.NetworkStream.EndRead(asyncResult); 

    if (byteReadCount < MIN_REQUEST_STRING_SIZE) 
    { 
     //Could not read form client. 
     //Erro - Como tratar? Close() 
    } 
    else 
    { 
     if (socketReadCompleteEvent != null) 
     { 
      socketReadCompleteEvent(this, eventArguments); 
     } 
    } 
} 

正如其他人所提到的,有與你匹配的讀取發送者和接收者之間/寫入期望更多的問題,但是這似乎是事業你看到的實際問題。

編輯:

這裏收到一個完整的消息時,將接收數據,並調用callback方法的服務器。 callback返回一個字符串,然後將其發送回客戶端,該客戶端使用響應數據調用其自己的replyCallback。每個連接只發送一個請求 - 響應(效率相當低,但應該是一個很好的起點)。

public static class Server 
{ 
    public static void Run(int port, Action<string> callback) 
    { 
     var listener = new TcpListener(IPAddress.Loopback, port); 
     listener.Start(); 

     while (true) 
     { 
      using (var client = listener.AcceptTcpClient()) 
      { 
       try 
       { 
        var buffer = new byte[2048]; 
        using (var memoryStream = new MemoryStream()) 
        { 
         using (var stream = client.GetStream()) 
         { 
          stream.ReadTimeout = 1000; // 1 second timeout 
          int bytesRead; 
          // Loop until Read returns 0, signalling the socket has been closed 
          while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) 
          { 
           memoryStream.Write(buffer, 0, bytesRead); 
          } 
         } 

         // Pass the client's message to the callback and use the response as the reply message to the client. 
         var reply = Encoding.UTF8.GetBytes(callback(Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length))); 
         stream.Write(reply, 0, reply.Length); 
        } 
       } 
       catch (Exception e) 
       { 
        Console.WriteLine("Error: {0}", e.Message); 
       } 
      } 
     } 
    } 
} 

這是一個小型客戶端程序,它將連接,發送數據並等待響應。一旦接收到響應,它會通過調用replyCallback與服務器的響應:

public static class Client 
{ 
    public static void Run(string hostname, int port, string dataToSend, Action<string> replyCallback) 
    { 
     using (var client = new TcpClient(hostname, port)) 
     { 
      using (var stream = client.GetStream()) 
      { 
       var buffer = Encoding.UTF8.GetBytes(dataToSend); 
       stream.Write(buffer, 0, buffer.Length); 
       // Shutdown the send side of the socket, indicating to the server we're done sending our message 
       client.Client.Shutdown(SocketShutdown.Send); 
       using (var memoryStream = new MemoryStream()) 
       { 
        stream.ReadTimeout = 1000; // 1 second timeout 
        int bytesRead; 
        // Loop until Read returns 0, signalling the socket has been closed 
        while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) 
        { 
         memoryStream.Write(buffer, 0, bytesRead); 
        } 
        replyCallback(Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length)); 
       } 
      } 
     } 
    } 
} 

和一個小測試,線束,以將其結合在一起:

static class Program 
{ 
    static void Main(string[] args) 
    { 
     var port = 12345; 
     ThreadPool.QueueUserWorkItem(o => Server.Run(port, ProcessClientMessage)); 
     while (true) 
     { 
      Console.WriteLine("Enter a message to send and hit enter (or a blank line to exit)"); 
      var data = Console.ReadLine(); 
      if (string.IsNullOrEmpty(data)) break; 
      Client.Run("localhost", port, data, m => Console.WriteLine("Client received server reply: {0}", m)); 
     } 
    } 

    private static string ProcessClientMessage(string clientMessage) 
    { 
     Console.WriteLine("Server received client message: {0}", clientMessage); 
     // This callback would ordinarily process the client message, then return a string that will be sent back to the client in response. 
     // For now, we'll just return a fixed string value... 
     return "This is the server reply..."; 
    } 
} 
+0

我實際上想阻止它一次只處理一個請求,但沒有找到使其同步的方法。發佈完整的課程來源。 – Astronaut

+0

@AdamSurfari然後不要使用顯式*異步*的'BeginRead'。相反,只需使用「Read」。 – Iridium

+0

我用過了,但現在我陷入了閱讀功能......你知道爲什麼會出現這種情況嗎? – Astronaut