2016-09-03 2 views
0

Я делаю XNA-игру, основанную на клиентском сервере, и у меня есть некоторые трудности с интеграцией механизма Message Framing в сетевую часть.TcpClient обновляет один раз, а затем застрял в прослушивании

Это сообщение класс протокола кадрирования я получил от here, с небольшим рефакторинга:

using System; 

namespace XnaCommonLib.Network 
{ 
    // Original source: http://blog.stephencleary.com/2009/04/sample-code-length-prefix-message.html 
    /// <summary> 
    /// Maintains the necessary buffers for applying a length-prefix message framing protocol over a stream. 
    /// </summary> 
    /// <remarks> 
    /// <para>Create one instance of this class for each incoming stream, and assign a handler to <see cref="MessageArrived"/>. As bytes arrive at the stream, pass them to <see cref="DataReceived"/>, which will invoke <see cref="MessageArrived"/> as necessary.</para> 
    /// <para>If <see cref="DataReceived"/> raises <see cref="System.Net.ProtocolViolationException"/>, then the stream data should be considered invalid. After that point, no methods should be called on that <see cref="PacketProtocol"/> instance.</para> 
    /// <para>This class uses a 4-byte signed integer length prefix, which allows for message sizes up to 2 GB. Keepalive messages are supported as messages with a length prefix of 0 and no message data.</para> 
    /// <para>This is EXAMPLE CODE! It is not particularly efficient; in particular, if this class is rewritten so that a particular interface is used (e.g., Socket's IAsyncResult methods), some buffer copies become unnecessary and may be removed.</para> 
    /// </remarks> 
    public class PacketProtocol 
    { 
     private const int LengthBufferSize = sizeof(int); 

     /// <summary> 
     /// Wraps a message. The wrapped message is ready to send to a stream. 
     /// </summary> 
     /// <remarks> 
     /// <para>Generates a length prefix for the message and returns the combined length prefix and message.</para> 
     /// </remarks> 
     /// <param name="message">The message to send.</param> 
     public static byte[] WrapMessage(byte[] message) 
     { 
      // Get the length prefix for the message 
      var lengthPrefix = BitConverter.GetBytes(message.Length); 

      // Concatenate the length prefix and the message 
      var ret = new byte[lengthPrefix.Length + message.Length]; 
      lengthPrefix.CopyTo(ret, 0); 
      message.CopyTo(ret, lengthPrefix.Length); 

      return ret; 
     } 

     /// <summary> 
     /// Wraps a keepalive (0-length) message. The wrapped message is ready to send to a stream. 
     /// </summary> 
     public static byte[] WrapKeepaliveMessage() 
     { 
      return BitConverter.GetBytes(0); 
     } 

     /// <summary> 
     /// Initializes a new <see cref="PacketProtocol"/>, limiting message sizes to the given maximum size. 
     /// </summary> 
     /// <param name="maxMessageBufferSize">The maximum message size supported by this protocol. This may be less than or equal to zero to indicate no maximum message size.</param> 
     public PacketProtocol(int maxMessageBufferSize) 
     { 
      // We allocate the buffer for receiving message lengths immediately 
      lengthBuffer = new byte[LengthBufferSize]; 
      maxMessageSize = maxMessageBufferSize; 
     } 

     /// <summary> 
     /// The buffer for the length prefix; this is always 4 bytes long. 
     /// </summary> 
     private readonly byte[] lengthBuffer; 

     /// <summary> 
     /// The buffer for the data; this is null if we are receiving the length prefix buffer. 
     /// </summary> 
     private byte[] dataBuffer; 

     /// <summary> 
     /// The number of bytes already read into the buffer (the length buffer if <see cref="dataBuffer"/> is null, otherwise the data buffer). 
     /// </summary> 
     private int bytesReceived; 

     /// <summary> 
     /// The maximum size of messages allowed. 
     /// </summary> 
     private readonly int maxMessageSize; 

     /// <summary> 
     /// Indicates the completion of a message read from the stream. 
     /// </summary> 
     /// <remarks> 
     /// <para>This may be called with an empty message, indicating that the other end had sent a keepalive message. This will never be called with a null message.</para> 
     /// <para>This event is invoked from within a call to <see cref="DataReceived"/>. Handlers for this event should not call <see cref="DataReceived"/>.</para> 
     /// </remarks> 
     public Action<byte[]> MessageArrived 
     { 
      get; set; 
     } 

