2025-05-07 Unity 网络基础7——TCP异步通信

发布于:2025-05-09 ⋅ 阅读:(20) ⋅ 点赞:(0)

1 同步与异步

  • 同步方法

    方法中逻辑执行完毕后,再继续执行后面的方法。

  • 异步方法

    方法中逻辑可能还没有执行完毕,就继续执行后面的内容。

    往往异步方法当中都会使用多线程执行某部分逻辑,因为不需要等待方法中逻辑执行完毕就可以继续执行下面的逻辑。

注意

​ Unity 中协同程序中的某些异步方法,有的使用的是多线程,有的使用的是迭代器分步执行。

2 常用异步方法

2.1 Beign / End 方法

IAsyncResult

IAsyncResult 接口由包含可异步作的方法的类实现,是启动异步作的方法的返回类型。

​ 当异步调用完成时,将向 WaitHandle 发出信号,可以通过调用 WaitOne 方法来等待它。

image-20250507101504475
  • AsyncState:调用异步方法时传入的参数,需要转换为传入的参数类型。
  • AsyncWaitHandle:用于同步等待。
  • CompletedSynchronously:异步操作是否同步完成。
  • IsCompleted:异步操作是否完成。

BeginConnect() / EndConnect()

BeginConnect()

​ 当调用BeginConnect方法时,该方法立即返回一个IAsyncResult对象,而不会等待连接完成。调用者可以继续执行其他操作,直到连接操作完成,然后通过EndConnect方法获取连接结果。

image-20250507101338850
  • remoteEP:表示远程主机的终结点(EndPoint)。
  • callback:表示异步操作完成时要调用的回调方法。
  • state:表示与异步操作关联的状态对象,可以是任何对象(通常传递 Socket 实例)。
  • address:表示远程主机的 IP 地址。
  • port:表示远程主机的端口号。
  • requestCallback:表示异步操作完成时要调用的回调方法。
  • host:表示远程主机的域名或 IP 地址。

EndConnect()

​ 通常与BeginConnect方法成对使用。

image-20250507102508695
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        var socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        socketTcp.BeginAccept(AcceptCallBack, socketTcp);
        
        var ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
        socketTcp.BeginConnect(ipPoint, result =>
        {
            Socket s = result.AsyncState as Socket;
            
            try
            {
                s.EndConnect(result);
                print("连接成功!");
            }
            catch (SocketException e)
            {
                print("连接出错:" + e.SocketErrorCode + e.Message);
            }

        }, socketTcp);
    }
    
    private void AcceptCallBack(IAsyncResult result)
    {
        ...
    }
}

BeginAccept() / EndAccept()

BeginAccept()

​ 异步开始接受传入的连接请求,避免阻塞主线程。需传入回调函数和状态对象(通常是原始 Socket 实例)。

image-20250507101413415
  • callback:连接建立后的回调函数。
  • state:状态对象(通常是原始 Socket 实例)。
  • receiveSize:缓冲区大小。
  • acceptSocket:接受套接字。

EndAccept()

​ 在回调函数中调用,用于完成异步连接操作并返回新的客户端 Socket。

​ 会返回一个新的 Socket 实例,用于与客户端通信。

image-20250507101438925
  • buffer:输出参数,用于接收客户端发送的数据。这个缓冲区的大小应该足够大,以容纳客户端发送的数据。
  • asyncResultIAsyncResult对象,表示异步操作的状态。
  • bytesTransferred:输出参数,用于返回实际传输的字节数。
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        var socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        ...
            
        socketTcp.BeginAccept(AcceptCallBack, socketTcp);
    }
    
    private void AcceptCallBack(IAsyncResult result)
    {
        try
        {
            // 获取传入的参数
            Socket s = result.AsyncState as Socket;
            
            // 通过调用 EndAccept 得到连入的客户端 Socket
            Socket clientSocket = s.EndAccept(result);

            // do something with clientSocket
            // ...
        }
        catch (SocketException e)
        {
            print(e.SocketErrorCode);
        }
    }
}

BeginSend() / EndSend()

BeginSend()

image-20250507104222504
  • buffer:接收数据的缓冲区。
  • offset:缓冲区中开始接收数据的偏移量。
  • size:要接收的最大字节数。
  • socketFlags:控制接收操作的标志。
  • errorCode:输出参数,表示操作的结果。
  • callback:异步操作完成时调用的回调方法。
  • state:与异步操作关联的用户状态对象。

