2015-02-10 36 views
0

我需要在C#中實現套接字客戶端。C#套接字客戶端:瞭解和管理消息

套接字服務器是通過端口與我的C#客戶端連接的軟件3000。

每個消息由如下:

  1. 某些字段:4個字節
  2. 信息長度:2個字節
  3. 某些字段:4個字節
  4. 消息指數:2個字節
  5. 某些字段:取決於第e'消息長度'字段

當客戶端接收時,緩衝區可能包含更多的一條消息和重複的消息。

我必須拆分消息中緩衝區的內容,如果該消息不存在於此列表中,則將消息保存在列表中。

據我所知,如果消息的索引在列表中,消息已經存在於列表中。

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 
using System.Net; 
using System.Net.Sockets; 

namespace Client 
{ 
public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
    } 

    Socket sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 

    IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 3000); 


    private void btnC_Click(object sender, EventArgs e) 
    { 

     sck.Connect(endPoint); 

     if (sck.Connected) 
     { 
      Form1.ActiveForm.Text = Form1.ActiveForm.Text + " - Connected"; 
     } 

     byte[] buffer = new byte[255]; 
     int rec; 

     while (true) 
     { 
      buffer = new byte[255]; 
      rec = sck.Receive(buffer, 0, buffer.Length, 0); 
      Array.Resize(ref buffer, rec); 


      /* Understand and Manage the messages */ 


     } 
    } 
} 
} 

你有一些建議,實施正確的代碼來理解和管理收到的消息嗎?

在此先感謝!

法比奧

+1

我會用'TcpClient'代替原始套接字的,爲了簡單起見啓動。然後從流中讀取 - 讀取您瞭解的數據量,解釋它,然後讀取其餘的數據。 'BinaryReader'可以對此非常有幫助。 – 2015-02-10 17:11:25

+0

您需要查看消息框架。 – 2015-02-10 17:25:08

+0

在這樣的情況下使用Tcp將產生比您期望的更復雜的代碼。例如,您可能也只收到一部分消息。即使只有一個字節是可能的,所以你甚至不知道消息的長度,但只是接收它的第一部分。這是因爲TCP是一個流媒體協議。儘可能在你的情況下使用UDP。使用UDP,您將收到單個消息。這種設計更簡單。無論是客戶端還是服務器。 – 2015-02-10 17:33:08

回答

0

您可以實現您的留言那樣:

public abstract class NetworkMessage 
{ 
    private List<byte> _buffer; 

    protected abstract void InternalDeserialize(BinaryReader reader); 

    protected NetworkMessage() 
    { 
     _buffer = new List<byte>(); 
    } 

    public void Deserialize() 
    { 
     using (MemoryStream stream = new MemoryStream(_buffer.ToArray())) 
     { 
      BinaryReader reader = new BinaryReader(stream); 
      this.InternalDeserialize(reader); 
     } 
    } 
} 

public class YourMessage : NetworkMessage 
{ 
    public int YourField 
    { 
     get; 
     set; 
    } 

    protected override void InternalDeserialize(BinaryReader reader) 
    { 
     YourField = reader.ReadInt32(); 
    } 
} 

你必須關心無阻塞網絡。我的意思是,如果您在等待(無限時間)在按鈕事件中接收一些數據,您將阻止您的客戶端。有很多關於它的教程:)

而且我認爲在你的「1.有些字段4字節」,每個消息都有一個Id。你最終可以創建一個Dictionnary<id, Networkmessage>,它將通過你的id返回好消息(如果你對反射有所瞭解,可以創建一個Func來生成你的消息)或者只是一個開關。我不確定我解釋了我在想什麼。

0

您的方案似乎最有效地通過將Socket包裝在NetworkStream中得到解決,而NetworkStream可能被BinaryReader包裹。由於您在Winforms程序中使用了此方法,因此您還需要避免阻塞UI線程,即不要在btnC_Click()事件處理程序方法中運行I/O本身。

不幸的是,股票BinaryReader不提供async方法,所以最簡單的解決方案是使用同步I/O,但在Task執行它。

像這樣的事情可能會爲你工作(爲了清楚省略了錯誤處理):

