在UDP服务器中重用流

时间:2016-03-13 05:12:56

标签: c# sockets udp memorystream binarywriter

我正在编写UDP服务器,并且希望减少每个传入和传出数据报数据包发生的分配数量。我想做的一件事就是重新使用我分配的Streams来读取和写入我的数据报包。

我有这种方法在接收数据包时将BinaryReader的实例用于将缓冲区读取到实例对象上。

// Simplified version of the receive data method.
private void ReceiveClientData(IAsyncResult result)
{
    int receivedData = socket.EndReceiveFrom(result, ref endPoint);
    if (receivedData == 0)
    {
        this.ListenForData(socket);
        return;
    }

    // Read the header in from the buffer first so we know what kind of message and how to route.
    this.binaryReader.BaseStream.SetLength(receivedData);
    this.binaryReader.BaseStream.Seek (0, SeekOrigin.Begin);

    // Can't reset the MemoryStream buffer to the new packet buffer.
    // How does this get consumed by the stream without allocating a new MemoryStream(buffer)?
    byte[] buffer = (byte[])result.AsyncState;

    // Deserialize the bytes into the header and datagram.
    IClientHeader header = new ClientHeader();
    header.GetMessageFromBytes(this.binaryReader);
    IClientDatagram datagram = this.datagramFactory.CreateDatagramFromClientHeader(header);
    datagram.GetMessageFromBytes(this.binaryReader);

    this.ListenForData(socket);
}

重新使用相同的BinaryReader和基础MemoryStream是否可以安全地提供I Seek回到流的开头?在做了一些阅读后,看起来Socket不会同时读取或写入数据包,这要求我每次调用Stream使用EndReceiveFrom。听起来你可以同时执行每个操作,例如读取+写入,但不能并发读取或并发写入。因此,我认为我可以重新使用读者,但我不确定我是否可以。为了做到这一点,我必须给MemoryStream一个新的缓冲区来读取。你可以这样做,还是我需要为从套接字收到的每个新数据包创建一个新的MemoryBuffer

我希望采用相同的方法将数据包发送回客户端。为了做到这一点,我尝试将MemoryStream的长度设置为0并回到开头。如another answer here中所述,这似乎不会清除缓冲区。我仍然在下面的t中看到旧的缓冲区数据。

public void SendMessage(IServerDatagram message)
{
    this.binaryStream.Seek(0, SeekOrigin.Begin);
    this.binaryStream.SetLength (0);

    // Contains old buffer data.
    var t = this.binaryStream.GetBuffer ();
    message.Serialize (this.binaryWriter);

    // Contains a mixture of new bytes + old bytes.
    byte[] data = this.binaryStream.GetBuffer();
    this.binaryWriter.Flush ();

    // Send the datagram packet.
    this.udpServerSocket.SendTo(data, this.connectedClients.FirstOrDefault().Key);
}

我事先并不知道缓冲区需要多大,所以我不能SetLength达到新缓冲区的大小。没有其他方法可以重新使用Stream而不必为每条消息添加一个新的消息吗?如果我每秒发送数千封可能会导致内存压力的消息,那不是吗?

1 个答案:

答案 0 :(得分:1)

我遇到了使用流和读者以及网络内容的相关问题 - 并且选择采用一种不同的方法。我研究了Expected 'Object.keys' and instead saw 'for in'BinaryReaderBinaryWriter实现,并编写了扩展方法,直接在底层byte []缓冲区上读取和写入数据。温和的PITA,但我没有流媒体,读者或作家了。

这里,例如,是一个int-writing扩展方法:

BitConverter

返回可能看起来很糟糕,但是所有的扩展方法都返回了我“移动”到字节数组中的字节数...所以我可以一直知道我在哪里;伪流,如果你愿意的话。此外,编写代码并不总是知道它在写什么。每个简单类型都有相同的覆盖,一个用于字符串,一个用于byte []。

我的更大的顾虑是在客户端,我有一个更受约束的操作环境...我维护一个长期的读缓冲区。 [System.Security.SecuritySafeCritical] public unsafe static int Write( this byte[ ] array, int value, int offset = 0 ) { if ( offset + sizeof( int ) > array.Length ) throw new IndexOutOfRangeException( ); fixed ( byte* b = array ) { *( ( int* ) ( b + offset ) ) = value; } return sizeof( int ); } 方法为每次读取生成新的字节数组,这只会杀死我。 GC疯了......这当然对UDP流非常具有破坏性:-)所以,我找到了UdpClient.ReceiveReceive实现,发现我可以控制底层socked需要什么再次使用我自己的ReceiveAsync扩展方法:

RecieveBroadcastToBuffer

几乎正是const int MaxUdpSize = 0x0000ffff; const int AnyPort = 0; static EndPoint anyV4Endpoint = new IPEndPoint( IPAddress.Any, AnyPort ); static EndPoint anyV6Endpoint = new IPEndPoint( IPAddress.IPv6Any, AnyPort ); /// <summary>Receives a UDP datagram into the specified buffer at the specified offset</summary> /// <returns>The length of the received data</returns> public static int ReceiveBroadcastToBuffer( this UdpClient client, byte[ ] buffer, int offset = 0 ) { int received; var socket = client.Client; if ( socket.AddressFamily == AddressFamily.InterNetwork ) { received = socket.ReceiveFrom( buffer, offset, MaxUdpSize, SocketFlags.None, ref anyV4Endpoint ); } else { received = socket.ReceiveFrom( buffer, offset, MaxUdpSize, SocketFlags.None, ref anyV6Endpoint ); } return received; } 所做的......除了我管理缓冲区。我发现UdpClient.Receive使用单个缓冲区来读取,我只是在发送端使用普通的“UdpClient