EndSend()

image-20250507104457578
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        ...
        
        var bytes = Encoding.UTF8.GetBytes("1231231231223123123");
        socketTcp.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, (result) =>
        {
            try
            {
                socketTcp.EndSend(result);
                print("发送成功");
            }
            catch (SocketException e)
            {
                print("发送错误" + e.SocketErrorCode + e.Message);
            }
        }, socketTcp);
    }
}

BeginReceive() / EndReceive()

BeginReceive()

image-20250507103211002

EndReceive()

image-20250507103659234
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        ...
        
        socketTcp.BeginReceive(_resultBytes, 0, _resultBytes.Length, SocketFlags.None, ReceiveCallBack, socketTcp);

    }
    
    private void ReceiveCallBack(IAsyncResult result)
    {
        try
        {
            Socket s = result.AsyncState as Socket;
            
            // 这个返回值是你受到了多少个字节
            int num = s.EndReceive(result);
            
            // 进行消息处理
            Encoding.UTF8.GetString(_resultBytes, 0, num);

            // 我还要继续接受
            s.BeginReceive(_resultBytes, 0, _resultBytes.Length, SocketFlags.None, ReceiveCallBack, s);
        }
        catch (SocketException e)
        {
            print("接受消息处问题" + e.SocketErrorCode + e.Message);
        }
    }
}

2.2 Async 方法

​ Async 方法相比于 Begin / End 方法,参数相对少一些。主要参数配置集中在 SocketAsyncEventArgs 中,该类封装了异步操作所需的参数和结果。

ConnectAsync()

image-20250507105600083
  • SocketTypeProtocolType参数用于指定 Socket 的类型和协议。
  • SocketAsyncEventArgs对象e包含了连接所需的参数。
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        ...
        
        SocketAsyncEventArgs e2 = new SocketAsyncEventArgs();
        e2.Completed += (socket, args) =>
        {
            if (args.SocketError == SocketError.Success)
            {
                //连接成功
            }
            else
            {
                //连接失败
                print(args.SocketError);
            }
        };
        socketTcp.ConnectAsync(e2);
    }
}

AcceptAsync()

image-20250507105022814
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        ...
        
        var e = new SocketAsyncEventArgs();
        e.Completed += (socket, args) =>
        {
            //首先判断是否成功
            if (args.SocketError == SocketError.Success)
            {
                //获取连入的客户端socket
                Socket clientSocket = args.AcceptSocket;

                (socket as Socket).AcceptAsync(args);
            }
            else
            {
                print("连入客户端失败" + args.SocketError);
            }
        };
        socketTcp.AcceptAsync(e);
    }
}

SendAsync()

​ 发送前需要调用 SocketAsyncEventArgsSetBuffer() 方法设置发送数组。

image-20250507105917639
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        ...
        
        var e3     = new SocketAsyncEventArgs();
        var bytes2 = Encoding.UTF8.GetBytes("123123的就是拉法基萨克两地分居");
        e3.SetBuffer(bytes2, 0, bytes2.Length);
        e3.Completed += (socket, args) =>
        {
            if (args.SocketError == SocketError.Success)
            {
                print("发送成功");
            }
            else
            { }
        };
        socketTcp.SendAsync(e3);
    }
}

ReceiveAsync()

​ 接收也需要调用 SocketAsyncEventArgsSetBuffer() 方法设置接收到哪个数组中。

image-20250507110326391
public class Lesson12 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        ...
        
        var e4 = new SocketAsyncEventArgs();

        // 设置接受数据的容器,偏移位置,容量
        e4.SetBuffer(new byte[1024 * 1024], 0, 1024 * 1024);
        e4.Completed += (socket, args) =>
        {
            if (args.SocketError == SocketError.Success)
            {
                // 收取存储在容器当中的字节
                // Buffer 是容器
                // BytesTransferred 是收取了多少个字节
                Encoding.UTF8.GetString(args.Buffer, 0, args.BytesTransferred);

                // 接收完消息 再接收下一条
                args.SetBuffer(0, args.Buffer.Length);
                (socket as Socket).ReceiveAsync(args);
            }
            else
            { }
        };
        socketTcp.ReceiveAsync(e4);
    }
}

