【问题标题】:UDP SocketAsyncEventArgs mismatching data sent and received by server and client服务器和客户端发送和接收的 UDP SocketAsyncEventArgs 数据不匹配
【发布时间】:2022-01-17 19:24:49
【问题描述】:

我正在尝试学习游戏网络编程,所以我使用 Unity 开始了一个非常简单的异步 udp 套接字系统,但是当我添加 JSON 序列化时,事情变得有点奇怪。

我可以连接到服务器并使用SendPacket(string msg) 方法发送数据包,它会在服务器端正常接收。只要 msg 变量的大小相同或更大,它就可以正常工作。因此,如果我发送一个字符串“Hello”,然后一个“Hello World”将正常工作,并且能够以其他尺寸打印它。但是,如果我现在要发送另一个带有字符串“Hi”的数据包,则不会发生任何事情,并且该套接字上将不再有连接,也不会发送新数据包。

我已经检查过,我正在通过网络接收数据包,它不是空的,它有信息但是当它到达代码时它似乎停止了:

Packet p = e.Buffer.FromJsonBinary<Packet>();

在我的服务器端的 OnConnect 函数上。在该函数之后似乎我的服务器停止侦听不接受新连接并且不会接收其他数据包,我怀疑它以某种方式停止了我的异步进程。

对于 Json 序列化,我使用 Unity 的 JsonUtility。

有什么想法吗?

服务器类

public class Server
{
    private static string _protocolID = "hash";
    private static ushort _port = 11000;
    private Socket _socket;

    private SocketAsyncEventArgs _event;
    private List<Socket> _connections = new List<Socket>();
    
    public void Start()
    {
        Listen();
    }

    private bool Listen()
    {
        // Create UDP Socket
        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        // Set the socket into non-blocking mode 
        _socket.Blocking = false;

        try
        {
            _socket.Bind(new IPEndPoint(IPAddress.Any, _port));
            _event = new SocketAsyncEventArgs();
            _event.Completed += OnConnect;

            byte[] buffer = new byte[1024];
            _event.SetBuffer(buffer, 0, 1024);

            
            //_socket.ReceiveAsync(_event);
            StartListening(_event);
            

        }
        catch (Exception e)
        {
            Debug.LogException(e);
            return false;
        }

        return true;
    }

    private void StartListening(SocketAsyncEventArgs e)
    {
        //e.AcceptSocket = null;
        _socket.ReceiveAsync(e);
        
    }
    private void OnConnect(object sender, SocketAsyncEventArgs e)
    {
        if (e.BytesTransferred > 0)
        {
            if (e.SocketError != SocketError.Success)
                Debug.Log("ERROR"); // TODO: Close Socket

            Packet p = e.Buffer.FromJsonBinary<Packet>();
            if (p._protocolID != _protocolID)
                Debug.Log("Protocol Error");

            Debug.Log("Connect:" + p._msg);

            if (!_socket.ReceiveAsync(e))
            {
                // Call completed synchonously
                StartListening(e);
            }
        }
        else
        {
            Debug.Log("No data");
        }
    }
}

客户端类

public class Client
{
    private static string _protocolID = "hash";
    private ushort _port = 11000;
    private string _ip = "127.0.0.1";
    private Socket _socket;
    private SocketAsyncEventArgs _event;

    public bool Connect()
    {

        // Create UDP Socket
        _socket = new Socket(SocketType.Dgram, ProtocolType.Udp);

        // Set the socket into non-blocking mode 
        _socket.Blocking = false;
        IPEndPoint ip = new IPEndPoint(IPAddress.Parse(_ip), _port);

        _event = new SocketAsyncEventArgs();
        //_event.Completed += Callback;
       

        try 
        {
            _socket.Connect(ip);
            Debug.Log($"Connection was to {_ip} was sucessfull");
        } 
        catch(Exception e)
        {
            Debug.LogException(e);
            Debug.Log("Couldn't connect Socket");
            return false;
        }

        return true;
    }

