精华内容
下载资源
问答
  • 该文章将简述我的一个基于Unity开发的ANDROID网游模板的大致开发路程和开发时遇到的关键节点。可能很少涉及具体代码,后续将可通过github获取项目资源。 目录 开发节点简述 核心一:网络通讯实现 核心二:通讯字符...

    该文章将简述我的一个基于Unity开发的ANDROID网游模板的大致开发路程和开发时遇到的关键节点。可能很少涉及具体代码,后续将可通过github获取项目资源。

    目录

    • 开发节点简述
    • 核心一:网络通讯实现
    • 核心二:通讯字符串的处理
    • 核心三:Unity上的开发关键点
    • 效果展示
    • github

    开发节点简述

    本次开发耗时四天,开发的过程中遇到的问题主要是网络通讯,字符串处理和客户端内的线程状态与服务端线程状态的协调。

    服务端:

    通过
    断开
    错误
    开始
    监听端口8080
    验证
    连接
    是否在table中
    打印正常信息
    是否处于进入table的初始状态
    打印初始信息,保存返回的颜色信息及分发table change事件
    同步位置信息
    是否退出table
    打印退出table信息,分发table change事件
    结束线程

    综上为服务端程序的基本流程。

    • 验证阶段:此处所用验证为我写的一个时间数验证,该验证能保证在2秒内延迟的网络都能正常接入服务端。此处可替换成数据库的用户名密码等验证。
    • 玩家所处状态验证:此处所涉及内容为对返回字符串进行判断,并综合当前服务端保存的客户信息得出当前客户端所处状态。此处所使用传递信息的字符串尚未经过加密处理,可通过加密算法进行加密处理以防止数据篡改和人为脚本操纵进程。
    • table change事件:该事件为同一table下所有玩家互通数据的关键。简单表述为,当玩家创建table时,会在服务器保留一个tableservice对象,该对象会保留所有玩家的id。同时玩家会有一个team数组,该数组对应当前队友的个人缓存。当有人退出会进入table,则table change被触发,会核对一遍所有玩家的team数组是否与tableservice保存的内容一致。不一致时,将会触发传参操作,提示客户端,删除或加入新的游戏角色。

    客户端

    创建桌面
    加入桌面
    退出
    加载TABLE完成,跳转
    OUT
    加载NEWBEGIN完成,跳转
    开始
    开始关卡
    TCP连接
    是否连接
    退出
    选择功能
    WAIT关卡
    TABLE关卡
    玩家操纵
    OTHERS同步
    NEWBEGIN关卡

    综上为客户端的基本流程

    • TCP连接:使用的是SOCKET通讯,该连接被挂载在一个gameobject中,该gameobject不会被删除,不会被重复创建。当TCP连接5秒未响应时,客户端会中断连接。当TCP未被连接时,客户端无法运行。
    • 玩家操纵:使用了比较流行的触摸盘来实现,通过对触摸点位的判断和Unity UI的位置控制来实现这一功能,除此之外还有跳跃功能。该游戏内的玩家只是一个正方体,可替换成动画资源,走路跑步等动画功能等可用位移速度来进行控制。
    • OTHERS同步:此处为TCP获取信息的拆封。具体逻辑为,每个玩家都会通过TCP将自己的位置信息,旋转信息打包成字符串发送给服务端。服务端会将除玩家外其他所有同个TABLE内的玩家信息打包发送给客户端。客户端可通过拆分这些位置信息分发到各个OTHERS游戏体,实现玩家的信息同步。
    • NEWBEGIN关卡的说明:如图所示,TCP,GAMEMANAGER等游戏关卡内只需被创建一次的物体都被挂载在BEGIN关卡中。因此如果BEGIN关卡被重复创建,这些物体也将被重复创建。所以采取了这样的处理方式。若有大神能给出更加完美的处理方式,请留言。感激不尽。

    核心一:网络通讯实现

    网络通讯实现的方式,其实我也只是在半个月前才刚刚接触到。本项目所使用的通讯方式为SOCKET。SOCKET是TCP协议的基础。关于通过SOCKET实现的服务端内容,其实就是在获得客户端连接需求时,创建一个线程,并通过该线程响应客户端的一些连接需求。
    此处为我个人使用java编写的服务端,挂载在腾讯云的linux系统上。因为并不清楚具体Android网络游戏开发可能会使用到的规范化的服务端框架,因此使用了自己编写这样的方式。
    同时,Unity也使用SOCKET进行通讯。
    该通讯过程可表现如下

    SERVER线程
    GET
    DOINGSOMETHING
    SET
    UNITY线程
    CHECK
    GET
    DOINGSOMETHING
    SET

    Unity的check过程将会完成第一次的线程交互,后续的交互过程。将是:

    UNITY SET->SERVER GET

    SERVER SET->UNITY GET

    这样一个交错的流程。
    而具体如何通过这样的交互实现网游的效果,则需要通过字符串的处理。

    核心二:通讯字符串的处理

    通讯字符串的处理,主要包括了对字符串内容的判断,切割,提取和舍弃。

    • 判断
      例:客户端需要创建一个table
      客户端发送一个"create table"的字符串给服务端
      服务端接到字符串,进行equals判断字符串的内容。得到需要创建table的结果后,进行tableservice的创建。

    • 切割
      例:客户端创建了一个table,服务端向玩家发送table内所有玩家的位置信息与旋转信息
      假设服务端发送的信息为
      “id|x|y|z|u|v|w|z”
      使用split(’|’)便可将字符串分割,再将字符串内容进行转换和组合,便可得到:
      id
      position:(x,y,z)
      rotation:(u,v,w,z)

    • 提取
      如上切割过程,其实既包括了切割,也包括了提取。此过程重要的是了解字符串的组成结构和方式。知道字符串内的那一部分对应所需信息的哪一块。

    • 舍弃
      该过程主要为了防止客户端出错。在服务端与客户端信息交互的过程中,会出现很多情况。
      例如客户端本身存在的问题,会造成错误字符串的传递,而这个字符串一旦未被舍弃而进入信息判断,可能会出现多种问题。
      再例如,服务端所受到数据,非客户端传递,而是人为脚本传递,则可能会将服务器连接下其他的客户端的正常连接破坏。

    核心三:Unity上的开发关键点

    玩家角色与OTHERS角色的不同

    在开发过程中所需注意的是,OTHERS并非玩家,OTHERS只是类似玩家的游戏体。玩家可通过自己直接操作,OTHERS并不可以。而且,考虑到网络延迟和设备差异的因素,OTHERS有时候并不会遵循玩家角色所遵循的游戏规则。
    个人经历过的坑点:

    • 1.charactercontroller类可用于玩家,尽量别用于OTHERS。
      原因是,该组件会限制游戏体的行动。在完全理想的情况下,OTHERS会实时获得其他玩家共享的所有移动数据。但在网络延迟和设备差异的情况下,该条件不可实现。
    • 2.分支线程不可用于删除角色
      当一个其他玩家退出当前table,那么你的客户端中理所当然的应该将他所对应的OTHERS对象删除。但是,判断玩家退出的线程的TCP线程。该线程为TCP游戏体下单独创建的分支线程。也因此,该线程不可使用删除游戏体的方法。只能给主线程设置一个信号,让接到信号的主线程去完成这个删除OTHERS的操作。同样的坑点还在于,分支线程不可用于跳转关卡,不可动态获取GAMEOBJECT的TRANSFORM

    为客户端设置最大帧率

    在单机游戏中,运行过程理所当然的需求尽可能高的帧数。帧数越高越能体现游戏的真实感和提高游戏的体验。
    但在网络游戏中可能并非如此,对帧数高于120的玩家来说,帧数低于60的玩家无异于处于劣势状态。所释放技能在数据判定上会出现参差不齐的差异。因此,将客户端的帧数限制在一定的范围之内,能有效的减少数据判定上的差异。一定程度上保证多个客户端数据统一。

    效果展示

    下图为自己跳跃的效果

    ijump

    下图为others加入

    youcome

    下图为others跳跃

    youjump

    github

    Unity客户端
    JAVA-SERVER
    Unity客户端对应的APK在server的github中~

    展开全文
  • 基于unity的hololens应用开发记录socket通信项目要求python实现socket通信python与C#实现socket通信 socket通信 项目要求 在unity中搭建环境作为客户端,并通过python程序(作为服务端)控制unity环境。在控制过程中...

    问题一:socket通信

    项目要求

    在unity中搭建环境作为客户端,并通过python程序(作为服务端)控制unity环境。在控制过程中,需要使用socket通信将python发出的控制命令发送到unity环境中,然后unity返回控制结果。
    首先使用了python搭建了服务端和客户端,进行通信试验。由于unity脚本控制使用C#语言,所以将客户端改为C#代码,进行数据收发试验。由于unity和python程序都运行在同一台电脑上,我们使用IP地址为127.0.0.1。
    整个通信过程是,建立连接后,服务器端向客户端发送字符串,客户端接收到字符串后,将该字符串发送到服务器端。

    python实现socket通信

    服务器端:

    import socket
    #创建服务端的socket对象socketserver
    socketserver = socket.socket(
        socket.AF_INET,             # AF_INET 表示服务器到服务器通信
        socket.SOCK_STREAM          # SOCK_STREAM 表示 socket 连接
    )
    host = '127.0.0.1'
    port = 8888
    #绑定地址(包括ip地址会端口号)
    socketserver.bind((host, port))
    #设置监听
    socketserver.listen(5)
    print("Waiting for connection...")
    #等待客户端的连接
    #注意:accept()函数会返回一个元组
    #元素1为客户端的socket对象,元素2为客户端的地址(ip地址,端口号)
    clientsocket,addr = socketserver.accept()
    
    #while循环是为了能让对话一直进行,直到客户端输入q
    while 1:
        msg = "NEXTPIC"
        #对要发送的数据进行编码
        clientsocket.send(msg.encode("utf-8"))
    
        # #接收客户端的请求
        recvmsg = clientsocket.recv(1024)
        # #把接收到的数据进行解码
        strData = recvmsg.decode("utf-8")
        # #判断客户端是否发送q,是就退出此次对话
        # if strData=='q':
        #     break
        print("收到:"+strData)
    socketserver.close()
    

    多线程服务器端:

    import socket
    import threading
    #创建服务端的socket对象socketserver
    socketserver = socket.socket(
        socket.AF_INET,             # AF_INET 表示服务器到服务器通信
        socket.SOCK_STREAM          # SOCK_STREAM 表示 socket类型为TCP连接
    )
    host = '127.0.0.1'
    port = 8888
    #绑定地址(包括ip地址会端口号)
    socketserver.bind((host, port))
    #设置监听
    socketserver.listen(5)
    print("waiting for connection")
    
    def handle_sock(sock, addr):
        while 1:
            msg="pic"+str(tiaojian)
            # 对要发送的数据进行编码
            clientsocket.send(msg.encode("utf-8"))
            # 接收客户端的请求
            recvmsg = clientsocket.recv(1024)
            # 把接收到的数据进行解码
            strData = recvmsg.decode("utf-8")
            print(strData)
    
    tiaojian=1
    #while循环是为了能让对话一直进行,直到客户端输入q
    while 1:
        tiaojian+=1
        # 等待客户端的连接
        # 注意:accept()函数会返回一个元组
        # 元素1为客户端的socket对象,元素2为客户端的地址(ip地址,端口号)
        clientsocket, addr = socketserver.accept()
        print('connected from %s'%str(addr))
        client_thread = threading.Thread(target=handle_sock, args=(clientsocket, addr))
        client_thread.start()
    
    socketserver.close()
    

    客户端:

    import socket
    #创建一个客户端的socket对象
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #设置服务端的ip地址
    host = "127.0.0.1"
    #设置端口
    port = 8888
    # 连接服务端
    client.connect((host, port))
    #while循环是为了保证能持续进行对话
    # while 1:
    msg = client.recv(1024)
    #接收服务端返回的数据,需要解码
    print(msg.decode("utf-8"))
        # #输入发送的消息
        # sendmsg = input("请输入:")
        # #如果客户端输入的是q,则停止对话并且退出程序
        # if sendmsg=='q':
        #     break
    sendmsg = 'set to nextpic'
    #发送数据,以二进制的形式发送数据,所以需要进行编码
    client.send(sendmsg.encode("utf-8"))
    #关闭客户端
    client.close()
    

    python与C#实现socket通信

    多线程服务器端:

    import socket
    import threading
    import time
    
    #创建服务端的socket对象socketserver
    socketserver = socket.socket(
        socket.AF_INET,             # AF_INET 表示服务器到服务器通信
        socket.SOCK_STREAM          # SOCK_STREAM 表示 socket类型为TCP连接
    )
    host = '127.0.0.1'
    port = 8888
    #绑定地址(包括ip地址会端口号)
    socketserver.bind((host, port))
    #设置监听
    socketserver.listen(5)
    print("waiting for connection")
    
    def handle_sock(sock, addr):
        # while 1:    #连接后一直对话
        for i in range(1,1000):
            # time.sleep(2)
            if i%3==0:
                msg="1"
            else:
                msg="0"             
            # 对要发送的数据进行编码
            clientsocket.send(msg.encode("utf-8"))
            # 接收客户端的请求
            recvmsg = clientsocket.recv(1024)
            # 把接收到的数据进行解码
            strData = recvmsg.decode("utf-8")
            print(strData)
            
    #while循环是为了能让对话一直进行,直到客户端输入q
    while 1:     #使客户端可以一直断开重连
        # 等待客户端的连接
        # 注意:accept()函数会返回一个元组
        # 元素1为客户端的socket对象,元素2为客户端的地址(ip地址,端口号)
        clientsocket, addr = socketserver.accept()
        print('connected from %s'%str(addr))
        client_thread = threading.Thread(target=handle_sock, args=(clientsocket, addr))
        client_thread.start()
    
    socketserver.close()
    

    客户端:
    新建c# script
    在这里插入图片描述
    在需要通信的场景中选择对象添加script.
    在这里插入图片描述

    using UnityEngine;
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using UnityEngine.SceneManagement;
    
    public class N0_speech : MonoBehaviour
    {
        private Socket socketClient;
        private IPEndPoint endPoint;
        //public string port = "8888";
        // _IpForConnect 是服务端ip地址
        //private string _IpForConnect = "127.0.0.1";
        public string IpForConnect { get => IP_globel._IpForConnect; set => IP_globel._IpForConnect = value; }
        private bool _ShallConnet;
        public string msg;
        public bool ShallConnet { get => _ShallConnet; set => _ShallConnet = value; }
        // Start is called before the first frame update
        void Start()
        {
            connect();
        }
    
        // Update is called once per frame
        void Update()
        {
            acceptmsg();
        }
    
        private void connect()
        {
    
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            // 用指定的ip和端口号初始化IPEndPoint实例
            //endPoint = new IPEndPoint(IPAddress.Parse(IP_globel._IpForConnect), 8888);
            endPoint = new IPEndPoint(IPAddress.Parse(IP_globel._IpForConnect), IP_globel.port);
            //与远程主机建立连接
            socketClient.Connect(endPoint);
        }
    
        private void acceptmsg()
        {
            Byte[] receive = new byte[1024];
            int length = socketClient.Receive(receive);  // length 接收字节数组长度  string.Equals(msg,"1")
            msg = Encoding.Unicode.GetString(receive);
    
            //if (string.Equals(msg,"1")  这两种都不行
            //if (msg == "1")
            if (String.Compare(msg, "1") == 0)
            {
                //Thread.Sleep(2000);
                //接收到msg为1,则切换界面
                SceneManager.LoadScene("SampleScene");
                sendmsgsp();
            }
            else
            {
                sendmsg();
            }
        }
    
        private void sendmsg()
        {
            Byte[] TimeStamp = Encoding.Default.GetBytes(msg);
            int sendBytes = socketClient.SendTo(TimeStamp, endPoint);
    
        }
        private void sendmsgsp()
        {
            Byte[] TimeStamp = Encoding.Default.GetBytes("next");
            int sendBytes = socketClient.SendTo(TimeStamp, endPoint);
        }
    }
    

    给unity安装uwp时,路径选择应为unity安装的根目录,如C:\Program Files\Unity

    问题二:切换unity场景

    在Assets/Scenes中创建要切换的场景,"SampleScene"为要切换的场景名,在build setting中add open scenes.在收到相应的msg信号后,切换unity场景,使用语句:SceneManager.LoadScene("SampleScene");
    在这里插入图片描述
    在这里插入图片描述

    using UnityEngine.SceneManagement;
    
    if (String.Compare(msg, "1") == 0)
            {
                //接收到msg为1,则切换界面
                SceneManager.LoadScene("SampleScene");
                sendmsgsp();
            }
    

    遇到的问题

    字符串比较

    客户端与服务端字符串编解码方式不同,详见代码。本项目中字符串内容全为单个数字,但仍出现字符串显示内容相同但字符串比较结果不同的情况,采用String.Compare解决。

    //if (string.Equals(msg,"1")  这两种都不行
    //if (msg == "1")
    if (String.Compare(msg, "1") == 0)
    

    unity项目中定义全局变量

    将ip地址和port定义为全局变量,供多个script调用,方便修改。
    创建一个脚本,不需要添加到任何物体上,_IpForConnectport变量可以跨场景全局调用。调用时采用文件名.变量名,如IP_globel._IpForConnect.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class IP_globel
    {
        public static int port = 8888;
        // _IpForConnect 是服务端ip地址
        public static string _IpForConnect = "127.0.0.1";
    }
    

    在这里插入图片描述

    脚本控制场景中物体颜色

    原本颜色控制:
    在这里插入图片描述
    脚本控制

    
    using UnityEngine;
    using UnityEngine.UI;
    
    public class N1_flash : MonoBehaviour
    {
        void Update()
        {
            GameObject obj = GameObject.Find("Image1");
            obj.GetComponent<Image>().color = new Color32(255, 255, 255, 100);
        }
    

    在这里插入图片描述

    https://www.cnblogs.com/CeasarH/p/9262460.html

    一些其他设置

    在这里插入图片描述
    buildsetting->playersetting->
    在这里插入图片描述

    展开全文
  • 本篇主要是分享基于unity的客户端socket网络通信方案。关于服务器的c#-socekt搭建放在了这里《基于C#的Tcp服务端通信》。其中关于socekt粘包断包的处理放在这里分享了《C# socket粘包断包处理》。 整体设计 如图...

    本篇主要是分享基于unity的客户端socket网络通信方案。关于服务器的c#-socekt搭建放在了这里《基于C#的Tcp服务端通信》。其中关于socekt粘包断包的处理放在这里分享了《C# socket粘包断包处理》

    目录

    整体设计

    TcpClient

    连接建立

    消息发送

    消息接收

    关闭连接

    完整代码

    TcpClientMgr-业务处理

    消息发送

    消息接收

    心跳和心跳超时

    消息等待和超时

    完整代码


    整体设计

    如图所示,一共采用了两层封装来处理整个客户端的逻辑。

    首先TcpClient脚本只处理最基础的连接建立,消息的发送和接收。

    TcpClientMgr管理和穿件TcpClient。在利用好连接建立,消息收发的基础上再处理业务上的需求:心跳、消息等待、事件传递等。

    TcpClient

    该层分为四个部分:

    1. 连接建立
    2. 消息发送
    3. 消息接收
    4. 关闭连接

    连接建立

    连接的创建这块采用的是异步连接Socket.ConnectAsync,并且通过Timer来处理连接超时的情况。

                // 创建套接字
                mClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                
                SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs();
                connectArgs.UserToken = mClientSocket;
                // 设置ip和端口号
                connectArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(strIP), intPort);
                // 设置完成回调
                connectArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnect);
                // 启动异步连接
                mClientSocket.ConnectAsync(connectArgs);

    超时的设计是,当开启异步连接的同时创建一定时器,定时器3秒之后执行。执行的内容为关闭连接。若期间连接上了则将定时器任务关闭。

                // 连接超时定时器
                System.Timers.Timer waitTimeOut;  
              
                // 设置超时
                waitTimeOut = new System.Timers.Timer();
                waitTimeOut.AutoReset = false;
                waitTimeOut.Elapsed += OnConnectTimeOut;
                waitTimeOut.Interval = 3000;
                waitTimeOut.Start();
    
                // 超时处理
                private void OnConnectTimeOut(object sender, System.Timers.ElapsedEventArgs e)
                {
                    if (mClientSocket != null && !mClientSocket.Connected)
                    {
                        FDebug.LogError("客户端连接超时");
                        Close();
                    }
                }
    
                

    连接成功之后就可以开启发送和接收任务了(线程),若连接收到的状态不是Success则执行连接失败的回调方法。

            private void OnConnect(object sender, SocketAsyncEventArgs e)
            {
                // 终止超时设置
                waitTimeOut.Stop();
    
                if (e.SocketError == SocketError.Success)
                {
                    // 开启发送线程
                    // 开启接收线程
                    // 发送一个连接请求
                    SendConnect();
                    FDebug.Log("客户端连接成功!");
                }
                else
                {
                    FDebug.LogError("客户端连接失败"+e.SocketError.ToString());
                    mFnOnConnectFailed?.Invoke((int)e.SocketError);
                }
            }

    中间有一个处理是SendConnect(),是给服务器推送一条连接协议。这里面的设计方案是,若服务器收到并返回一个连接协议,才将我们的连接状态标记改为true。

    业务层TcpClientMgr的处理都依赖于这个标记为true。主要是为了确保我们的消息接发正常再跑业务逻辑。

    消息发送

    设置一个消息发送队列

    外部只管向队列中放入我们约定的消息结构TcpData

    内部开启一个线程不停的从队列中取得数据并通过socket发送出去

    关于TcpData的定义可以参考一下这里《基于C#的Tcp服务端通信》

                    // 发送消息线程
                    Thread threadSend = null;
                    // 发送消息队列
                    private BlockQueue<TcpData> mSendQueue = new BlockQueue<TcpData>(50);                
    
                    // 开启发送线程
                    threadSend = new Thread(HandleSend);
                    threadSend.IsBackground = true;
                    threadSend.Start();
    
                    // 发送任务
                    private void HandleSend()
                    {
                        while (bAlive)
                        {
                            try
                            {
                                if (mSendQueue.TryDequeue(out var tcpData))
                                {
                                    byte[] bytesData = tcpData.Get();
                                    mClientSocket.Send(bytesData);
                                }
                            }
                            catch(Exception ex)
                            {
                                FDebug.Log("客户端发送消息异常:" + ex.Message);
                                Close();
                            }
                        }
                    }

    消息接收

    同样的设置一个消息接收队列

    内部开启一个线程去接收来自服务器的消息,收到消息后转换为TcpData存储到队列中

    外部主线程定时的从队列中取得消息

                    // 接收消息线程
                    Thread threadRecive = null;
                    // 接收消息队列
                    private BlockQueue<TcpData> mRecvQueue = new BlockQueue<TcpData>(50);
    
                    // 开启接收线程
                    threadRecive = new Thread(HandleRecieve);
                    threadRecive.IsBackground = true;
                    threadRecive.Start();
    
                    // 接收消息任务
                    private void HandleRecieve()
                    {
                        while (bAlive)
                        {
                            try
                            {
                                SocketError error;
                                int intLength = mClientSocket.Receive(mArryBytesRecMsg, 0 , mArryBytesRecMsg.Length, SocketFlags.None, out error);
                                if (intLength == 0)
                                    continue;
                                if (!bSocketError)
                                {
                                    bSocketError = error != SocketError.Success;
                                }
                                byte[] arryBytes = CommonTool.SubArry(mArryBytesRecMsg, 0, intLength);
                                // 处理粘包断包后
                                if (mTcpReciever.HandleRecMessage(arryBytes))
                                {
                                    TcpData tcpData = null;
                                    while (mTcpReciever.TryGetReciveTcpData(out tcpData))
                                    {
                                        // 处理连接消息
                                        if (!mBConnected && tcpData.protocol == TcpProtocol.TCP_S2C_CONNECT)
                                        {
                                            mBConnected = true;
                                            mFnOnConnectSuccess?.Invoke();
                                            FDebug.Log("客户端建立起通讯!");
                                        }
                                        // 处理业务消息
                                        else
                                        {
                                            mRecvQueue.Enqueue(tcpData);
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                FDebug.Log("客户端接收消息异常:" + ex.Message);
                                Close();
                            }
                        }
                    }

    关闭连接

    关闭这块没啥特别的,主要是要把线程的bAlive标记设置为false让线程自动结束掉。然后重置各种变量。

            public void Close()
            {
                mBConnected = false;
                bAlive = false;
    
                mRecvQueue.Close();
                mSendQueue.Close();
    
                if (mClientSocket != null && mClientSocket.Connected)
                {
                    mClientSocket.Shutdown(SocketShutdown.Both);
                    mClientSocket.Close();
                }
    
                Thread.Sleep(50);
                
                threadSend = null;
                threadRecive = null;
            }

    完整代码

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using GYSQ.Tool;
    using GYSQ.Base;
    
    namespace GYSQ.Net.Tcp.Client
    {
        // tcp客户端
        public class TcpClient
        {
            // 消息缓冲大小
            private const int RECV_BUFFER = 2 * 1024 * 1024;
            // 缓冲字节数组
            private byte[] mArryBytesRecMsg;
            // 套接字
            private Socket mClientSocket = null;
            // 连接标记
            private bool mBConnected = false;
            // 连接超时等待
            System.Timers.Timer waitTimeOut;
            public bool bConnected
            {
                get
                {
                    return mBConnected;
                }
            }
            // 网络错误
            public bool bSocketError = false;
            // 激活标记
            private bool bAlive;
            // 发送消息线程
            Thread threadSend = null;
            private BlockQueue<TcpData> mSendQueue = new BlockQueue<TcpData>(50);
            // 接收消息线程
            Thread threadRecive = null;
            private BlockQueue<TcpData> mRecvQueue = new BlockQueue<TcpData>(50);
    
            // 连接失败回调
            private Action<int> mFnOnConnectFailed;
            // 连接成功回调
            private Action mFnOnConnectSuccess;
    
            // tcp数据接收器
            private TcpMsgRecv mTcpReciever = new TcpMsgRecv();
    
            public void Init(Action<int> fnOnConnectFailed, Action fnOnConnectSuccess)
            {
                mFnOnConnectFailed = fnOnConnectFailed;
                mFnOnConnectSuccess = fnOnConnectSuccess;
    
                mArryBytesRecMsg = new byte[RECV_BUFFER];
    
                waitTimeOut = new System.Timers.Timer();
                waitTimeOut.AutoReset = false;
                waitTimeOut.Elapsed += OnConnectTimeOut;
            }
    
            public void Connect(string strIP, int intPort)
            {
                // 重置状态
                bAlive = true;
                bSocketError = false;
                mRecvQueue.Reset();
                mSendQueue.Reset();
    
                mClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs();
                connectArgs.UserToken = mClientSocket;
                connectArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(strIP), intPort);
                connectArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnect);
    
                mClientSocket.ConnectAsync(connectArgs);
    
                // 设置超时
                waitTimeOut.Interval = 3000;
                waitTimeOut.Start();
            }
    
            // 超时处理
            private void OnConnectTimeOut(object sender, System.Timers.ElapsedEventArgs e)
            {
                if (mClientSocket != null && !mClientSocket.Connected)
                {
                    FDebug.LogError("客户端连接超时");
                    Close();
                }
            }
    
            private void OnConnect(object sender, SocketAsyncEventArgs e)
            {
                // 终止超时设置
                waitTimeOut.Stop();
    
                if (e.SocketError == SocketError.Success)
                {
                    // 开启发送线程
                    threadSend = new Thread(HandleSend);
                    threadSend.IsBackground = true;
                    threadSend.Start();
                    // 开启接收线程
                    threadRecive = new Thread(HandleRecieve);
                    threadRecive.IsBackground = true;
                    threadRecive.Start();
                    // 发送一个连接请求
                    SendConnect();
                    FDebug.Log("客户端连接成功!");
                }
                else
                {
                    FDebug.LogError("客户端连接失败"+e.SocketError.ToString());
                    mFnOnConnectFailed?.Invoke((int)e.SocketError);
                }
            }
    
            private void HandleSend()
            {
                while (bAlive)
                {
                    try
                    {
                        if (mSendQueue.TryDequeue(out var tcpData))
                        {
                            byte[] bytesData = tcpData.Get();
                            mClientSocket.Send(bytesData);
                        }
                    }
                    catch(Exception ex)
                    {
                        bSocketError = false;
                        FDebug.Log("客户端发送消息异常:" + ex.Message);
                    }
                }
            }
    
            private void HandleRecieve()
            {
                while (bAlive)
                {
                    try
                    {
                        int intLength = mClientSocket.Receive(mArryBytesRecMsg, 0 , mArryBytesRecMsg.Length, SocketFlags.None);
                        if (intLength == 0)
                            continue;
                        byte[] arryBytes = CommonTool.SubArry(mArryBytesRecMsg, 0, intLength);
                        // 处理粘包断包后
                        if (mTcpReciever.HandleRecMessage(arryBytes))
                        {
                            TcpData tcpData = null;
                            while (mTcpReciever.TryGetReciveTcpData(out tcpData))
                            {
                                // 处理连接消息
                                if (!mBConnected && tcpData.protocol == TcpProtocol.TCP_S2C_CONNECT)
                                {
                                    mBConnected = true;
                                    mFnOnConnectSuccess?.Invoke();
                                    FDebug.Log("客户端建立起通讯!");
                                }
                                // 处理业务消息
                                else
                                {
                                    mRecvQueue.Enqueue(tcpData);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        bSocketError = false;
                        FDebug.Log("客户端接收消息异常:" + ex.Message);
                    }
                }
            }
    
            public void Close()
            {
                mBConnected = false;
                bAlive = false;
    
                mRecvQueue.Close();
                mSendQueue.Close();
    
                if (mClientSocket != null && mClientSocket.Connected)
                {
                    mClientSocket.Shutdown(SocketShutdown.Both);
                    mClientSocket.Close();
                }
    
                Thread.Sleep(50);
                
                threadSend = null;
                threadRecive = null;
            }
    
            public void Send(TcpData tcpData)
            {
                mSendQueue.Enqueue(tcpData);
            }
    
            public bool TryGetRecieve(out TcpData tcpData)
            {
                return mRecvQueue.TryDequeue(out tcpData);
            }
    
            public bool HasRecieveData()
            {
                return mRecvQueue.count > 0;
            }
    
            private void SendConnect()
            {
                TcpData tcpData = TcpData.GetPooled();
                tcpData.Build(TcpProtocol.TCP_C2S_CONNECT, " ");
                mSendQueue.Enqueue(tcpData);
            }
        }
    }
    

    TcpClientMgr-业务处理

    该脚本包含了四块处理

    1. 消息发送
    2. 消息接收并通过事件系统转发到其他业务系统
    3. 心跳和心跳超时
    4. 消息等待和消息等待超时

    心跳那块是比较通用的一个业务处理,主要是双方可以通过心跳来判定连接是否继续存在。

    消息等待超时这块主要是为了满足以下的情况:客户端向服务器请求了某个消息,客户端在该消息来之前需要锁定用户的操作和socekt通信直到服务器返回指定的消息为止。

    比如客户端登录之后请求玩家信息,如果玩家信息没有返回,则不能做后续的处理。那么发送完请求之后需要设置好等待的消息,再继续处理后续业务。

    消息发送

    发送前判定一下连接是否建立好就可以了。

        // 发送网络消息
        public void Send(int protocol, string jsonData = null, int waitProtocol = -1)
        {
            if (!mTcpClient.bConnected)
            {
                return;
            }
    
            TcpData tcpData = TcpData.GetPooled();
            tcpData.Build(protocol, jsonData);
            mTcpClient.Send(tcpData);
    
            if (waitProtocol != -1)
            {
                SetTcpWait(waitProtocol);
            }
        }

    消息接收

    消息的接收需要放在unity的update中。

    每次update从TcpClient的接收队列中取得数据。

    取得数据后直接推送到事件系统即可。

                // 获取网络消息
                if (mTcpClient.HasRecieveData())
                {
                    mTcpClient.TryGetRecieve(out var tcpData);
    
                    // 发送业务事件
                    TcpEvent.instance.NotifyEvent(tcpData.protocol, tcpData.GetJsonContent());
    
                    // 回收数据包
                    tcpData.Dispose();
                }

    这里有一个注意的地方,就是我们从队列中取得数据前先判定有没有数据,有再去取得。

    这里为什么不直接调用TryGetRecieve去取得呢。

    主要是我们的接收和发送消息队列都是基于多线程来处理的(有兴趣可以看一下这篇《Unity游戏开发 基于多线程的Http网络通信》,里面有一个BlockQueue(多线程队列))。

    我们队列去Dequeue的时候如果当前没有数据会将调用的线程挂起等待有数据再执行。

    如果我们再unity主线程直接调用Dequeue的话主线程就会被阻塞。

    另外就是要注意再非主线程,如果用了while(true)这样的逻辑将我们多线程队列的操作包起来的话则反而不能先判定数据有没有再取得。

    因为这样相当于执行了一个while(true){if(xxx);}。当前线程就会处于满载状态,因此这种情况必须直接用我们多线程队列的接口去取得数据。

    或者也可以自己处理一下线程的调度,当取不到数据的时候手动将当前线程挂起,等待有数据再唤醒。

    心跳和心跳超时

    简单的说就是,定时向服务器推送一个消息A,服务器会回复一个消息B。这样双方可以确认连接还在。

    如果客户端超过一定时间没有收到消息B则判定超时,断开连接。

    同理服务器超过一定时间没有收到消息A则判定超时,断开连接。

    这块的逻辑也是写在unity的update里面的。

                    // 心跳发送
                    mFPingTimer += Time.deltaTime;
                    if (mFPingTimer > PING_SEND_TIME)
                    {
                        Send(TcpProtocol.TCP_C2S_HEART);
                        mFPingTimer = 0f;
                    }
                    // 心跳超时
                    mFPongTimer += Time.deltaTime;
                    if (mFPongTimer > PONG_TIME_OUT)
                    {
                        FDebug.LogError("客户端接收心跳超时!");
                        HandleLostConnection();
                    }

    消息等待和超时

    当我们发送一条消息的同时设置了需要等待的消息id的时候。

    客户端打开网络层遮罩屏蔽用户的输入并启动计时器,超过一定时间不返回则弹出消息等待窗口,如果继续超时就断开连接。

    这里启动屏蔽层和消息等待窗口都是用事件通知的形式处理的。

        // 发送网络消息
        public void Send(int protocol, string jsonData = null, int waitProtocol = -1)
        {
            if (!mTcpClient.bConnected)
            {
                return;
            }
    
            TcpData tcpData = TcpData.GetPooled();
            tcpData.Build(protocol, jsonData);
            mTcpClient.Send(tcpData);
    
            if (waitProtocol != -1)
            {
                SetTcpWait(waitProtocol);
            }
        }
    
        // 设置网络等待
        private void SetTcpWait(int protocol)
        {
            mWaitTimer = 0f;
            mBWaiting = true;
            mWaitProtocol = protocol;
            // 通知游戏业务网络请求等待中
            GameEvent.instance.NotifyEvent(GameEventId.TCP_WAIT);
        }
    
        // 重置网络等待
        public void ResetTcpWait()
        {
            mBWaiting = false;
            mWaitTimer = 0f;
            // 通知游戏业务解除请求等待
            GameEvent.instance.NotifyEvent(GameEventId.TCP_RESET_WAIT);
            GameEvent.instance.NotifyEvent(GameEventId.TCP_HIDE_LOCK);
        }
    
        // 处理消息等待
        if (mBWaiting &&
            mWaitProtocol == tcpData.protocol)
        {
            ResetTcpWait();
        }

    完整代码

    这里可以看到TcpClientMgr是一个单例。

    因为客户端一般不需要一次监听多个端口所有搞个单例方便使用。

    using System;
    using GYSQ.Net.Tcp.Client;
    using GYSQ.Net.Tcp;
    using UnityEngine;
    
    // tcp客户端业务管理器
    public class TcpClientMgr : MonoSingleTon<TcpClientMgr>
    {
        // tcp客户端通信
        private TcpClient mTcpClient = new TcpClient();
    
        // 工作状态
        private bool mBWorking = false;
    
        // tcp消息等待处理
        private int mWaitProtocol;
        private float mWaitTimer = 0f;
        private bool mBWaiting = false;
        private const float WAIT_TIME_OUT = 5f;
        private const float WAIT_SHOW_LOCK = 0.5f;
    
        // 连接相关业务回调
        private Action mFnConnectSuccess;
        private Action mFnConnectFailed;
        private Action mFnLostConnection;
    
        // 心跳
        private float mFPingTimer = 0f;
        private float mFPongTimer = 0f;
        // 心跳发送间隔
        private const float PING_SEND_TIME = 5f;
        // 心跳超时间隔
        private const float PONG_TIME_OUT = 15;
    
        // 初始化
        public override void Init()
        {
            mTcpClient.Init(OnConnectFailed, OnConnectSuccess);
            TcpEvent.instance.Init();
        }
    
        private void Update()
        {
            if (!mTcpClient.bConnected)
            {
                return;
            }
    
            // 处理网络异常
            if (mTcpClient.bSocketError)
            {
                HandleLostConnection();
            }
            else
            {
                // 获取网络消息
                if (mTcpClient.HasRecieveData())
                {
                    mTcpClient.TryGetRecieve(out var tcpData);
                    // 处理消息等待
                    if (mBWaiting &&
                        mWaitProtocol == tcpData.protocol)
                    {
                        ResetTcpWait();
                    }
                    // 处理心跳
                    else if (tcpData.protocol == TcpProtocol.TCP_S2C_HEART)
                    {
                        mFPongTimer = 0f;
                    }
    
                    FDebug.LogClientTcp(tcpData.protocol, tcpData.bytesData);
                    // 发送业务事件
                    TcpEvent.instance.NotifyEvent(tcpData.protocol, tcpData.GetJsonContent());
    
                    // 回收数据包
                    tcpData.Dispose();
                }
    
                // 处理工作状态
                if (mBWorking)
                {
                    // 心跳发送
                    mFPingTimer += Time.deltaTime;
                    if (mFPingTimer > PING_SEND_TIME)
                    {
                        Send(TcpProtocol.TCP_C2S_HEART);
                        mFPingTimer = 0f;
                    }
                    // 心跳超时
                    mFPongTimer += Time.deltaTime;
                    if (mFPongTimer > PONG_TIME_OUT)
                    {
                        FDebug.LogError("客户端接收心跳超时!");
                        HandleLostConnection();
                    }
                }
    
                // 处理等待状态
                if(mBWaiting)
                {
                    mWaitTimer += Time.deltaTime;
                    if (mWaitTimer > WAIT_TIME_OUT)
                    {
                        FDebug.LogError("客户端等待消息超时!");
                        HandleLostConnection();
                    }
                    else if (mWaitTimer > WAIT_SHOW_LOCK)
                    {
                        GameEvent.instance.NotifyEvent(GameEventId.TCP_SHOW_LOCK);
                    }
                }
            }
        }
    
        // 请求连接
        public void Connect(string ip,
            int port,
            Action fnSuccess,
            Action fnFailed,
            Action fnLost)
        {
            mTcpClient.Connect(ip, port);
            mFnConnectSuccess = fnSuccess;
            mFnConnectFailed = fnFailed;
            mFnLostConnection = fnLost;
        }
    
        // 断开连接
        public void BreakConnection()
        {
            mBWorking = false;
            mBWaiting = false;
            mWaitTimer = 0f;
            mTcpClient.Close();
            FDebug.LogError("客户端主动断开连接!");
        }
    
        // 连接失败
        private void OnConnectFailed(int intErrorCode)
        {
            mFnConnectFailed?.Invoke();
        }
    
        // 连接成功
        private void OnConnectSuccess()
        {
            mBWorking = true;
            mBWaiting = false;
            mWaitTimer = 0f;
            mFnConnectSuccess?.Invoke();
        }
    
        // 处理响应超时
        private void HandleLostConnection()
        {
            mBWorking = false;
            mBWaiting = false;
            mWaitTimer = 0f;
            mTcpClient.Close();
            mFnLostConnection?.Invoke();
            FDebug.LogError("丢失连接!");
        }
    
        // 发送网络消息
        public void Send(int protocol, string jsonData = null, int waitProtocol = -1)
        {
            if (!mTcpClient.bConnected)
            {
                return;
            }
    
            TcpData tcpData = TcpData.GetPooled();
            tcpData.Build(protocol, jsonData);
            mTcpClient.Send(tcpData);
    
            if (waitProtocol != -1)
            {
                SetTcpWait(waitProtocol);
            }
        }
    
        // 设置网络等待
        private void SetTcpWait(int protocol)
        {
            mWaitTimer = 0f;
            mBWaiting = true;
            mWaitProtocol = protocol;
            // 通知游戏业务网络请求等待中
            GameEvent.instance.NotifyEvent(GameEventId.TCP_WAIT);
        }
    
        // 重置网络等待
        public void ResetTcpWait()
        {
            mBWaiting = false;
            mWaitTimer = 0f;
            // 通知游戏业务解除请求等待
            GameEvent.instance.NotifyEvent(GameEventId.TCP_RESET_WAIT);
            GameEvent.instance.NotifyEvent(GameEventId.TCP_HIDE_LOCK);
        }
    }
    

     

    展开全文
  • 这是一套基于TCP网络传送方案,Unity开发的客户端以及服务端,局域网内测试完全通过(有需要的朋友,可以,部署自己的服务器,配置公网IP和端口号即可使用),关于中文传输现在显示的???,可将发送和接受数据格式 ...

    背景

    开发过程中,联网发送信息,在现在是不可以避免的,客户端之间需要数据相互同步,达到人人互联。

    原理开发

    这是一套基于TCP网络传送方案,Unity开发的客户端以及服务端,局域网内测试完全通过(有需要的朋友,可以,部署自己的服务器,配置公网IP和端口号即可使用),关于中文传输现在显示的???,可将发送和接受数据格式 Encoding.ASCII转变成 Encoding.UTF8即可。注意:一定要在PC端开启Server,模拟器安卓端将无法获取端口号,开启Server会报错!

    核心代码

    服务端代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets; 
    using System.Text; 
    using System.Threading;
    using UnityEngine;
    
    public class TCPTestServer : MonoBehaviour
    {
    	[Serializable]
    	public class ClientData
    	{
    		public static int MAX_ID;
    		
    		public int ID;
    		public string Name;
    	}
    	
    	public class ConnectedClient
    	{
    		public ClientData ClientData;
    		public TcpClient Client;
    
    		public ConnectedClient(ClientData data, TcpClient client)
    		{
    			ClientData = data;
    			Client = client;
    		}
    	}
    
    	[Serializable]
    	public class ServerMessage
    	{
    		public ClientData SenderData;
    		public string Data;
    
    		public ServerMessage(ClientData client, string message)
    		{
    			SenderData = client;
    			Data = message;
    		}
    	}
    	
    	public Action<string> OnLog = delegate{};
    	
    	public bool IsConnected
    	{
    		get { return tcpListenerThread != null && tcpListenerThread.IsAlive; }
    	}
    	
    	public string IPAddress = "127.0.0.1";
    	public int Port = 8052;
    	
    	/// <summary> 	
    	/// TCPListener to listen for incoming TCP connection 	
    	/// requests. 	
    	/// </summary> 	
    	private TcpListener tcpListener;
    
    	/// <summary> 
    	/// Background thread for TcpServer workload. 	
    	/// </summary> 	
    	private Thread tcpListenerThread;
    
    	private List<ConnectedClient> connectedClients = new List<ConnectedClient>();
    
    	// Use this for initialization
    	public void StartServer()
    	{
    		// Start TcpServer background thread 		
    		tcpListenerThread = new Thread(ListenForIncomingRequests);
    		tcpListenerThread.IsBackground = true;
    		tcpListenerThread.Start();
    	}
    
    	/// <summary> 	
    	/// Runs in background TcpServerThread; Handles incoming TcpClient requests 	
    	/// </summary> 	
    	private void ListenForIncomingRequests()
    	{
    		try
    		{
    			// Create listener on localhost port 8052. 			
    			tcpListener = new TcpListener(System.Net.IPAddress.Any, Port);
    			tcpListener.Start();
    			
    			ThreadPool.QueueUserWorkItem(ListenerWorker, null);
    			OnLog("Server is listening");
    		}
    		catch (SocketException socketException)
    		{
    			OnLog("SocketException " + socketException);
    		}
    	}
    
    	private void ListenerWorker(object token)
    	{
    		while (tcpListener != null)
    		{
    			var client = tcpListener.AcceptTcpClient();
    			ThreadPool.QueueUserWorkItem(HandleClientWorker, client);
    		}
    	}
    	
    	private void HandleClientWorker(object token)
    	{
    		Byte[] bytes = new Byte[1024];
    		using (TcpClient client = token as TcpClient)
    		{
    			ClientData data = new ClientData();
    			data.ID = ++ClientData.MAX_ID;
    			data.Name = "User" + data.ID;
    
    			ConnectedClient connectedClient = new ConnectedClient(data, client);
    			connectedClients.Add(connectedClient);
    			OnLog(string.Format("{0} has Connected as {1}", ((IPEndPoint)client.Client.RemoteEndPoint).Address, data.Name));
    			DispatchMessage(new ServerMessage(data, "Client Connected"));
    
    			// Get a stream object for reading
    			try
    			{
    				using (NetworkStream stream = client.GetStream())
    				{
    					int length;
    					// Read incoming stream into byte array. 						
    					while (stream.CanRead && (length = stream.Read(bytes, 0, bytes.Length)) != 0)
    					{
    						var incomingData = new byte[length];
    						Array.Copy(bytes, 0, incomingData, 0, length);
                            // Convert byte array to string message.
                            //string clientMessage = Encoding.UTF8.GetString(incomingData, 0, length);
                            string clientMessage = Encoding.ASCII.GetString(incomingData);
    						//OnLog("Server received: " + clientMessage);
    							
    						if (clientMessage == "!disconnect")
    						{
    							stream.Close();
    							client.Close();
    						}
    						
    						ServerMessage serverMessage = new ServerMessage(data, clientMessage);
    						if (clientMessage.StartsWith("!"))
    						{
    							ProcessMessage(connectedClient, clientMessage);
    						}
    						else
    						{
    							DispatchMessage(serverMessage);
    						}
    					}
    				}
    			}
    			catch (SocketException e)
    			{
    				OnLog(e.ToString());
    			}
    		}
    	}
    
    	private void ProcessMessage(ConnectedClient connectedClient, string command)
    	{
    		string[] split = command.Split(' ');
    		string response = string.Empty;
    		ServerMessage serverMessage = null;
    		switch (split[0])
    		{
    				case "!disconnect":
    					response = (string.Format("{0} has Disconnected", connectedClient.ClientData.Name));
    					OnLog(response);
    					DisconnectClient(connectedClient);
    					break;
    				case "!ping":
    					response = String.Join(" ", split) + " " + (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
    					serverMessage = new ServerMessage(connectedClient.ClientData, response);
    					SendMessage(connectedClient.Client, serverMessage);
    					break;
    				default:
    					response = "Unknown Command '" + command + "'";
    					serverMessage = new ServerMessage(connectedClient.ClientData, response);
    					SendMessage(connectedClient.Client, serverMessage);
    					break;
    		}
    	}
    
    	private void DispatchMessage(ServerMessage serverMessage)
    	{
    		for (int i = 0; i < connectedClients.Count; i++)
    		{
    			ConnectedClient connection = connectedClients[i];
    			TcpClient client = connection.Client;
    			if (!SendMessage(client, serverMessage))
    			{
    				OnLog(string.Format("Lost connection with {0}", connection.ClientData.Name));
    				DisconnectClient(connection);
    				i--;
    			}
    		}
    	}
    
    	private void DisconnectClient(ConnectedClient connection)
    	{
    		connectedClients.Remove(connection);
    	}
    
    	/// <summary> 	
    	/// Send message to client using socket connection. 	
    	/// </summary> 	
    	private bool SendMessage(TcpClient client, ServerMessage serverMessage)
    	{
    		if (client != null && client.Connected)
    		{
    			try
    			{
    				// Get a stream object for writing. 			
    				NetworkStream stream = client.GetStream();
    				if (stream.CanWrite)
    				{
                        // Convert string message to byte array.           
                        //  byte[] serverMessageAsByteArray = Encoding.UTF8.GetBytes(JsonUtility.ToJson(serverMessage));
                        byte[] serverMessageAsByteArray = Encoding.ASCII.GetBytes(JsonUtility.ToJson(serverMessage));
    					// Write byte array to socketConnection stream.               
    					stream.Write(serverMessageAsByteArray, 0, serverMessageAsByteArray.Length);
    					return true;
    				}
    			}
    			catch (SocketException socketException)
    			{
    				OnLog("Socket exception: " + socketException);
    			}
    		}
    
    		return false;
    	}
    }
    

    客户端代码:

    using System;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using UnityEngine;
    
    public class TCPTestClient : MonoBehaviour
    {
    	public Action<TCPTestClient> OnConnected = delegate{};
    	public Action<TCPTestClient> OnDisconnected = delegate{};
    	public Action<string> OnLog = delegate{};
    	public Action<TCPTestServer.ServerMessage> OnMessageReceived = delegate{};
    
    	public bool IsConnected
    	{
    		get { return socketConnection != null && socketConnection.Connected; }
    	}
    
    	public string IPAddress = "127.0.0.1";
    	public int Port = 8052;
    	
    	private TcpClient socketConnection;
    	private Thread clientReceiveThread;
    	private NetworkStream stream;
    	private bool running;
    
    
    	/// <summary> 	
    	/// Setup socket connection. 	
    	/// </summary> 	
    	public void ConnectToTcpServer()
    	{
    		try
    		{
    			OnLog(string.Format("Connecting to {0}:{1}", IPAddress, Port));
    			clientReceiveThread = new Thread(new ThreadStart(ListenForData));
    			clientReceiveThread.IsBackground = true;
    			clientReceiveThread.Start();
    		}
    		catch (Exception e)
    		{
    			OnLog("On client connect exception " + e);
    		}
    	}
    
    	/// <summary> 	
    	/// Runs in background clientReceiveThread; Listens for incoming data. 	
    	/// </summary>     
    	private void ListenForData()
    	{
    		try
    		{
    			socketConnection = new TcpClient(IPAddress, Port);
    			OnConnected(this);
    			OnLog("Connected");
    			
    			Byte[] bytes = new Byte[1024];
    			running = true;
    			while (running)
    			{
    				// Get a stream object for reading
    				using (stream = socketConnection.GetStream())
    				{
    					int length;
    					// Read incoming stream into byte array. 					
    					while (running && stream.CanRead)
    					{
    						length = stream.Read(bytes, 0, bytes.Length);
    						if (length != 0)
    						{
    							var incomingData = new byte[length];
    							Array.Copy(bytes, 0, incomingData, 0, length);
                                // Convert byte array to string message. 	
                                //string serverJson = Encoding.UTF8.GetString(incomingData, 0, length);
                                string serverJson = Encoding.ASCII.GetString(incomingData);
    							TCPTestServer.ServerMessage serverMessage = JsonUtility.FromJson<TCPTestServer.ServerMessage>(serverJson);
    							MessageReceived(serverMessage);
    						}
    					}
    				}
    			}
    			socketConnection.Close();
    			OnLog("Disconnected from server");
    			OnDisconnected(this);
    		}
    		catch (SocketException socketException)
    		{
    			OnLog("Socket exception: " + socketException);
    		}
    	}
    
    	public void CloseConnection()
    	{
    		SendMessage("!disconnect");
    		running = false;
    	}
    
    	public void MessageReceived(TCPTestServer.ServerMessage serverMessage)
    	{
    		OnMessageReceived(serverMessage);
    	}
    
    	/// <summary> 	
    	/// Send message to server using socket connection. 	
    	/// </summary> 	
    	public bool SendMessage(string clientMessage)
    	{
    		if (socketConnection != null && socketConnection.Connected)
    		{
    			try
    			{
    				// Get a stream object for writing. 			
    				NetworkStream stream = socketConnection.GetStream();
    				if (stream.CanWrite)
    				{
                        // Convert string message to byte array.                 
                        // byte[] clientMessageAsByteArray = Encoding.UTF8.GetBytes(clientMessage);
                        byte[] clientMessageAsByteArray = Encoding.ASCII.GetBytes(clientMessage);
    					// Write byte array to socketConnection stream.                 
    					stream.Write(clientMessageAsByteArray, 0, clientMessageAsByteArray.Length);
    					OnSentMessage(clientMessage);
    					return true;
    				}
    			}
    			catch (SocketException socketException)
    			{
    				OnLog("Socket exception: " + socketException);
    			}
    		}
    
    		return false;
    	}
    
    	public virtual void OnSentMessage(string message)
    	{
    	}
    }
    

    演示视频:
    https://live.csdn.net/v/110261
    有需要的,可自取Demo_011
    链接:https://pan.baidu.com/s/1CP4FLttldNHW-gW2zUrlDA
    提取码:ncv6

    展开全文
  • 通过c#开发服务器端,unity搭建客户端。实现客户端多用户之间消息的收发,类似于群聊的一种效果。 先看一下程序最终运行效果图: 服务端程序: using System; using System.Collections.Generic; using ...
  • 只要自己能辨别就行,创建完后,点击查看应用详情在应用详情界面,我们可以看到自己的应用名称,AppID、APIKey、SecretKey,这些后续开发是要用到的 然后点击左侧列表中的服务端SDK下载,找到C#SDK下载 下载后打开...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • 第二部分“网络原理”:第6章至第8章,主要介绍网络通信的原理,开发客户端的网络模块和服务端程序框架。这套框架具有较高的通用性,可以运用在多种游戏上。 第三部分“网络游戏”:第9章至第12章,主要讲解房间系统...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C#.net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp协议...
  • Unity 3D网络游戏实战

    2019-04-25 13:59:46
    第二部分“网络原理”:第6章至第8章,主要介绍网络通信的原理,开发客户端的网络模块和服务端程序框架。这套框架具有较高的通用性,可以运用在多种游戏上。 第三部分“网络游戏”:第9章至第12章,主要讲解房间系统...
  • Unity高效游戏开发技术栈尝试 (编辑器扩展,工作流改善) (基于Playable的简单强大的动画解决方案) (快速关卡原型制定解决方案) (基于C#双端共享代码的分布式网络游戏开发框架) (简单强大的资源管理...
  • 五:基于前面游戏项目的开发,进一步刨析PureMVC架构的高级开发技巧:     1:PureMVC框架类与脚本之间的相互通讯机理。     2:基于PureMVC框架原理,实现游戏项目模型与脚本的彻底...
  • 《攻城Online》,以下简称“攻城”,是基于Unity3D引擎开发的一款MMORPG端游。  “攻城”开发采用C/S架构,其中服务端使用了Photon引擎来简化开发流程。Photon引擎底层是C++,采用多线程并发处理客户端连接,且...
  • ET 之网络框架

    2021-01-28 15:20:38
    ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET框架简介

    千次阅读 2020-06-28 09:32:00
    ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • 基于protobuf3开发的一个简易网络通信,unity做客户端,c#做服务端
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp ...
  • Bmob后台云数据库

    千次阅读 2019-06-23 01:45:54
    使用Bmob基于Unity3D、UE4、微信小游戏、Cocos2Dx等游戏引擎开发的客户端,接入Bmob提供的客户端Game SDK后,通过Tcp、Udp、WebSocket等方式与服务端通讯,能在1小时内让单机游戏变成多人同时在线的联网游戏。...

空空如也

空空如也

1 2
收藏数 37
精华内容 14
关键字:

服务端基于unity开发