3 实战

3.1 服务端配置

​ 依次创建如下 3 个脚本:

  • ClientSocket.cs
  • ServerSocket.cs
  • Program.cs

3.1.1 ClientSocket.cs

  1. 异步连接管理:自动为每个客户端连接分配唯一 ID,并立即开始异步接收消息。
  2. 异步消息发送:提供 Send() 方法异步发送字符串消息到服务器。
  3. 异步消息接收:通过回调机制持续接收服务器发送的消息。
  4. 错误处理:捕获并处理套接字操作中的异常。

1)成员变量

private static int _BeginId = 1;  // 静态ID计数器
public Socket Client { get; private set; }  // 底层Socket对象
public int Id { get; set; }  // 客户端唯一标识
private byte[] _buffer = new byte[1024];  // 接收缓冲区
private int _bufferOffset = 0;  // 缓冲区偏移量

2)构造函数

​ 构造函数接收一个已连接的 Socket 对象,为其分配 ID,并立即开始异步接收数据。

public ClientSocket(Socket client)
{
    Client = client;
    Id = _BeginId++;  // 分配唯一ID
    
    // 立即开始异步接收消息
    Client.BeginReceive(_buffer, _bufferOffset, _buffer.Length - _bufferOffset, 
                       SocketFlags.None, ReceiveCallback, Client);
}

3)消息发送

Send()方法将字符串编码为 UTF-8 字节数组,然后异步发送。

public void Send(string message)
{
    try
    {
        var bytes = Encoding.UTF8.GetBytes(message);
        Client.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, Client);
    }
    catch (SocketException e)
    {
        Console.WriteLine($"客户端 Id 发送消息时发生错误:e.Message");
    }
}

4)发送回调

​ 发送完成后调用此回调,处理发送结果。

private void SendCallback(IAsyncResult ar)
{
    try
    {
        var client = (Socket) ar.AsyncState!;
        client.EndSend(ar);  // 完成异步发送
    }
    catch (SocketException e)
    {
        Console.WriteLine($"客户端 Id 发送消息时发生错误:e.Message");
    }
}

5)接收回调

​ 这是核心的接收逻辑,处理接收到的数据并保持持续接收状态。

private void ReceiveCallback(IAsyncResult ar)
{
    try
    {
        var client = (Socket) ar.AsyncState!;
        _bufferOffset = client.EndReceive(ar);  // 完成接收
        
        // 处理消息
        var message = Encoding.UTF8.GetString(_buffer, 0, _bufferOffset);
        Console.WriteLine($"收到客户端 {Id} 的消息:{message}");
        
        _bufferOffset = 0;  // 重置缓冲区
        
        if (client.Connected)
        {
            // 继续接收下一条消息
            client.BeginReceive(_buffer, _bufferOffset, _buffer.Length - _bufferOffset, 
                              SocketFlags.None, ReceiveCallback, client);
        }
        else
        {
            Console.WriteLine($"客户端 {Id} 断开连接!");
        }
    }
    catch (SocketException e)
    {
        Console.WriteLine($"客户端 {Id} 接收消息时发生错误:{e.Message}");
    }
}

3.1.2 ServerSocket.cs

  1. 服务器管理:创建并管理 TCP 服务器套接字,监听指定 IP 和端口
  2. 客户端连接处理:异步接受客户端连接,为每个连接创建ClientSocket实例
  3. 消息广播:向所有已连接客户端发送消息
  4. 客户端管理:使用字典存储所有连接的客户端套接字

1)成员变量

public Socket Server { get; private set; }  // 服务器Socket对象
private Dictionary<int, ClientSocket> _clientSockets = new();  // 存储所有客户端连接

2)启动服务器

Start()方法初始化服务器 Socket,绑定到指定 IP 和端口,并开始监听连接请求。

public void Start(string ip, int port, int num)
{
    Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    var ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
    
    Server.Bind(ipPoint);
    Server.Listen(num);  // 设置最大挂起连接数
    
    Server.BeginAccept(AcceptCallback, Server);  // 开始异步接受连接
}

3)接受客户端连接

​ 这是核心的异步连接处理逻辑,使用回调机制处理新连接。

