2017-01-18 112 views
2

我已經實現了一個使用TcpClient類的套接字客戶端。所以我可以發送和接收數據,一切正常。但是我想問一些古魯的問題:)我的實現有什麼問題嗎?也許有更好的做事方式。特別是,我該如何處理斷開連接?有沒有一些指標(或者我可以自己寫一個)告訴我套接字已經斷開連接?使用TcpClient類的異步套接字客戶端C#

我也研究了Socket類的異步等待功能,但無法將我的頭包裹在「SocketAsyncEventArgs」中,爲什麼它首先出現。 爲什麼我不能:等待Client.SendAsync(「data」);

public class Client 
{ 
    private TcpClient tcpClient; 

    public void Initialize(string ip, int port) 
    { 
     try 
     { 
      tcpClient = new TcpClient(ip, port); 

      if (tcpClient.Connected) 
       Console.WriteLine("Connected to: {0}:{1}", ip, port); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
      Initialize(ip, port); 
     } 
    } 

    public void BeginRead() 
    { 
     var buffer = new byte[4096]; 
     var ns = tcpClient.GetStream(); 
     ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); 
    } 

    public void EndRead(IAsyncResult result) 
    { 
     var buffer = (byte[])result.AsyncState; 
     var ns = tcpClient.GetStream(); 
     var bytesAvailable = ns.EndRead(result); 

     Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesAvailable)); 
     BeginRead(); 
    } 

    public void BeginSend(string xml) 
    { 
     var bytes = Encoding.ASCII.GetBytes(xml); 
     var ns = tcpClient.GetStream(); 
     ns.BeginWrite(bytes, 0, bytes.Length, EndSend, bytes); 
    } 

    public void EndSend(IAsyncResult result) 
    { 
     var bytes = (byte[])result.AsyncState; 
     Console.WriteLine("Sent {0} bytes to server.", bytes.Length); 
     Console.WriteLine("Sent: {0}", Encoding.ASCII.GetString(bytes)); 
    } 
} 

和使用:

static void Main(string[] args) 
{ 
    var client = new Client(); 
    client.Initialize("127.0.0.1", 8778); 

    client.BeginRead(); 
    client.BeginSend("<Names><Name>John</Name></Names>"); 

    Console.ReadLine(); 
} 

回答

4

好花了我10秒鐘找到你可能已經做了最大的問題:

public void BeginRead() 
{ 
    var buffer = new byte[4096]; 
    var ns = tcpClient.GetStream(); 
    ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); 
} 

不過不用擔心,這就是爲什麼我們在SO上。


首先讓我解釋爲什麼它是這樣一個問題。

假設您發送的消息是4097字節長。您的緩衝區只能接受4096字節這意味着您無法將整個消息打包到此緩衝區中。或者讓我們假設您發送的是12字節長包但仍然...您正在分配4096字節在您的內存中僅用於存儲12個字節

如何處理此問題?

每當你與網絡,你應該考慮做一些種協議的工作(有些人把它的消息框,但它只是一個協議),他們會幫助您識別整個包的時間。

實施例協議可以是:

[1B =類型的消息的] [4B =長度] [XB =消息]

- where X == BitConvert.ToInt32(length);

簡單的例子:

收件人:

byte messageType = (byte)netStream.ReadByte(); 
byte[] lengthBuffer = new byte[sizeof(int)]; 
int recv = netStream.Read(lengthBuffer, 0, lengthBuffer.Length); 
if(recv == sizeof(int)) 
{ 
    int messageLen = BitConverter.ToInt32(lengthBuffer, 0); 
    byte[] messageBuffer = new byte[messageLen]; 
    recv = netStream.Read(messageBuffer, 0, messageBuffer.Length); 
    if(recv == messageLen) 
    { 
     // messageBuffer contains your whole message ... 
    } 
} 

發件人:

byte messageType = (1 << 3); // assume that 0000 1000 would be XML 
byte[] message = Encoding.ASCII.GetBytes(xml); 
byte[] length = BitConverter.GetBytes(message.Length); 
byte[] buffer = new byte[sizeof(int) + message.Length + 1]; 
buffer[0] = messageType; 
for(int i = 0; i < sizeof(int); i++) 
{ 
    buffer[i + 1] = length[i]; 
} 
for(int i = 0; i < message.Length; i++) 
{ 
    buffer[i + 1 + sizeof(int)] = message[i]; 
} 
netStream.Write(buffer); 

休息您的代碼看起來還好。但在我看來,在你的情況下使用異步操作是沒用的。您可以對同步調用執行相同的操作。

2

這很難回答,因爲這裏沒有確切的問題,但更多的代碼審查。但仍有一些提示:

  • 您的連接機制似乎不對。我不認爲TcpClient.Connected會阻塞,直到連接建立。所以它通常會在連接正在進行時失敗,然後你重新開始。您應該切換到使用阻止或異步方法。
  • SocketAsyncEventArgs是一種高性能異步數據傳輸機制。很少需要它。您應該忽略它
  • 如果要異步發送數據,則應使用返回TaskAsync方法,因爲這些方法可以輕鬆地與async/await結合使用。
  • APM模型(BeginXYZ/EndXYZ)是一種不贊成的,你不應該在新代碼中使用它。有一個問題是,有時End方法在Begin方法內同步調用,這會導致令人驚訝的行爲。如果不是這種情況,則完成回調將從ThreadPool上的隨機線程執行。這通常也不是你想要的。 TPL方法避免了這種情況。
  • 爲了您的簡單使用情況,阻塞方法也是完全正確的,不會出現各種異步方法的複雜性。

與TPL方法的代碼(未經測試)的閱讀面:

public async Task Initialize(string ip, int port) 
{ 
    tcpClient = new TcpClient; 
    await tcpClient.ConnectAsync(ip, port); 

    Console.WriteLine("Connected to: {0}:{1}", ip, port); 
} 

public async Task Read() 
{ 
    var buffer = new byte[4096]; 
    var ns = tcpClient.GetStream(); 
    while (true) 
    { 
     var bytesRead = await ns.ReadAsync(buffer, 0, buffer.Length); 
     if (bytesRead == 0) return; // Stream was closed 
     Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesRead)); 
    } 
} 

在初始化部分,你會怎麼做:

await client.Initialize(ip, port); 
// Start reading task 
Task.Run(() => client.Read()); 

對於使用同步方法刪除所有Async出現次數和用線程替換任務。

+0

'if(bytesRead == 0)return; '對我來說似乎不對? – Sir

+0

沒關係。當流從遠程端關閉時,NetworkStream.ReadAsync返回0。 然而,根據應用程序,在這裏拋出一個異常而不是僅僅返回可能更有意義。這只是一個例子。 但是在我的示例中,似乎在'ReadAsync'之前缺少了'await'。我修正了這一點。 – Matthias247

+0

你是什麼意思從遠端關閉?這與發送消息何時完成/到達EOF相比如何。但是未來可能還會有更多的消息。 目前正在努力解決的一件事是如何使用從未關閉的連接來繼續讀取來自流的傳入消息。 MS文檔沒有長期連接的例子,他們在閱讀後不久就關閉它。 – Sir