C#Socket客户端:理解和管理消息

C#Socket客户端:理解和管理消息,c#,sockets,client,C#,Sockets,Client,我需要在C#中实现一个套接字客户端 socket服务器是一个通过端口3000与我的C#客户端连接的软件 每条信息的组成如下: 部分字段:4字节 消息长度:2字节 部分字段:4字节 消息索引:2字节 某些字段:取决于“消息长度”字段 当客户端接收时,缓冲区可以包含一条消息和多条重复消息 我必须在消息中拆分缓冲区的内容,如果该消息尚未出现在此列表中,则将该消息保存在列表中 我理解,如果邮件索引在列表中,则邮件已存在于列表中 using System; using System.Collections

我需要在C#中实现一个套接字客户端

socket服务器是一个通过端口3000与我的C#客户端连接的软件

每条信息的组成如下:

  • 部分字段:4字节
  • 消息长度:2字节
  • 部分字段:4字节
  • 消息索引:2字节
  • 某些字段:取决于“消息长度”字段
  • 当客户端接收时,缓冲区可以包含一条消息和多条重复消息

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

    我理解,如果邮件索引在列表中,则邮件已存在于列表中

    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

    您可以这样实现您的信息:

    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();
        }
    }
    
    公共抽象类网络消息
    {
    私有列表缓冲区;
    受保护的抽象空内部反序列化(BinaryReader);
    受保护的网络消息()
    {
    _buffer=新列表();
    }
    public void反序列化()
    {
    使用(MemoryStream stream=new MemoryStream(\u buffer.ToArray())
    {
    BinaryReader=新的BinaryReader(流);
    这个。内部反序列化(读卡器);
    }
    }
    }
    公共类YourMessage:NetworkMessage
    {
    公共int YourField
    {
    收到
    设置
    }
    受保护的重写无效内部反序列化(BinaryReader)
    {
    YourField=reader.ReadInt32();
    }
    }
    
    您必须关心非阻塞网络。我的意思是,如果您正在等待(无限时间)在按钮事件中接收一些数据,您将阻止您的客户端。有很多关于它的教程:)


    我认为在你的“1.某些字段4字节”中,每条消息都有一个Id。您最终可以创建一个
    Dictionnary
    ,它将通过您的id返回好消息(如果您对反射稍有了解,您可以创建一个Func来生成消息),或者只创建一个开关。我不确定我是否正确地解释了我的想法。

    NetworkStream
    中包装
    Socket
    似乎可以最有效地解决您的问题,而
    BinaryReader
    反过来也可以包装
    Socket
    。因为您在Winforms程序中使用了此功能,所以还需要避免阻塞UI线程,即不要在
    btnC\u Click()
    事件处理程序方法中运行i/O本身

    不幸的是,stock
    BinaryReader
    不提供
    async
    方法,因此最简单的解决方案是使用同步I/O,但在
    任务中执行它

    类似的方法可能适合您(为了清楚起见,省略了错误处理):

    一般来说,异步I/O会更好。上述方法适用于数量相对较少的连接。考虑到这是客户端,很可能您只有一个连接。因此,我提供的示例将很好地工作。但是请注意,为每个套接字指定一个线程将很难扩展,也就是说,它只能有效地处理相对较少的连接

    因此,当您发现自己正在编写一个更复杂的套接字场景时,请记住这一点。在这种情况下,实现某种基于状态的机制会增加额外的麻烦,这种机制可以在每条消息通过原始字节接收时为其积累数据,您可以使用
    NetworkStream.ReadAsync()


    最后,请注意以下几点,以说明您收到的关于问题本身的一些评论:

    • 你肯定想在这里使用TCP。UDP是不可靠的–是的,它是基于消息的,但您还必须处理这样一个事实:给定消息可能会被多次接收,消息的接收顺序可能与发送顺序不同,并且消息可能根本不会被接收
    是的,TCP是面向流的,这意味着您必须将自己的消息边界强加给它。但与UDP相比,这是唯一的复杂之处,否则UDP的处理会复杂得多(当然,除非您的代码本身不需要可靠性……UDP在某些方面确实很好,但对于新的网络程序员来说,它绝对不是一个开始的地方)

    • 你的问题并没有暗示你需要一个“倾听”插座。只需要侦听TCP连接的一端;那就是服务器。您已经明确声明要实现客户机,因此不需要监听。您只需要连接到服务器(代码就是这样做的)

    为了简单起见,我首先使用
    TcpClient
    而不是原始套接字。然后从流中读取—读取您知道的数据量,解释它,然后读取其余的数据
    BinaryReader
    对此非常有用。您需要查看消息帧。在这种情况下使用Tcp将产生比您预期的复杂得多的代码。例如,您也可能只收到消息的一部分。即使只有一个字节也是可能的,因此您甚至不知道消息的长度,只是接收消息的第一部分。这是因为TCP是一种流协议。在您的情况下,尽可能使用UDP。使用UDP,您将收到单个消息。这种设计要简单得多。对于客户端和服务器,我通常使用。要从一种状态进入下一种状态,您需要验证缓冲区是否包含至少一条完整的信息,例如完整的12字节消息头。您不断在缓冲区中积累数据,并尝试根据当前状态和缓冲区内容转换到新状态。请注意,由于接收dat,可能会发生多个状态转换
    // 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;
        }
    }
    
    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;
        }  
    }