private void AcceptCallback(IAsyncResult ar)
{
    var server = (Socket) ar.AsyncState!;
    var client = server.EndAccept(ar);  // 完成异步接受
    
    var clientSocket = new ClientSocket(client);  // 创建客户端Socket包装
    _clientSockets.Add(clientSocket.Id, clientSocket);  // 存储客户端
    
    Console.WriteLine($"客户端 {clientSocket.Id} 连接成功!");
    
    server.BeginAccept(AcceptCallback, server);  // 继续接受新连接
}

4)消息广播

​ 该方法遍历所有已连接客户端,发送相同消息。

public void BroadCast(string msg)
{
    foreach (var clientSocket in _clientSockets.Values)
    {
        clientSocket.Send(msg);  // 向每个客户端发送消息
    }
}

3.1.3 Program.cs

  1. 服务器启动:初始化并启动 TCP 服务器。
  2. 交互式控制:通过控制台输入管理服务器。
  3. 消息广播:向所有连接客户端发送消息。
  4. 退出机制:提供优雅关闭服务器的选项。

1)服务器初始化

  • 创建ServerSocket实例。
  • 启动服务器监听本地回环地址(127.0.0.1)的 8080 端口。
  • 设置最大挂起连接数为 10。
  • 输出启动信息。
var serverSocket = new ServerSocket();
serverSocket.Start("127.0.0.1", 8080, 10);
Console.WriteLine("服务器已启动,等待客户端连接...");

2)主控制循环

​ 这是一个无限循环,等待控制台输入并处理命令:

  1. exit 命令:退出循环,结束程序。
  2. B:前缀消息:广播消息给所有客户端(去除"B:"前缀)。
  3. 其他输入:当前版本忽略。
while (true)
{
    var input = Console.ReadLine();
    if (input == "exit")
    {
        break;
    }
    else if (input?[..2] == "B:")
    {
        serverSocket.BroadCast(input[2..]);
    }
}

3)使用说明

  1. 启动服务器:运行程序即启动服务器。
  2. 广播消息:输入"B:消息内容"广播给所有客户端。
  3. 退出服务器:输入"exit"关闭服务器。

3.2 客户端配置

​ 依次创建如下 3 个脚本:

  • NetAsyncMgr.cs
  • MainAsync.cs
  • Lesson13.cs

3.2.1 NetAsyncMgr.cs

​ 实现 Unity 异步 Socket 网络管理器,用于处理与服务器的 TCP 连接、消息发送和接收。

  1. 单例模式:通过Instance属性确保全局唯一访问点。
  2. 异步连接:使用ConnectAsync实现非阻塞连接。
  3. 消息收发:支持异步发送和接收 UTF-8 编码的字符串消息。
  4. 连接管理:提供连接状态检查和关闭方法。

1)初始化与连接

​ 使用SocketAsyncEventArgs实现异步连接,连接完成后触发ConnectCallback回调。

public void Connect(string ip, int port)
{
    var ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    
    var args = new SocketAsyncEventArgs();
    args.RemoteEndPoint = ipPoint;
    args.Completed += ConnectCallback;
    _socket.ConnectAsync(args);
}

2)消息发送

​ 将字符串编码为 UTF-8 字节数组后异步发送,发送完成后触发SendCallback

public void Send(string msg)
{
    var bytes = Encoding.UTF8.GetBytes(msg);
    var args = new SocketAsyncEventArgs();
    args.SetBuffer(bytes, 0, bytes.Length);
    args.Completed += SendCallback;
    _socket.SendAsync(args);
}

3)消息接收

​ 使用循环接收模式,每次接收完成后立即开始下一次接收。

private void ReceiveCallback(object sender, SocketAsyncEventArgs e)
{
    print(Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred));
    socket.ReceiveAsync(e); // 继续接收下一条消息
}

3.2.2 MainAsync.cs

​ MainAsync.cs 用于初始化并连接网络管理器 NetAsyncMgr。

  1. 网络管理器初始化:确保NetAsyncMgr单例实例存在。
  2. 自动连接服务器:启动时自动连接本地 8080 端口。

1)网络管理器初始化

​ 这段代码实现了"懒加载"模式:

  • 检查NetAsyncMgr单例是否存在。
  • 如不存在则创建新的 GameObject 并附加NetAsyncMgr组件。
if (NetAsyncMgr.Instance == null)
{
    var go = new GameObject("NetAsyncMgr");
    go.AddComponent<NetAsyncMgr>();
}

