【发布时间】: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