You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity C# 异步TcpListener服务端向客户端发数据失败排查

Unity异步TcpListener通信故障排查修复

核心问题点

代码存在4个直接导致通信异常的逻辑错误:

  • 服务端发送回调空引用崩溃:服务端调用BeginWrite时最后一个异步状态参数传了null,但Send_Complete回调里强制从ar.AsyncState取TcpClient实例,拿到null后执行client.GetStream().EndWrite(ar)直接抛出空引用。这类线程池线程上的异常默认不会在Unity控制台高亮提示,会直接中断当前连接的IO流程,导致消息没真正发送完成,后续逻辑全部失效。
  • 读循环状态对象与缓冲区不匹配:服务端和客户端的Client_Received回调里,每次循环读都新建了ClientStateObject实例,但调用BeginRead时用的还是旧state的缓冲区,传给回调的状态参数却是新创建的newState。下一次回调触发时,拿到的新状态对象自带全新的空缓冲区,和实际写入数据的旧缓冲区完全不对应,读出来的数据全是空值,逻辑直接错乱。
  • 服务端仅接受一次连接:BeginAcceptTcpClient只在Start里调用了一次,处理完第一个客户端连接后没有重新注册监听,后续新客户端无法接入。
  • 客户端同步连接阻塞主线程:直接在协程(运行在主线程)里调用同步Connect方法会卡住Unity引擎主线程,极端情况会导致异步IO回调注册失败。

修复后代码

1. 服务端修复

首先修改Socket_Connected方法,处理完连接立刻重新注册监听,同时修复BeginWrite传参错误:

private void Socket_Connected(IAsyncResult ar)
{
    if (ar.IsCompleted)
    {
        var listener = ar.AsyncState as TcpListener;
        // 重新注册监听,接受下一个客户端连接
        listener.BeginAcceptTcpClient(Socket_Connected, listener);
        
        var client = listener.EndAcceptTcpClient(ar);
        var state = new ClientStateObject(client);
        var msg = Encoding.ASCII.GetBytes("Hello, from TcpServer!");
        // 修复:传入client作为异步状态,不再传null
        client.GetStream().BeginWrite(msg, 0, msg.Length, Send_Complete, client);

        client.GetStream().BeginRead(state.Buffer,0,state.Buffer.Length,Client_Received,state);
    }
}

修改服务端Send_Complete方法,增加异常捕获:

private void Send_Complete(IAsyncResult ar)
{
    if (ar.IsCompleted)
    {
        try
        {
            var client = ar.AsyncState as TcpClient;
            client.GetStream().EndWrite(ar);
        }
        catch (Exception e)
        {
            Debug.LogError($"服务端发送失败: {e.Message}");
        }
    }
}

修改服务端Client_Received读逻辑,复用状态对象,增加断开判断和异常捕获:

private void Client_Received(IAsyncResult ar)
{
    if (ar.IsCompleted)
    {
        var state = ar.AsyncState as ClientStateObject;
        try
        {
            var bytesIn = state.Stream.EndRead(ar);
            // 收到0字节表示客户端已断开
            if (bytesIn == 0)
            {
                state.Client.Close();
                return;
            }
            var msg = Encoding.ASCII.GetString(state.Buffer,0,bytesIn);
            print($"From client: {msg}");
            
            // 清空缓冲区避免脏数据,直接复用现有state和缓冲区,不需要新建对象
            Array.Clear(state.Buffer, 0, state.Buffer.Length);
            state.Stream.BeginRead(state.Buffer, 0, state.Buffer.Length, Client_Received, state);
        }
        catch (Exception e)
        {
            Debug.LogError($"服务端接收异常: {e.Message}");
            state.Client?.Close();
        }
    }
}

2. 客户端修复

修改客户端Start协程,用异步连接避免阻塞主线程:

IEnumerator Start()
{
    _client = new TcpClient();
    bool connectFinished = false;
    // 异步连接,不卡主线程
    _client.BeginConnect(_ipAddress, _port, ar =>
    {
        try
        {
            _client.EndConnect(ar);
        }
        catch (Exception e)
        {
            Debug.LogError($"连接服务端失败: {e.Message}");
        }
        connectFinished = true;
    }, null);

    yield return new WaitUntil(() => connectFinished);
    if (!_client.Connected) yield break;
    
    var msg = Encoding.ASCII.GetBytes("Hello, from TcpClient!");
    _client.GetStream().BeginWrite(msg,0,msg.Length,Send_Complete,_client);

    var state = new ClientStateObject(_client);
    _client.GetStream().BeginRead(state.Buffer, 0, state.Buffer.Length, Client_Received, state);
}

修改客户端Client_Received读逻辑,和服务端逻辑一致(注意打印内容区分服务端消息):

private void Client_Received(IAsyncResult ar)
{
    Debug.Log("receiving");
    if (ar.IsCompleted)
    {
        var state = ar.AsyncState as ClientStateObject;
        try
        {
            var bytesIn = state.Stream.EndRead(ar);
            if (bytesIn == 0)
            {
                state.Client.Close();
                return;
            }
            var msg = Encoding.ASCII.GetString(state.Buffer, 0, bytesIn);
            print($"From Server: {msg}");
            
            Array.Clear(state.Buffer, 0, state.Buffer.Length);
            state.Stream.BeginRead(state.Buffer, 0, state.Buffer.Length, Client_Received, state);
        }
        catch (Exception e)
        {
            Debug.LogError($"客户端接收异常: {e.Message}");
            state.Client?.Close();
        }
    }
}

额外注意事项

  • 所有异步回调运行在.NET线程池线程,不是Unity主线程,如果收到消息后要操作GameObject、调用Unity主线程API,必须把逻辑派发到主线程执行,否则会触发线程安全错误。
  • 当前实现没有处理粘包拆包问题,实际生产环境使用需要自行设计消息协议,处理消息拆分、合并的情况。
  • OnDestroy时除了停止TcpListener,还要遍历关闭所有已连接的TcpClient,释放流资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者user3762507

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 06:21:30