【问题标题】:Asynchronous socket hangs intermittently on Unity-iOS异步套接字在 Unity-iOS 上间歇性挂起
【发布时间】:2019-12-15 06:12:53
【问题描述】:

我目前正在编写一个网络控制器脚本,它使用 C# Socket 类与专用服务器进行通信。

它利用异步回调方法,以便异步发送/接收数据并相应地处理它们。

大多数时候,它可以完美地跨平台运行(iOS 和 Android)。

但是,有时套接字会默默地“挂起”而没有任何明确的错误,既不发送也不接收来自服务器的更多数据。 (在我的测试中,它总是发生在 iOS 客户端上。)

我再次检查以确保这不是服务器端的问题。

有趣的是,服务器发现与受影响客户端的套接字连接仍然存在,强制退出客户端仍然会导致它断开连接。只是调用发送/接收失败。

其他未受影响的设备继续正常发送和接收数据。

从这种情况中恢复的唯一方法是强制关闭并重新打开套接字,这是不切实际的,因为发生这种情况时似乎没有引发异常。 (我曾经在调用我现在正在处理的 SendAsync 时遇到“InvalidOperationException:没有正在进行的操作”——但为什么会发生这种情况?)

可能是什么原因?

以下是部分代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Net;
using System.Net.Sockets;

public class NetworkController : MonoBehaviour
{
    public static NetworkController instance;

    private Socket _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    private byte[] _receiveBuffer = new byte[8142];

    private List<byte> _inBuffer;

    void Awake()
    {
        if (!instance)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        Connect();
    }

    private void Connect()
    {

        _inBuffer = new List<byte>();

        SetupServer();
    }

    private void Disconnect()
    {

        _clientSocket.Disconnect(true);
        _inBuffer = null;
    }

    IEnumerator Reconnect()
    {
        Disconnect();
        yield return new WaitForSeconds(5);
        Connect();
    }

    private void SetupServer()
    {
        try
        {
            _clientSocket.Connect(new IPEndPoint(IPAddress.Parse(IP), PORT));
        }
        catch (SocketException ex)
        {
            Debug.Log(ex.Message);

            StartCoroutine(Reconnect());
        }

        _clientSocket.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);

    }

    private void CheckForMessages()
    {
        while (true)
        {
            if (_inBuffer.Count < sizeof(int))
            {
                return;
            }

            int msgLength = BitConverter.ToInt32(_inBuffer.ToArray(), 0);
            msgLength = IPAddress.NetworkToHostOrder(msgLength);
            if (_inBuffer.Count < msgLength + 4)
            {
                return;
            }

            byte[] message = _inBuffer.GetRange(4, msgLength).ToArray();
            ProcessMessage(message);

            int amtRemaining = _inBuffer.Count - msgLength - sizeof(int);
            if (amtRemaining == 0)
            {
                _inBuffer = new List<byte>();
            }
            else
            {
                _inBuffer = _inBuffer.GetRange(msgLength + 4, amtRemaining);
            }

        }
    }

    private void ReceiveCallback(IAsyncResult AR)
    {
        try
        {
            int received = _clientSocket.EndReceive(AR);

            if (received <= 0)
            {
                StartCoroutine(Reconnect());
                return;
            }

            byte[] recData = new byte[received];
            Buffer.BlockCopy(_receiveBuffer, 0, recData, 0, received);

            _inBuffer.AddRange(recData);
            CheckForMessages();

            _clientSocket.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
        }
        catch (SocketException)
        {
            StartCoroutine(Reconnect());
        }
    }

    private void SendData(byte[] data)
    {
        SocketAsyncEventArgs socketAsyncData = new SocketAsyncEventArgs();
        socketAsyncData.SetBuffer(data, 0, data.Length);
        try
        {
            _clientSocket.SendAsync(socketAsyncData);
        }
        catch (Exception)
        {
            StartCoroutine(Reconnect());
        }
    }
}

编辑

Here 是套接字在62.838564 挂起之前的完整数据包日志(由于大尺寸而建立链接)。

【问题讨论】:

  • 连接失败后可以通过cmd.exe>netstat -a查看连接状态,看看连接是否有错误。您还可以使用嗅探器来帮助确定失败的位置/时间。如果您看到表明连接已关闭的 [FIN],请使用嗅探器检查。
  • @jdweng 我已经对连接到 iPhone 设备的 Wireshark 进行了一些测试,但是当问题重现时我没有看到任何 [FIN]。这是我在连接失败之前看到的最后一个条目。 imgur.com/a/eQIxBY5 尝试从那时起发送更多数据包不会产生更多输出,但退出应用程序会导致 [FIN](这是预期的)。
  • 看起来数据只在一个方向发送(10.0.1.4 到 112.184.7.251)。另一个方向的所有内容都是 52 字节的 ACK(第一行除外)。似乎缺少 361456 的 ACK。如果没有 ACK,则应在 5 秒后(或更早)重试。通常有 3 次重试,因此 361456 可能是第 3 次重试。
  • 需要考虑三件事 1) 没有重试 2) 由于末尾的 [FIN] 连接仍然良好 3) 它不会在 Android 上挂起。这让我相信应用程序和设备上的以太网驱动程序之间存在问题。设备上的以太网驱动程序仍连接到服务器。因此,要么应用程序挂起,要么应用程序和以太网驱动程序之间存在通信问题。我会确保设备已更新以太网驱动程序,并且设备上有最新版本的应用程序。设备上的另一个应用程序可能导致设备挂起。
  • 如何在回调例程中加一个锁以确保每个 BeginSend 都获得一个 EndSend。在您发送另一条消息之前,EndSend 可能未完成。

标签: c# sockets unity3d asynchronous tcp


【解决方案1】:

我似乎已经解决了这个问题。

这是我所做的:

使用BeginSend 代替SendAsync。在BeginSend 和调用EndSend 的回调中放置一个线程锁,以便每个BeginSend 在另一个BeginSend 之前获得一个EndSend

代码如下:

    private ManualResetEvent sendDone =
        new ManualResetEvent(false);

    private void SendData(byte[] data)
    {
        _clientSocket.BeginSend(data, 0, data.Length, 0,
            new AsyncCallback(SendCallback), null);

        sendDone.WaitOne();
        sendDone.Reset();
    }

    private void SendCallback(IAsyncResult ar)
    {
        try
        {
            int bytesSent = _clientSocket.EndSend(ar);

            sendDone.Set();
        }
        catch (Exception)
        {
            StartCoroutine(Reconnect());
        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 2016-09-14
    相关资源
    最近更新 更多