     /// <summary> 
     /// Notifies the <see cref="PacketProtocol"/> instance that incoming data has been received from the stream. This method will invoke <see cref="MessageArrived"/> as necessary. 
     /// </summary> 
     /// <remarks> 
     /// <para>This method may invoke <see cref="MessageArrived"/> zero or more times.</para> 
     /// <para>Zero-length receives are ignored. Many streams use a 0-length read to indicate the end of a stream, but <see cref="PacketProtocol"/> takes no action in this case.</para> 
     /// </remarks> 
     /// <param name="data">The data received from the stream. Cannot be null.</param> 
     /// <exception cref="System.Net.ProtocolViolationException">If the data received is not a properly-formed message.</exception> 
     public void DataReceived(byte[] data) 
     { 
      // Process the incoming data in chunks, as the ReadCompleted requests it 

      // Logically, we are satisfying read requests with the received data, instead of processing the 
      // incoming buffer looking for messages. 

      var i = 0; 
      while (i != data.Length) 
      { 
       // Determine how many bytes we want to transfer to the buffer and transfer them 
       var bytesAvailable = data.Length - i; 
       if (dataBuffer != null) 
       { 
        // We're reading into the data buffer 
        var bytesRequested = dataBuffer.Length - bytesReceived; 

        // Copy the incoming bytes into the buffer 
        var bytesTransferred = Math.Min(bytesRequested, bytesAvailable); 
        Array.Copy(data, i, dataBuffer, bytesReceived, bytesTransferred); 
        i += bytesTransferred; 

        // Notify "read completion" 
        ReadCompleted(bytesTransferred); 
       } 
       else 
       { 
        // We're reading into the length prefix buffer 
        var bytesRequested = lengthBuffer.Length - bytesReceived; 

        // Copy the incoming bytes into the buffer 
        var bytesTransferred = Math.Min(bytesRequested, bytesAvailable); 
        Array.Copy(data, i, lengthBuffer, bytesReceived, bytesTransferred); 
        i += bytesTransferred; 

        // Notify "read completion" 
        ReadCompleted(bytesTransferred); 
       } 
      } 
     } 

     /// <summary> 
     /// Called when a read completes. Parses the received data and calls <see cref="MessageArrived"/> if necessary. 
     /// </summary> 
     /// <param name="count">The number of bytes read.</param> 
     /// <exception cref="System.Net.ProtocolViolationException">If the data received is not a properly-formed message.</exception> 
     private void ReadCompleted(int count) 
     { 
      // Get the number of bytes read into the buffer 
      bytesReceived += count; 

      if (dataBuffer == null) 
      { 
       // We're currently receiving the length buffer 

       if (bytesReceived != LengthBufferSize) 
       { 
        // We haven't gotten all the length buffer yet: just wait for more data to arrive 
       } 
       else 
       { 
        // We've gotten the length buffer 
        var length = BitConverter.ToInt32(lengthBuffer, 0); 

        // Sanity check for length < 0 
        if (length < 0) 
         throw new System.Net.ProtocolViolationException("Message length is less than zero"); 

        // Another sanity check is needed here for very large packets, to prevent denial-of-service attacks 
        if (maxMessageSize > 0 && length > maxMessageSize) 
         throw new System.Net.ProtocolViolationException("Message length " + length.ToString(System.Globalization.CultureInfo.InvariantCulture) + " is larger than maximum message size " + maxMessageSize.ToString(System.Globalization.CultureInfo.InvariantCulture)); 

        // Zero-length packets are allowed as keepalives 
        if (length == 0) 
        { 
         bytesReceived = 0; 
         MessageArrived?.Invoke(new byte[0]); 
        } 
        else 
        { 
         // Create the data buffer and start reading into it 
         dataBuffer = new byte[length]; 
         bytesReceived = 0; 
        } 
       } 
      } 
      else 
      { 
       if (bytesReceived != dataBuffer.Length) 
        // We haven't gotten all the data buffer yet: just wait for more data to arrive 
        return; 

       // We've gotten an entire packet 
       MessageArrived?.Invoke(dataBuffer); 

       // Start reading the length buffer again 
       dataBuffer = null; 
       bytesReceived = 0; 
      } 
     } 
    } 
} 

Я отлажена код, и он, кажется, функционирует должным образом. Проблема в том, как я использую этот код. Из того, что я понимаю, мне нужно звонить PacketProtocol::DataReceived каждый раз, когда я получаю данные. Но с моей точки зрения, поскольку я использую TCP, было довольно сложно понять, что именно считается Data Received, так как TCP использует Stream, тогда как UDP, например, использует дейтаграммы, поэтому определение DataReceived для меня довольно сложно для определения для TCP.

Я попытался использовать следующий вспомогательный метод, чтобы заставить его работать:

using System.IO; 
using System.Net.Sockets; 

