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