zoukankan      html  css  js  c++  java
  • Unity3d C# Socket异步发送与接收数据

    在网络游戏开发中,一些游戏需要使用长连接的方式进行网络通信,即使用Socket建立长连接。那么在Unity3d中,如何使用C#与服务端建立长连接呢?为什么 要说使用异步呢?我们知道,在Unity3d中,每个游戏画面的播放都是以帖的概念循环播放的。而且只能在UI线程中播放,在其它线程不可以操作UI有关的东西,这都是网络通信需要解决的问题。

    使用Socket创建连接

    众所周知,在游戏客户端启动之后,一定有一个时机是创建网络连接的,比如一般是选游戏大区这后,或用户点击进入游戏时,这都是由UI层触发点击和创建网络连接的。但是网络连接是一个IO阻塞操作,如果直接使用Socket的同步方法,会卡住当前UI线程,导致游戏画面出现卡顿现象。所以要么使用协程,要么便需要使用异步创建连接。为了方便创建socket连接,这里使用异步连接方式。如下面代码所示:

        public void ConnectServer()
        {
            this.Close();
            try
            {
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();//创建连接参数对象
                this.endPoint = new IPEndPoint(IPAddress.Parse(host), port);
                args.RemoteEndPoint = this.endPoint;
                args.Completed += OnConnectedCompleted;//添加连接创建成功监听
                _socket.ConnectAsync(args); //异步创建连接
            }
            catch(Exception e)
            {
                Debug.Log("服务器连接异常:" + e);
            }
        }
    
        private void OnConnectedCompleted(object sender,SocketAsyncEventArgs args)
        {
            try
            {   ///连接创建成功监听处理
                if (args.SocketError != SocketError.Success)
                {
                    //通知上层连接失败
                    CompleteConnectServerHandler?.Invoke(ConnectedStatus.ConnectFailed);
                }
                else
                {
                    Debug.Log("网络连接成功线程:" + Thread.CurrentThread.ManagedThreadId.ToString());
                    //通知上层连接创建成功
                    CompleteConnectServerHandler?.Invoke(ConnectedStatus.ConnectedSucess);
                    StartReceiveMessage(); //启动接收消息
    
                }
            }
            catch(Exception e)
            {
                Debug.Log("开启接收数据异常" + e);
            }
            
        }
    
    

    发送消息

    一般来说,消息的发送都是UI层触发的,比如点击某个按钮,执行了某个操作,就会向服务器获取或保存数据。所以也需要使用协程或异步的方式发送消息,这里使用异步的方式。如下面代码所示:

        public void SendToServer(byte[] data)
        {
            try
            {
                if (_socket == null || !_socket.Connected)
                {
                    Debug.Log("socket未连接,发送消息失败");
                    return;
                }
                //创建发送参数
                SocketAsyncEventArgs sendEventArgs = new SocketAsyncEventArgs();
                sendEventArgs.RemoteEndPoint = endPoint;
                //设置要发送的数据
                sendEventArgs.SetBuffer(data, 0, data.Length);
                //异步发送数据
                _socket.SendAsync(sendEventArgs);
            }
            catch(Exception e)
            {
                Debug.Log("发送数据异常:" + e);
            } 
        }
    
    

    由于Socket制作是一种非常底层的操作,所以这里直接发送byte[],具体发送什么样的数据,由应用层决定。

    接收数据

    由于我们使用的是Socket自带的异步创建连接,发送数据,所以接收数据也是在socket的异步线程中。如下面代码所示:

        private void StartReceiveMessage()
        {
    
            //启动接收消息
            SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
            //设置接收消息的缓存大小,正式项目中可以放在配置 文件中
            byte[] buffer = new byte[1024 * 512];
            //设置接收缓存
            receiveArgs.SetBuffer(buffer, 0, buffer.Length);
            receiveArgs.RemoteEndPoint = this.endPoint;
            receiveArgs.Completed += OnReceiveCompleted; //接收成功
            _socket.ReceiveAsync(receiveArgs);//开始异步接收监听
        }
    
    
        public void OnReceiveCompleted(object sender,SocketAsyncEventArgs args)
        {
            try
            {
                Debug.Log("网络接收成功线程:" + Thread.CurrentThread.ManagedThreadId.ToString());
    
                if (args.SocketError == SocketError.Success && args.BytesTransferred > 0)
                {
                    //创建读取数据的缓存
                    byte[] bytes = new byte[args.BytesTransferred];
                    //将数据复制到缓存中
                    Buffer.BlockCopy(args.Buffer, 0, bytes, 0, bytes.Length);
                    if(ReceiveSocketDataHandler == null)
                    {
                        Debug.Log("没有处理接收消息的事件 ");
                    }
                    //接收数据成功,调上层处理接收数据的事件
                    ReceiveSocketDataHandler?.Invoke(bytes);
                    //再次启动接收数据监听,接收下次的数据。
                    StartReceiveMessage();
                }
                else
                {
                    CompleteConnectServerHandler(ConnectedStatus.Disconnect);
                }
            }
            catch(Exception e)
            {
                Debug.Log("接收数据异常:" + e);
            }
        }
    
    

    接收数据的方法会在连接创建成功时调一次,它一直监听服务器的数据到来。当接收到服务器的数据时,我们会调用上层处理数据的事件 ,即ReceiveSocketDataHandler?.Invoke(bytes); ,首先要做的就是对接收的数据进行断包和粘包处理。这个在下篇文章中再说。

    详细的代码,可以参考项目源码:http://www.xinyues.com/h-nd-195.html#_np=2_627
    可以关注微信公众号,及时接收文章推送
    欢迎关注微信公号

  • 相关阅读:
    Linux使用定时器timerfd 和 eventfd接口实现进程线程通信
    C++面向对象实现封装线程池
    生产者与消费者问题,C++利用bind基于对象实现与面向对象实现
    C++11新特性,bind,基于对象
    [HDU
    [HDU
    [HDU
    【BZOJ3707】圈地 (几何,旋转坐标系)
    [HDU
    [Codeforces Round #595 (Div. 3)] F. Maximum Weight Subset (树形DP)
  • 原文地址:https://www.cnblogs.com/wgslucky/p/12008433.html
Copyright © 2011-2022 走看看