// Simple holder for header and data 
class Message 
{ 
    public int Field1 { get; private set; } 
    public short Length { get; private set; } 
    public int Field2 { get; private set; } 
    public short Index { get; private set; } 
    public byte[] Data { get; private set; } 

    public Message(int field1, short length, int field2, int index, byte[] data) 
    { 
     Field1 = field1; 
     Length = length; 
     Field2 = field2; 
     Index = index; 
     Data = data; 
    } 
} 

private void btnC_Click(object sender, EventArgs e) 
{ 
    sck.Connect(endPoint); 

    // If Connect() completes without an exception, you're connected 
    Form1.ActiveForm.Text = Form1.ActiveForm.Text + " - Connected"; 

    using (NetworkStream stream = new NetworkStream(sck)) 
    using (BinaryReader reader = new BinaryReader(stream)) 
    { 
     Message message; 

     while ((message = await Task.Run(() => ReadMessage(reader))) != null) 
     { 
      // process message here, preferably asynchronously 
     } 
    } 
} 

private Message ReadMessage(BinaryReader reader) 
{ 
    try 
    { 
     int field1, field2; 
     short length, index; 
     byte[] data; 

     field1 = reader.ReadInt32(); 
     length = reader.ReadInt16(); 
     field2 = reader.ReadInt32(); 
     index = reader.ReadInt16(); 

     // NOTE: this is the simplest implementation based on the vague 
     // description in the question. I assume "length" contains the 
     // actual length of the _remaining_ data, but it could be that 
     // the number of bytes to read here needs to take into account 
     // the number of bytes already read (e.g. maybe this should be 
     // "length - 20"). You also might want to create subclasses of 
     // Message that are specific to the actual message, and use 
     // the BinaryReader to initialize those based on the data read 
     // so far and the remaining data. 

     data = reader.ReadBytes(length); 
    } 
    catch (EndOfStreamException) 
    { 
     return null; 
    } 
} 

一般來說,異步I/O會更好。以上適用於相對較少數量的連接。考慮到這是客戶端,很可能你只有一個連接。所以我提供的例子可以正常工作。但請注意,爲每個套接字指定一個單獨的線程將極其不利地縮放,,即它只能處理相對較少數量的連接。

所以請記住,如果當你發現自己編碼一個更復雜的套接字場景。在這種情況下,你會想要實現某種基於狀態機制的額外麻煩,該機制可以爲每條消息彙總數據,因爲它是通過原始字節接收進行的,您可以使用NetworkStream.ReadAsync()來完成此操作。


最後,一對夫婦的注意事項,以解決一些您的問題本身收到的意見:

  • 你絕對想在這裏使用TCP。 UDP是不可靠的–是的,它是基於消息的,但是你也必須處理一個事實,即一個給定的消息可能被多次接收,消息可能以不同於它們發送的順序被接收,並且消息可能根本沒有收到。

是的,TCP是面向流的,這意味着您必須在其上添加自己的消息邊界。但是這是與UDP相比的唯一複雜情況,否則UDP會更復雜些(當然除非你的代碼固有地不需要可靠性; UDP對於某些事情肯定是有好處的,但它絕對是而不是開始爲一個新的網絡程序員)。

  • 有沒有關於你的問題,建議你需要一個「傾聽」套接字。只有TCP連接的一端需要監聽;那就是服務器。您已明確指出您正在實施客戶端,因此不需要收聽。你只需要連接到服務器(你的代碼就是這樣)。
0
bool getHeader = True; 
int header_size = 4+2+4+2; 
int payload_size = 0; 
byte[] header = new byte[header_size]; 
byte[] buffer = new byte[255]; 
int rec; 

while (true) 
{ 
    if(getHeader) 
    { 
     rec = sck.Receive(header, 0, header_size, 0); 
     payload_size = ..... parse header[4] & header[5] to payload size (int) 
     getHeader = False; 
    }else{ 
     rec = sck.Receive(buffer, 0, payload_size, 0); 
     getHeader = True; 
    } 
} 
+0

應該解釋代碼的作用,而不是將它放在可能不知道如何閱讀它的人身上。讓我們知道,你不只是複製粘貼這個答案 – 2015-07-31 15:51:17

相關問題