namespace XnaCommonLib.Network 
{ 
    public static class HelperMethods 
    { 
     public static void Receive(TcpClient connection, BinaryReader reader, PacketProtocol packetProtocol) 
     { 
      var buffer = new byte[connection.ReceiveBufferSize]; 

      while (reader.Read(buffer, 0, buffer.Length) > 0) // this is where it gets stuck 
      { 
       packetProtocol.DataReceived(buffer); 
       buffer = new byte[connection.ReceiveBufferSize]; 
      } 
     } 
    } 
} 

Использование этого метода заключается в следующем:

ConnectionHandler - класс на стороне клиента сетевого управления

private void ConnectionHandler_InteractWithServer() 
{ 
    while (Connection.Connected) 
    { 
     try 
     { 
      HelperMethods.Receive(Connection, Reader, PacketProtocol); 
     } 
     catch (Exception) 
     { 
      Connection.Close(); 
      break; 
     } 

     Thread.Sleep(Constants.Time.UpdateThreadSleepTime); 
    } 
} 

Этот метод вызывается в потоке, поэтому он работает постоянно. Клиентская обратного вызова PacketProtocol это:

private void PacketProtocol_MessageRecievedCallback(byte[] data) 
{ 
    if (data.Length == 0) 
     return; 

    var stringData = Encoding.UTF8.GetString(data); 
    ProcessServerUpdate(stringData); 
    WritePlayerData(); 
} 

private void ProcessServerUpdate(string message) 
{ 
    UpdatePing(); 

    var incomingUpdate = JsonConvert.DeserializeObject<ServerToClientUpdateMessage>(message); 
    EmsServerEndpoint.BroadcastIncomingEvents(incomingUpdate.Broadcasts); 

    foreach (var update in incomingUpdate.PlayerUpdates) 
     ApplyUpdate(update); 
} 

private void ApplyUpdate(PlayerUpdate update) 
{ 
    var entity = new Entity(update.Guid); 
    if (!ClientGameManager.EntityPool.Exists(entity)) 
    { 
     var newGo = ClientGameManager.BeginAllocateRemote(entity.Id); 
     newGo.Components.Get<NetworkPlayer>().Update(update); 
     ClientGameManager.EndAllocate(newGo); 
    } 
    else 
    { 
     var remoteComponents = ClientGameManager.EntityPool.GetComponents(entity); 
     remoteComponents.Get<NetworkPlayer>().Update(update); 
    } 
} 

private void WritePlayerData() 
{ 
    var message = new ClientToServerUpdateMessage 
    { 
     Broadcasts = EmsServerEndpoint.Flush(), 
     PlayerUpdate = new PlayerUpdate(GameObject.Components) 
    }; 

    var messageBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); 
    var wrapperMessage = PacketProtocol.WrapMessage(messageBytes); 
    Writer.Write(wrapperMessage); 
} 

Это метод обратного вызова рядом методов, называемых им.

Это обратный вызов сервера для PacketProtocol:

private void PacketProtocol_MessageArrivedCallback(byte[] bytes) 
{ 
    if (bytes.Length == 0) 
     return; 

    var stringData = Encoding.UTF8.GetString(bytes); 
    ProcessClientUpdate(stringData); 
    SendServerUpdate(); 
} 

private void SendServerUpdate() 
{ 
    var message = JsonConvert.SerializeObject(new ServerToClientUpdateMessage 
    { 
     Broadcasts = EmsServerEndpoint.Flush(), 
     PlayerUpdates = PlayerUpdates() 
    }); 

    var messageBytes = Encoding.UTF8.GetBytes(message); 
    Writer.Write(PacketProtocol.WrapMessage(messageBytes)); 
} 

private void ProcessClientUpdate(string clientMessageString) 
{ 
    var clientMessage = JsonConvert.DeserializeObject<ClientToServerUpdateMessage>(clientMessageString); 
    EmsServerEndpoint.BroadcastIncomingEvents(clientMessage.Broadcasts); 
    UpdateClient(clientMessage.PlayerUpdate); 
} 

private IList<PlayerUpdate> PlayerUpdates() 
{ 
    return GameManager.EntityPool.AllThat(PlayerUpdate.IsPlayer).Select(c => new PlayerUpdate(c)).ToList(); 
} 

private void UpdateClient(PlayerUpdate playerUpdate) 
{ 
    var components = GameObject.Components; 
    components.Get<DirectionalInput>().Update(playerUpdate.Input); 
} 

Так что теперь для актуальной задачи: Код работает нормально для первого обновления - сообщение получено правильно и обновление происходит. Принимая во внимание, что и клиент, и сервер застревают в строке while (reader.Read(buffer, 0, buffer.Length) > 0).

