我需要在C#中实现一个Socket Client。
套接字服务器是通过端口3000与我的C#客户端连接的软件。
每条消息的组成如下:
当客户端接收时,缓冲区可能包含更多的消息和重复的消息。
我必须在消息中拆分缓冲区的内容,如果该消息不在此列表中,则将消息保存在列表中。
据我所知,如果邮件的索引在列表中,邮件已经存在于列表中。
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
答案 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)
Socket
将NetworkStream
包裹在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相比,这是唯一的复杂因素,否则UDP会很难处理(除非你的代码本身不需要可靠性...... UDP对某些人来说肯定是好的事情,但它确实不是新网络程序员的起点。)
答案 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;
}
}