我正在构建一个服务器/客户端应用程序,并且我正在查看用于分离数据包的选项。我已经读过,最合适的一个是创建一个包含有效载荷有多大的信息的标题,然后读取直到它结束。
这是如何以编程方式工作的?
使用" \ n"分隔它们。新队。一个恰当的例子会很好。
我以这种方式异步接收数据:
private void AsyncReceive(IAsyncResult result)
{
int bytesTransfered;
try
{
bytesTransfered = _handle.EndReceive(result);
if(bytesTransfered <= 0)
{
throw new Exception("No bytes transfered");
}
}
catch(NullReferenceException)
{
return;
}
catch(ObjectDisposedException)
{
return;
}
catch(Exception)
{
return;
}
byte[] received = new byte[bytesTransfered];
try
{
Array.Copy(_readBuffer, received, received.Length);
}
catch(Exception)
{
Disconnect();
return;
}
// How should I process the received data now?
try
{
_handle.BeginReceive(_readBuffer, 0, _readBuffer.Length, SocketFlags.None, AsyncReceive, null);
}
catch(ObjectDisposedException)
{
return;
}
catch(Exception)
{
}
}
答案 0 :(得分:2)
首先,您需要区分不同类型的消息。您可以使用单字节,这将允许最多255种不同的消息类型。为此创建一个枚举,并使用一个属性来标记您的消息(见下文):
enum MessageType : byte {
FirstMessage,
SecondMessage
}
class MessageAttribute : Attribute {
public MessageAttribute(MessageType type) {
Type = type;
}
public MessageType Type { get; private set; }
}
其次,您的消息需要紧凑的序列化程序。一个很好的选择是protobuf - 它非常紧凑(不会序列化属性名称,只有值等等),同时仍然易于使用。
[Message(MessageType.FirstMessage)]
[ProtoContract]
class MyFirstMessage {
[ProtoMember(1)]
public string Value { get; set; }
[ProtoMember(2)]
public int AnotherValue { get; set; }
}
[Message(MessageType.SecondMessage)]
[ProtoContract]
class MySecondMessage {
[ProtoMember(1)]
public decimal Stuff { get; set; }
}
第三,你需要知道消息的长度,正如来电者所说。使用2或4个字节(分别为Int16和Int32类型的大小)。
所以我们的格式是:1字节 - 消息类型。 2-5字节 - 消息大小,5-5 +大小字节 - protobuf序列化消息。然后按照以下定义分三个步骤阅读您的信息流:
class MessageReader {
static readonly Dictionary<byte, Type> _typesMap = new Dictionary<byte, Type>();
static MessageReader() {
// initialize your map
// this is executed only once per lifetime of your app
foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(c => c.GetCustomAttribute<MessageAttribute>() != null)) {
var message = type.GetCustomAttribute<MessageAttribute>();
_typesMap.Add((byte)message.Type, type);
}
}
public async Task<object> Read(Stream stream) {
// this is your network or any other stream you have
// read first byte - that is message type
var firstBuf = new byte[1];
if (await stream.ReadAsync(firstBuf, 0, 1) != 1) {
// failed to read - end of stream
return null;
}
var type = firstBuf[0];
if (!_typesMap.ContainsKey(type)) {
// unknown message, handle somehow
return null;
}
// read next 4 bytes - length of a message
var lengthBuf = new byte[4];
if (await stream.ReadAsync(lengthBuf, 0, 4) != 4) {
// read less than expected - EOF
return null;
}
var length = BitConverter.ToInt32(lengthBuf, 0);
// check if length is not too big here! or use 2 bytes for length if your messages allow that
if (length > 1*1024*1024) {
// for example - adjust to your needs
return null;
}
var messageBuf = new byte[length];
if (await stream.ReadAsync(messageBuf, 0, length) != length) {
// didn't read full message - EOF
return null;
}
try {
return ProtoBuf.Serializer.NonGeneric.Deserialize(_typesMap[type], new MemoryStream(messageBuf));
}
catch {
// handle invalid message somehow
return null;
}
}
}
从流中读取一条消息后 - 继续以相同的方式阅读下一条消息。读取调用将阻塞,直到新数据到达。如果有任何违反协议的行为 - 丢弃连接。
答案 1 :(得分:0)
您是否考虑过使用TCPClient和TCPListener,然后使用NetworkStream?套接字的级别很低,在大多数情况下可能不需要。
看这篇文章: How reading messages from server?(TCP)
此外,除非您记录并重新抛出异常,否则不要捕获无法恢复的异常。当异常被静默吞噬时,这将导致很难调试行为。