C#套接字客户端:理解和管理消息

时间:2015-02-10 17:06:46

标签: c# sockets client

我需要在C#中实现一个Socket Client。

套接字服务器是通过端口3000与我的C#客户端连接的软件。

每条消息的组成如下:

  1. 某些字段:4个字节
  2. 消息长度:2个字节
  3. 某些字段:4个字节
  4. 消息索引:2个字节
  5. 某些字段:取决于“消息长度”字段
  6. 当客户端接收时,缓冲区可能包含更多的消息和重复的消息。

    我必须在消息中拆分缓冲区的内容,如果该消息不在此列表中,则将消息保存在列表中。

    据我所知,如果邮件的索引在列表中,邮件已经存在于列表中。

    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 */
    
    
            }
        }
    }
     }
    

    您是否有建议实施正确的代码来理解和管理收到的消息?

    提前致谢!

    的Fabio

3 个答案:

答案 0 :(得分: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来生成你的消息)或只是一个开关。我不确定我是否正确地解释了我在想什么。

答案 1 :(得分:0)

SocketNetworkStream包裹在BinaryReader中,而btnC_Click()可以包裹BinaryReader,从而最有效地解决了您的问题。因为您在Winforms程序中使用它,所以您还希望避免阻止UI线程,即不要在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()中执行。

这样的东西可能对你有用(为清楚起见省略了错误处理):

{{1}}

通常,异步I / O会更好。以上适用于相对较少数量的连接。鉴于这是客户端,可能只有一个连接。所以我提供的例子会很好。但请注意,将单个线程专用于每个套接字会使非常扩展得很差,即它只能有效地处理相对较少的连接。

因此,如果您发现自己编写了更详细的套接字方案,请记住这一点。在这种情况下,你会想要实现某种基于状态的机制的额外麻烦,这种机制可以为每个消息累积数据,因为它通过原始字节接收进入,你可以使用{{1} }。


最后,有几点注意事项,以解决您收到的关于问题本身的一些评论:

  • 你肯定想在这里使用TCP。 UDP是不可靠的 - 是的,它是基于消息的,但你还必须处理这样一个事实,即给定的消息可能被多次接收,消息的接收顺序可能与发送的顺序不同。 ,根本不会收到消息。

是的,TCP是面向流的,这意味着您必须在其上强加自己的消息边界。一些人与UDP相比,这是唯一的复杂因素,否则UDP会很难处理(除非你的代码本身不需要可靠性...... UDP对某些人来说肯定是好的事情,但它确实是新网络程序员的起点。)

  • 您的问题没有任何暗示您需要&#34;倾听&#34;插座。只有TCP连接的一端需要监听;那是服务器。您已明确声明您正在实施客户端,因此无需进行收听。您只需要连接到服务器(您的代码就是这样)。

答案 2 :(得分: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;
    }  
}