2)服务器连接

​ 使用单例实例连接本地服务器 (127.0.0.1) 的 8080 端口。

NetAsyncMgr.Instance.Connect("127.0.0.1", 8080);

3.2.3 Lesson13.cs

  1. UI 组件绑定
    • BtnSend:发送按钮。
    • TMP_InputField:文本输入框(使用 TextMeshPro 实现)。
  2. 消息发送逻辑
    • Start()中注册按钮点击事件。
    • 点击按钮时调用NetAsyncMgr.Instance.Send()发送输入框内容。

3.3 代码

服务端

// ------------------------------------------------------------
// @file       ClientSocket.cs
// ------------------------------------------------------------

namespace NetLearningTcpServerAsync;

using System.Net.Sockets;
using System.Text;

public class ClientSocket
{
    private static int _BeginId = 1;

    public Socket Client { get; private set; }

    public int Id { get; set; }

    private byte[] _buffer       = new byte[1024];
    private int    _bufferOffset = 0;

    public ClientSocket(Socket client)
    {
        Client = client;
        Id     = _BeginId++;

        // 开始收消息
        Client.BeginReceive(_buffer, _bufferOffset, _buffer.Length - _bufferOffset, SocketFlags.None, ReceiveCallback, Client);
    }

    public void Send(string message)
    {
        try
        {
            var bytes = Encoding.UTF8.GetBytes(message);
            Client.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, Client);
        }
        catch (SocketException e)
        {
            Console.WriteLine($"客户端 Id 发送消息时发生错误:e.Message");
        }
    }

    private void SendCallback(IAsyncResult ar)
    {
        try
        {
            var client = (Socket) ar.AsyncState!;
            client.EndSend(ar);
        }
        catch (SocketException e)
        {
            Console.WriteLine($"客户端 Id 发送消息时发生错误:e.Message");
        }
    }

    private void ReceiveCallback(IAsyncResult ar)
    {
        try
        {
            var client = (Socket) ar.AsyncState!;
            _bufferOffset = client.EndReceive(ar);

            // 处理收到的消息
            var message = Encoding.UTF8.GetString(_buffer, 0, _bufferOffset);
            Console.WriteLine($"收到客户端 {Id} 的消息:{message}");

            // 清空缓冲区
            _bufferOffset = 0;

            if (client.Connected)
            {
                // 继续接收消息
                client.BeginReceive(_buffer, _bufferOffset, _buffer.Length - _bufferOffset, SocketFlags.None, ReceiveCallback, client);
            }
            else
            {
                Console.WriteLine($"客户端 {Id} 断开连接!");
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine($"客户端 {Id} 接收消息时发生错误:{e.Message}");
        }
    }
}
// ------------------------------------------------------------
// @file       ServerSocket.cs
// ------------------------------------------------------------

namespace NetLearningTcpServerAsync;

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

public class ServerSocket
{
    public Socket Server { get; private set; }

    private Dictionary<int, ClientSocket> _clientSockets = new();