Для начала фактического общения, это то, что ConnectionHandler делает:

WriteLoginDataToServer(name, team); 
ReadLoginResponseFromServer(); 
WritePlayerData(); 
UpdateThread.Start(); 

Перед началом резьбы обновления (который запускает цикл связи с сервером), он называет WritePlayerData, переводящих первоначальное обновление до сервер. Я бы предположил, что это заставит его позвонить SendServerUpdate, и клиент подберет его, и цикл продолжится, но этого не произойдет.

+0

Линия, в которой он находится, застрял, это метод блокировки, если данных недостаточно для чтения (buffer.length), он не вернется, но это может и не быть проблемой. Кроме того, для меня ваш код кажется litle messy: 'Something'->' ConnectionHandler_InteractWithServer() '->' HelperMethods'-> 'PacketProtocol'. Слишком большая глубина только для получения данных. – null

+0

'PacketProtocol' принимает обратный вызов для вызова, когда он получает сообщение, поэтому я даю ему метод обратного вызова, который преобразует полученный' byte [] 'в строку и десериализует строку для объекта (я работаю с jsons). 'ConnectionHandler_InteractWithServer()' - это метод потока, то есть его вызываемый один раз и выполняется в цикле до тех пор, пока не произойдет отключение. Я переместил метод «Получить» во внешний класс утилиты, потому что его логика распространена как для клиента, так и для сервера. Я думаю, что это вряд ли сложен и глубже. –

+0

Ну, может быть, это не так уж плохо, комментарии к PacketProtocol заставляют его выглядеть очень уродливо и трудно читать. О определении 'DataReceived': это зависит от того, как вы его реализуете. Здесь, при отправке, по крайней мере, это делается путем создания префикса для каждого «сообщения», которое содержит длину ('length: message'), таким образом, легко идентифицировать каждое сообщение, каждый раз, когда вы получаете« префикс », который вы читаете' length' bytes и обработать это сообщение, а затем повторить. Этот способ позволяет избежать сообщения фиксированной длины и буфера. По какой-то причине это не делается при возврате, размер буфера фиксирован на 8192 байта. – null

ответ

0
using System.IO; 
using System.Net.Sockets; 

namespace XnaCommonLib.Network 
{ 
    public static class HelperMethods 
    { 
     public static void Receive(TcpClient connection, BinaryReader reader, PacketProtocol packetProtocol) 
     { 
      var bufferSize = connection.Available; 
      var buffer = reader.ReadBytes(bufferSize); 
      packetProtocol.DataReceived(buffer); 
     } 
    } 
} 

Это то, что Receive сейчас выглядит, и это работает. Благодаря null чувак в комментариях!

0

Это, как я хотел бы подойти прием сообщений:

класс для получения данных:

class DataReceiver 
{  
public Delegate MessageReceived(string message); 
pulic MessageReceived MessageReceivedEvent; 

private void Socket; 
private bool IsReceiving; 
public Class DataReceiver(Socket sock) 
{ 
Socket = sock; 
} 
public void StartReceiving() 
    => Task.Run((Action)ReceiveLoop); 
public void StopReceiving() 
    => IsReceiving = false; 

private void ReceiveLoop() 
{ 
    IsReceiving = true; 
    while(IsReceiving) 
    { 
    byte[] LengthHeader = new byte[4]; 
    Socket.Receive(LenthHeader, 0, 4); 
    byte[] Buffer = new byte[BitConverter.ToInt32(LengthHeader); 
    Socket.Receive(Buffer, 0,Buffer.Length); 
    MessageReceivedEvent?.Invoke(Encoding.UTF8.GetString(Buffer); 
    } 
}   
} 

Вместо того, чтобы накапливать байт, пока сообщение не будет завершен вы получите header, который содержит длину из сообщение, затем получите это сообщение полностью. Затем он преобразуется в строку, и вы можете обрабатывать его по MessageReceivedEvent.

Для передачи данных вы используете эту функцию:

public void Send(string message, Socket sock) 
{ 
    byte[] LengthHeader; 
    byte[] Buffer; 
    Buffer = Encoding.UTF8.GetBytes(message); 
    LengthHeader = BitConverter.GetBytes(Buffer.Length); 
    sock.Send(LengthHeader, 0, 4); 
    socket.Send(Buffer, 0, Buffer.Length; 
} 

Вы должны реализовать это на клиенте и на сервере, и упрощает сообщение приема/передачи, вы просто должны сериализовать послать и преобразовали получить.

Смежные вопросы