    public void SendPacket<T>(T t)
    {
        try
        {
            if (_socket == null)
                Debug.Log("Null socket");

            // Encode the data string into a byte array.  
            byte[] buffer = t.ToJsonBinary();
            _event.SetBuffer(buffer, 0, buffer.Length);

            // Send the data through the socket.  
            //_socket.SendAsync(_event);

            bool willRaiseEvent = _socket.SendAsync(_event);
            //if (!willRaiseEvent)
            //{
            //    SendPacket<T>(t);
            //}
        }
        catch (SocketException e)
        {a
            Debug.LogException(e);
            Debug.Log("Couldn't Send Packet");
        }
    }

    public void SendPacket(string msg)
    {
        Packet packet = new Packet(_protocolID, msg);
        SendPacket<Packet>(packet);
    }
}

Json 序列化

    public static byte[] ToJsonBinary(this object obj)
    {
        return Encoding.ASCII.GetBytes(JsonUtility.ToJson(obj));
    }

    public static T FromJsonBinary<T>(this byte[] data)
    {
        return JsonUtility.FromJson<T>(Encoding.ASCII.GetString(data));
    }

数据包

[Serializable]
public struct Packet
{
    public string _protocolID;
    public string _msg;

    public Packet(string protocolID, string msg)
    {
        _protocolID = protocolID;
        _msg = msg;
    }
}

编辑:我发现它由于 Json Utility 而崩溃

  System.ArgumentException: JSON parse error: The document root must not follow by other values.

Json string before being sent (top), Json string reiceived at the server (bottom)

似乎服务器上的缓冲区没有被清除,所以它仍然有信息。

【问题讨论】:

  • 一般UDP 协议是连接较少的......你发出数据包并且(可能)有人监听和接收数据包,但你不在乎也不知道数据包是否会丢失之间。如果你想要一个稳定的连接并保证所有的东西都被接收到,那么你宁愿需要 TCP 协议。我还建议直接使用UdpClient
  • 也不确定,但我认为你会这样做 if (!_socket.ReceiveAsync(e)) { OnConnect(null, e); } 因为它返回 true if the I/O operation is pending. The Completed event on the e parameter will be raised upon completion of the operation. false if the I/O operation completed synchronously. In this case, The Completed event on the e parameter will not be raised and the e object passed as a parameter may be examined immediately after the method call returns to retrieve the result of the operation. 它也应该被称为 OnReceived 而不是 OnConnect ;)
  • 我正在为游戏制作它,所以 TCP 会有点慢,但丢失数据包的问题很好。但是问题是,如果我使用字符串大小为 5 的函数 SendPacket,然后使用字符串大小为 6 的函数,依此类推,只要字符串大小更大或与最后一个相同,数据包就可以到达。但是,如果我现在发送一个较小的字符串,它就行不通了。我将收到无法反序列化的数据包!你也完全正确,它应该是 OnReceived,但我试图通过 UDP 进行虚拟连接,就像 TCP 一样!
  • but I was trying to do a virtual connection over UDP sort of like TCP does .. 正如所说,真的没有这样的事情。这将需要您有一个消息计数器并确认每个收到的包裹的消息并重新发送丢失的包裹并延迟直到您按顺序收到所有包裹......然后您可以再次使用 TCP ;)
  • 如前所述,您是否尝试过使用而不是 if (!_socket.ReceiveAsync(e)) { OnConnect(null, e); } ?还要记住,每条 UDP 消息都有一个大约 46 bytes 开销的标头......所以发送这么小的消息是一种巨大的浪费,你可能想想出一个系统来将多个消息捆绑在一个 UDP 包中总大小 1500 字节 ...

标签: c# .net unity3d networking


【解决方案1】:

好的,看来问题出在 SocketAsyncEventArgs 的工作方式上。由于我递归调用并传递相同的事件,它永远不会清除它的缓冲区。

 if (!_socket.ReceiveAsync(e))
       {
            OnConnect(null, e);
       }

我发现了两种解决方案,一种是传递一个新的 SocketAsyncEventArgs,它将有一个清晰的缓冲区。但我相信一个更好的主意是创建一个池来处理和重用一组预定义的 SocketAsyncEventArgs,而不是不断创建新的。

我发现的另一个解决方案是简单地设置一个新缓冲区来清除最后一个缓冲区。

byte[] buffer = t.ToJsonBinary();
_event.SetBuffer(buffer, 0, buffer.Length);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-10
    • 1970-01-01
    • 2017-08-20
    • 1970-01-01
    • 2021-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多