    public void Start(string ip, int port, int num)
    {
        Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        var ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
        try
        {
            Server.Bind(ipPoint);
            Server.Listen(num);

            Server.BeginAccept(AcceptCallback, Server);
        }
        catch (SocketException e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    public void BroadCast(string msg)
    {
        foreach (var clientSocket in _clientSockets.Values)
        {
            clientSocket.Send(msg);
        }
    }
    
    private void AcceptCallback(IAsyncResult ar)
    {
        try
        {
            var server = (Socket) ar.AsyncState!;
            var client = server.EndAccept(ar);

            var clientSocket = new ClientSocket(client);
            _clientSockets.Add(clientSocket.Id, clientSocket);
            
            Console.WriteLine($"客户端 {clientSocket.Id} 连接成功!");

            // 继续让别的客户端连入
            server.BeginAccept(AcceptCallback, server);
        }
        catch (SocketException e)
        {
            Console.WriteLine(e);
            throw;
        }
    }
}
// ------------------------------------------------------------
// @file       Program.cs
// ------------------------------------------------------------

using NetLearningTcpServerAsync;

var serverSocket = new ServerSocket();
serverSocket.Start("127.0.0.1", 8080, 10);
Console.WriteLine("服务器已启动,等待客户端连接...");

while (true)
{
    var input = Console.ReadLine();
    if (input == "exit")
    {
        break;
    }
    else if (input?[..2] == "B:")
    {
        serverSocket.BroadCast(input[2..]);
    }
}

客户端

// ------------------------------------------------------------
// @file       NetAsyncMgr.cs
// ------------------------------------------------------------

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class NetAsyncMgr : MonoBehaviour
{
    public static NetAsyncMgr Instance { get; private set; }

    private Socket _socket; // 连接服务器的 Socket

    private byte[] _buffer       = new byte[1024 * 1024];
    private int    _bufferOffset = 0;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
    }

    public void Connect(string ip, int port)
    {
        if (_socket != null && _socket.Connected)
        {
            return;
        }

        var ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        var args = new SocketAsyncEventArgs();
        args.RemoteEndPoint =  ipPoint;
        args.Completed      += ConnectCallback;
        _socket.ConnectAsync(args);
    }

    public void Send(string msg)
    {
        if (_socket == null || !_socket.Connected)
        {
            return;
        }

        var bytes = Encoding.UTF8.GetBytes(msg);

        var args = new SocketAsyncEventArgs();
        args.SetBuffer(bytes, 0, bytes.Length);
        args.Completed += SendCallback;
        _socket.SendAsync(args);
    }

    public void Close()
    {
        if (_socket != null)
        {
            _socket.Shutdown(SocketShutdown.Both);
            _socket.Disconnect(false);
            _socket.Close();
            _socket = null;
        }
    }

    private void SendCallback(object sender, SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            Debug.Log("Send Success");
        }
        else
        {
            Debug.Log("Send Failed: " + e.SocketError);
            Close();
        }
    }

    private void ConnectCallback(object sender, SocketAsyncEventArgs eventArgs)
    {
        if (eventArgs.SocketError == SocketError.Success)
        {
            Debug.Log("Connect Success");

            var socket = sender as Socket;

            if (socket == null || !socket.Connected)
            {
                return;
            }

            // TODO: 连接成功后,开始接收数据
            var args2 = new SocketAsyncEventArgs();
            args2.SetBuffer(_buffer, 0, _buffer.Length);
            args2.Completed += ReceiveCallback;
            socket.ReceiveAsync(args2);
        }
        else
        {
            Debug.Log("Connect Failed: " + eventArgs.SocketError);
        }
    }

    private void ReceiveCallback(object sender, SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            print(Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred));

            // TODO: 处理接收到的数据

            var socket = sender as Socket;

            if (socket == null || !socket.Connected)
            {
                return;
            }

            // 继续接收数据
            socket.ReceiveAsync(e);
        }
        else
        {
            print("接受消息出错:" + e.SocketError);
            Close();
        }
    }
}
// ------------------------------------------------------------
// @file       MainAsync.cs
// ------------------------------------------------------------

using UnityEngine;

public class MainAsync : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        if (NetAsyncMgr.Instance == null)
        {
            var go = new GameObject("NetAsyncMgr");
            go.AddComponent<NetAsyncMgr>();
        }
        
        NetAsyncMgr.Instance.Connect("127.0.0.1", 8080);
    }
}

// ------------------------------------------------------------
// @file       Lesson13.cs
// ------------------------------------------------------------

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class Lesson13 : MonoBehaviour
{
    public Button         BtnSend;
    public TMP_InputField TxtMessage;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        BtnSend.onClick.AddListener(() =>
        {
            NetAsyncMgr.Instance.Send(TxtMessage.text);
        });
    }
}

4 测试

  1. Unity 中创建新场景,新建空物体 Main,将 MainAsync.cs 脚本挂载上去。
image-20250507194100660
  1. 创建 Canvas 画布,添加 InputField 和 Button 并关联至 Lesson13.cs。
image-20250507194236535
  1. 首先运行服务器。
image-20250507194349409
  1. 服务器启动后,运行 Unity。服务器显示连接成功。

    输入命令“B:Hello!”,回车。

image-20250507194613365
  1. Unity 中接收到消息并输出。
image-20250507194801832
  1. Unity 中在 InputField 中输入文字,点击发送。
image-20250507194851682
  1. 服务器中接收到了消息并打印输出。
image-20250507194918709

​ 到此,完成了服务器和客户端的双向通信。


网站公告

今日签到

点亮在社区的每一天
去签到