Unity开发微信小游戏完整流程(包括云服务器搭建部署和CDN的使用)注意!!!是完完整整的流程!!!

Unity开发微信小游戏完整流程,从Unity版本选择,到服务器开发,最后到小游戏的发布,所有流程都会详细介绍,包括一些注意事项和问题解决。

准备工作:

Unity版本选择问题

如果使用Unity团结引擎,那么可以直接进行开发(团结引擎版本,打包出去后小游戏右下角有水印,如果能接受就无所谓了),如果不使用团结引擎的话,Unity的版本选择2021版,22版本URP项目导出的小游戏会报Shader错误

Unity微信工具下载

地址:https://game.weixin.qq.com/cgi-bin/gamewxagwasmsplitwap/getunityplugininfo?download=1

云服务器和CDN(如果有云服务器和CDN需求,先备案域名!!!)

提前说明,工信部域名备案审核需要20天!!!,所以有需求的话,先把域名备案了。

云服务器网上一搜一大推,看自己选择,我使用的是华为云,以下所有服务器配置之类的都是基于华为云,别的大差不大,也可以借鉴。!

UnityWebSocket(没有后端需求可以忽略)

地址:UnityWebSocket: UnityWebSocket

因为微信小游戏仅支持wss://方式链接后端,所以需要使用UnityWebSocket

微信公众平台注册和微信开发者工具下载

链接:微信公众平台

注册账号相关信息,下载微信开发工具

Unity开发:

先导入准备工作中下载的Unity微信工具,和UnityWebSocket(没有后端需求可以忽略)。

UnityWebSocket导入时,会有一个案例,可以看看,很简单的

注意事项

第一,项目中不要使用多线程相关,不支持,可以使用Unity的协同程序或者异步函数

第二,文件系统System.IO不支持,需要使用微信SDK的文件系统,本地只读文件可以使用Resources.lod加载,Json读取数据,或者可以直接使用ScriptableObject。(这里有一点就是,导入Unity微信工具后,这个插件里面自带LitJson)

第三,System.Net不支持,改为UnityWebSocket(也支持UnityWebRequest)

第四,所有客户端能用到的URL都需要在微信公众平台-开发管理-服务器域名里面进行填写,

包括下载头像(填写一级域名即可),如果域名后有端口号,那就把端口号也一起写上

第五,微信小游戏也是需要备案的,和域名备案的时间差不多,也是20天左右。如果手里有美术设计图之类的,可以提前备案。微信公众平台-左下角-账号设置-小程序备案。

微信文件系统API使用

微信文件系统:FileSystemManager | 微信开放文档

//创建全局唯一文件管理器
public static WXFileSystemManager WXFileManager = new WXFileSystemManager();
//微信提供的一个可以让用户读写的根目录
private string rootPath = WX.env.USER_DATA_PATH;
//这里举一个例子,是我存储音乐和音效声音大小到本地的方法
private string SettingData = "Setting.xxl";
private string fileName = "/Setting";
public void GetMusicSoundVolume_WX(out float music, out float sound)
{
  byte[] bytes = new byte[8];
  //判断文件夹/或者文件是否存。如果存在,返回"access:ok"
  if (WXFileManager.AccessSync(rootPath + fileName) != "access:ok")
  {
     //‌mkdirParam‌ 是一个结构体,用于定义创建目录时所需的参数
     MkdirParam mkdirParam = new MkdirParam();
     //fileName为自定义文件夹名 注意:前面必须加@符号,不然无法创建目录
     mkdirParam.dirPath = rootPath + @fileName;
     //recursive MkdirParam 只能创建单一层级的目录,如果你需要创建多层级的目录(即递归创建目录),则需要设置recursive = trur
     mkdirParam.recursive = true;
     //创建目录
     WXFileManager.Mkdir(mkdirParam);
     music = sound = 1f;
     BitConverter.GetBytes(music).CopyTo(bytes, 0);
     BitConverter.GetBytes(sound).CopyTo(bytes, 4);
     //写入文件
     WXFileManager.WriteFileSync(rootPath+ fileName+ "/"+ SettingData, bytes);
   }
   else
   {
      //如果文件存在,直接读取就行
      bytes = WXFileManager.ReadFileSync(rootPath + fileName + "/" + SettingData);
      music = BitConverter.ToSingle(bytes, 0);
      sound = BitConverter.ToSingle(bytes, 4);
   }
}

获取自己的微信信息(名称、头像)

注:先到微信公众平台-左下角小程序名称-账号设置 找到服务内容说明

把这两个设置了

Unity中创建个界面,就是UI登录界面,加个开始按钮,自己随意设置,按钮点击事件绑定函数StartGame

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using WeChatWASM;

public class LoaderTest : MonoBehaviour
{
    /// <summary>
    /// 用户信息
    /// 获取到用户信息后可根据需要存云端或本地持久存储
    /// </summary>
    public WXUserInfo userInfo;

    /// <summary>
    /// 是否获取用户信息
    /// </summary>
    private bool infoFlag = false;

    /// <summary>
    /// 头像材质
    /// </summary>
    public Texture2D avatarTexture;

    // Start is called before the first frame update
    void Start()
    {
        // 初始化微信SDK
        WX.InitSDK((code) =>
        {
            Debug.Log("init WXSDK code: " + code);
            // 加载用户信息
            this.LoaderWXMess();
        });
    }

    /// <summary>
    /// 加载微信授权相关信息
    /// </summary>
    private void LoaderWXMess()
    {
        // 1. 询问隐私协议授权情况
        WX.GetPrivacySetting(new GetPrivacySettingOption()
        {
            success = (res) =>
            {
                /**
                 * needAuthorization - 含义
                 * 是否需要用户授权隐私协议(如果开发者没有在[mp后台-设置-服务内容声明-用户隐私保护指引]中声明隐私收集类型则会返回false;
                 * 如果开发者声明了隐私收集,且用户之前同意过隐私协议则会返回false;
                 * 如果开发者声明了隐私收集,且用户还没同意过则返回true;
                 * 如果用户之前同意过、但后来小程序又新增了隐私收集类型也会返回true)
                 */
                // 询问成功
                if (res.needAuthorization)
                {
                    // 有隐私协议,且未授权
                    // 2. 发起隐私协议授权
                    // 弹出隐私协议询问弹窗
                    WX.RequirePrivacyAuthorize(new RequirePrivacyAuthorizeOption()
                    {
                        success = (res) =>
                        {
                            Debug.Log("同意隐私协议:" + JsonUtility.ToJson(res, true));
                            // 用户同意隐私协议
                            // 3. 获取用户信息
                            this.GetScopeInfoSetting();
                            // 将 信息获取标志 标记为true
                            this.infoFlag = true;
                        },
                        fail = (err) =>
                        {
                            Debug.Log("拒绝隐私协议:" + JsonUtility.ToJson(res, true));
                        },
                        complete = (res) =>
                        {
                            Debug.Log("询问隐私协议结束");
                        }
                    });
                }
            },
            fail = (err) => { },
            complete = (res) =>
            {
                // 处理询问隐私协议失败或之前已经同意但未授权用户信息的情况
                if (!this.infoFlag)
                {
                    this.GetScopeInfoSetting();
                }
            }
        });
    }


    /// <summary>
    /// 点击开始游戏
    /// </summary>
    public void StartGame()
    {
        Debug.Log("start game");
        Debug.Log("用户信息:" + JsonUtility.ToJson(this.userInfo, true));
    }

    /// <summary>
    /// 获取授权
    /// </summary>
    private void GetScopeInfoSetting()
    {
        // 询问用户信息授权情况
        WX.GetSetting(new GetSettingOption()
        {
            success = (res) =>
            {
                Debug.Log("获取用户信息授权情况成功: " + JsonUtility.ToJson(res.authSetting, true));
                // 判断用户信息的授权情况
                if (!res.authSetting.ContainsKey("scope.userInfo") || !res.authSetting["scope.userInfo"])
                {
                    // 3.1 未授权,创建授权按钮区
                    // 需引导用户点击所创建的区域,这里的做法是将开始游戏的按钮放在该区域
                    this.CreateUserInfoButton();
                }
                else
                {
                    // 3.2 已授权,直接获取用户信息
                    this.GetUserInfo();
                    // 这里也可以先不获取,留到点击开始游戏按钮再获取,但没必要,先获取后存起来即可
                }
            },
            fail = (err) =>
            {
                Debug.Log("获取用户信息授权情况失败:" + JsonUtility.ToJson(err, true));
            }
        });
    }

    /// <summary>
    /// 创建用户信息授权点击区域
    /// </summary>
    private void CreateUserInfoButton()
    {
        Debug.Log("create userinfo button area");

        /**
         * 方法一:创建用户信息获取按钮,在底部区域创建一个300高度的 透明!!! 区域
         * 首次获取会弹出用户授权窗口, 可通过右上角-设置-权限管理用户的授权记录
         * 可根据需要设置不同高度,使用屏幕还有其它点击热区的情况
         */
        // 获取屏幕信息
        // var systemInfo = WX.GetSystemInfoSync();
        // var canvasWith = (int)(systemInfo.screenWidth * systemInfo.pixelRatio);
        // var canvasHeight = (int)(systemInfo.screenHeight * systemInfo.pixelRatio);
        // var buttonHeight = (int)(canvasWith / 1080f * 300f);
        // 很容易被误导,与其叫按钮,不如叫热区
        // WXUserInfoButton btn = WX.CreateUserInfoButton(0, canvasHeight - buttonHeight, canvasWith, buttonHeight, "zh_CN", false);
        /**
         * 方法二:创建布满整个屏幕的授权按钮区
         */
        WXUserInfoButton btn = WX.CreateUserInfoButton(0, 0, Screen.width, Screen.height, "zh_CN", false);
        // 监听授权区域的点击
        btn.OnTap((res) =>
        {
            Debug.Log("click userinfo btn: " + JsonUtility.ToJson(res, true));
            if (res.errCode == 0)
            {
                // 用户已允许获取个人信息,返回的 res.userInfo 即为用户信息
                Debug.Log("userinfo: " + JsonUtility.ToJson(res.userInfo, true));
                // 将用户信息存入成员变量,以待后用
                this.userInfo = res.userInfo;
                // 展示,只是为了测试看到
                this.ShowUserInfo(res.userInfo.avatarUrl, res.userInfo.nickName);
            }
            else
            {
                Debug.Log("用户拒绝获取个人信息");
            }
            // 最后隐藏授权区域,防止阻塞游戏继续
            btn.Hide();
            Debug.Log("已隐藏热区");
        });
    }

    /// <summary>
    /// 调用Api获取用户信息
    /// </summary>
    private void GetUserInfo()
    {
        WX.GetUserInfo(new GetUserInfoOption()
        {
            lang = "zh_CN",
            success = (res) =>
            {
                Debug.Log("获取用户信息成功(API): " + JsonUtility.ToJson(res.userInfo, true));
                // 将用户信息存入成员变量,或存入云端,方便后续使用
                this.userInfo = this.ConvertUserInfo(res.userInfo);

                this.ShowUserInfo(res.userInfo.avatarUrl, res.userInfo.nickName);
            },
            fail = (err) =>
            {
                Debug.Log("获取用户信息失败(API): " + JsonUtility.ToJson(err, true));
            }
        });
    }

    /// <summary>
    /// 展示用户信息,对头像、昵称展示的整合
    /// ps: 测试用
    /// </summary>
    /// <param name="avatarUrl"></param>
    /// <param name="nickName"></param>
    private void ShowUserInfo(string avatarUrl, string nickName)
    {
        StartCoroutine(LoadAvatar(avatarUrl));
        showNickname(nickName);
    }

    /// <summary>
    /// 展示用户头像
    /// ps: 测试用
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    IEnumerator LoadAvatar(string url)
    {
        // 加载头像图片
        UnityWebRequest request = new UnityWebRequest(url);
        DownloadHandlerTexture texture = new DownloadHandlerTexture(true);
        request.downloadHandler = texture;
        yield return request.SendWebRequest();
        if (string.IsNullOrEmpty(request.error))
        {
            avatarTexture = texture.texture;
        }

        Sprite sprite = Sprite.Create(avatarTexture, new Rect(0, 0, avatarTexture.width, avatarTexture.height), new Vector2(0.5f, 0.5f));
        // 场景中图片对象名称为Avatar
        Image tempImage = GameObject.Find("Avatar").GetComponent<Image>();

        tempImage.sprite = sprite;
    }

    /// <summary>
    /// 展示用户昵称
    /// ps: 测试用
    /// </summary>
    /// <param name="name"></param>
    void showNickname(string name)
    {
    	// 场景中文本对象名称为Nickname
        Text nickname = GameObject.Find("Nickname").GetComponent<Text>();
        nickname.text = name;
    }

    /// <summary>
    /// 将UserInfo对象转为WXUserInfo
    /// ps: 不知为何,相同结构要搞两个对象
    /// </summary>
    /// <param name="userInfo"></param>
    /// <returns></returns>
    WXUserInfo ConvertUserInfo(UserInfo userInfo)
    {
        return new WXUserInfo()
        {
            nickName = userInfo.nickName,
            avatarUrl = userInfo.avatarUrl,
            country = userInfo.country,
            province = userInfo.province,
            city = userInfo.city,
            language = userInfo.language,
            gender = (int)userInfo.gender
        };
    }
}

其实就是运行时候,会主动弹出一个是否允许授权的提示窗,还有一个是否允许获取个人信息的提示窗,都允许后,就可以获取到个人微信信息了。开始按钮只是方便切换到下一个界面。

这里需要注意的就是,如果需要获取用户OpenID(使用小游戏的唯一用户ID),需要先获取Code

//该函数在用户授权后调用就行
private void GetWXLoginCode()
{
    Debug.Log("****** GetCode ");
    LoginOption loginOption = new LoginOption();
    loginOption.complete = (e) => 
    {
        Debug.Log("****** e.complete " + e);
    };
    loginOption.success = ((e) =>
    {
        //成功获取到用户Code
        string code = e.code;
    });
    loginOption.fail = ((e) =>
    {
        Debug.Log("****** e.errMsg " + e.errMsg);
    });
    WX.Login(loginOption);
}

获取到Code后,最好通过后端服务器获取OpenID在进行返回,这样安全系数比较高。

//下面三个参数分别为 小游戏AppID,小程序秘钥,和以获取到的Code
string openIDURL = string.Format("https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code", WXAppID, WXSecret,WXCode);
HttpWebRequest req = HttpWebRequest.Create(new Uri(url)) as HttpWebRequest;
req.Method = WebRequestMethods.Http.Get;
req.Timeout = 3000;
HttpWebResponse res = req.GetResponse() as HttpWebResponse;
if (res.StatusCode == HttpStatusCode.OK)
{
    Stream downLoadStream = res.GetResponseStream();
    StreamReader reader = new StreamReader(downLoadStream);
    string json = reader.ReadToEnd();
    JObject jsonObject = JObject.Parse(json);
    //成功获取到用户OpenID
    string openid = jsonObject["openid"].ToString();
    res.Close();
    reader.Close();
    downLoadStream.Close();
}

注:小程序ID和秘钥,都可以在微信公众平台里面找到。

还有一点需要注意的是,如果需要头像下载到本地使用,把保存下来的头像地址,使用replace方法,把头像URL中的https://thirdwx.qlogo.cn,替换为https://wx.qlogo.cn,包括其他用户的头像地址,都是一样的操作,不然可能会出现头像下载不下来的问题,记得把https://wx.qlogo.cn这个域名添加了

UnityWebSocket链接服务器相关

这个基本就没啥可说的了,用导入时那个案例的代码进行修改就可以,需要注意的就是,微信小游戏只支持wss://IP+端口 方式链接,不过本地测试的话可以使用ws://

ws/wss类似http/https

屏幕触摸相关

可以使用Unity的Input.Touch(不过总感觉那里怪怪的,后面换成微信提供的触摸相关API了)

微信触摸API使用:wx.onTouchStart(function listener) | 微信开放文档

 //添加监听
 private void Input_ON()
 {
     WX.OnTouchStart(Input_WX_Start);
     WX.OnTouchMove(Input_WX_Move);
     WX.OnTouchEnd(Input_WX_End);
 }
 //移除监听
 private void Input_OffWX()
 {
     WX.OffTouchStart(Input_WX_Start);
     WX.OffTouchMove(Input_WX_Move);
     WX.OffTouchEnd(Input_WX_End);
 }

注:手指移动监听函数,不知道为什么,会出现掉帧的情况(包括Unity中的Input.Touch也是),就是进行手指移动距离检测的时候,会出现某一帧为0的情况,这个就根据实际情况修改一下代码逻辑。

打包相关

颜色空间要选为Gamme

选择生成并转换

这里需要注意的是:

游戏AppID就是你注册微信小游戏时候的ID

项目前期,只是进行测试的话,游戏资源可以本地加载,游戏资源CDN地址填写 http://本机的IP地址:8001,可以通过win+R,打开运行,输入cmd,在输入ipconfig查看自己的IP地址(V4)。

首包资源加载方式选择小游戏包内。还有就是,如果没有好友关系链的需求,可以取消勾选,如果有好友功能需求,运行时可能会出现报错,点击报错链接,会出现一个插件应用的提示,启用下就好了。

压缩首包资源,这个选项可以勾选,测试没问题,不过到后面如果需要把资源放到CDN上面,记得在Web服务器上添加请求文件类型规则(MIME),因为勾选压缩首包资源,打包出来的文件后缀是.br,如果不添加可访问的文件类型,会出现404找不到指定文件错误。

打包后,在导出路径下,会生成两个文件

minigame是上传到微信的,webgl后面可以传到CDN上面,如果是使用本地测试,可以将webgl放入到minigame文件夹里,方便在微信开发者工具里读取资源

然后打开微信开发者工具,导入刚刚打包的项目,选择minigame文件夹

查看微信开发者工具的本地资源地址:

微信开发者工具第一次运行时候,可能会报插件错误,直接点启用就好了

几个注意点

1、素材的分辨率,宽高是4次幂就行,压缩模式选择ASTC(开发者工具可能会报一堆提示,不用理会)

2、项目运行测试时,最好是把那个不校验合法域名的选项去掉勾选,这样能及时发现没有在微信公众平台中添加的域名,添加后,在微信开发者工具,详情-本地设置,刷新一下。

3、真机调试,如果没有启用代码分包的话,一般都会报代码包太大的错误。启用代码分包,在微信开发者工具,设置-扩展设置-找到wasmCodeSplit进行安装,安装后

这个图标就是代码分包

服务器搭建和部署:

配置:

需要先购买云服务器和域名,服务器大概一百左右一年,域名看自己喜欢,.top的域名一年才几块钱。域名的购买和备案都可以直接在华为云完成。

如果需要CDN服务,也可以顺便购买上,也是一百左右。

关于CDN。其实就是把你打包出来的那个webgl文件缓存在全国服务器上面。这样可以减轻源服务器的压力和带宽,也可以加速资源下载。源服务器就是你购买的云服务器。

说的更直白一些,就是首先在你的云服务器上部署一个web网页,只是这个网页用来下载文件,CDN就是帮你把原本需要从云服务器上下载的东西,缓存到全国各地的服务器上,所以,CDN地址你直接填云服务器地址都可以,只要你能承受云服务器的流量刷刷的就没了就行

注意事项:

服务器购买后,远程登录服务器的话,需要先配置安全组,以华为云为例,登录后点击控制台

然后在左侧列表中选择安全组,添加3389、80、443规则,其它规则按需添加即可。

域名看自己需求,一级域名备案后,可以在弄个二级域名,方便区分web和服务器应用。二级域名不需要备案

一级域名和二级域名都是直接在云解析服务 DNS中创建添加即可。

ssl证书一定要申请,因为很多地方都能用到。如果是华为云的话,直接搜索证书就出来了。申请后下载保存。

然后CDN的配置,如果要使用CDN,那么域名必须先备案,备案后可以添加域名,域名添加后,进行设置。

源站就是你的云服务器。看需要什么填写什么就行,如果需要https请求,就把你下载下来的证书,解压,找到后缀为LLS的那个文件,里面两个文件,一个是证书内容,一个是秘钥,在HTTPS设置里面进行复制就行。

域名添加完成后,会给你一个CNAME地址,把这个地址复制,然后再 云解析服务DNS中添加域名解析

添加成功后,返回CDN管理

这样就是添加并已开启。

注意:

CDN的域名,只需要添加这一个DNS解析就行。不需要再添加指向云服务器IP的解析,因为你在配置CDN的时候,那个源站地址已经填写过了。

Web域名DNS解析,可以直接指向云服务器的IP,也记得填一下!

服务器环境部署,可以使用宝塔配置,也可以直接下载Nginx和Apache部署,我使用的是Nginx,所以这里的是Nginx的配置

这里主要讲一下,如果对后端和服务器部署不是很了解的,可以把Web和后端服务器应用分开处理,这样相对好一些,我自己是单独下载一个Nginx用来反向代理服务器应用,然后宝塔使用IIS配置Web服务器(CDN),大佬请无视

宝塔的端口为8888,也记得在安全组中填一下

搭建:

Nginx配置

下载地址:nginx: download(Stable version(稳定版),下载稳定版就行)

安装后,在安装目录里面找到conf文件夹,里面有个nginx.conf的配置文件。

worker_processes auto;
error_log D:/nginx/logs/error.log;

events {
    worker_connections 1024;
}

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  D:/nginx/logs/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    default_type        application/octet-stream;

    server {
        listen 需要监听的端口号(就是从外网访问进来的端口) ssl;
        server_name 服务器域名;
        #下面这两行是配置https证书
        ssl_certificate    D:/nginx/cert/server.crt;
        ssl_certificate_key D:/nginx/cert/server.key;
        ssl_session_timeout 20m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_verify_client off;

        location / {
            proxy_pass http://本地服务器应用绑定的地址和端口号(使用127.0.0.1也行,或者可以查看自己的ip地址,使用本地地址,端口号为服务器绑定的端口,一般自己设置就行,记得别和别的端口冲突);
			
			
			
			
			
			proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Host $host;

            # 可选:如果需要传递客户端的真实IP给后端服务器
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # 禁用缓存
            proxy_no_cache 1;
            proxy_cache_bypass 1;
        }
    }
}

listen中填的端口号,记得在云服务器的安全组中填一下,后面的ssl代表是https访问方式,需要证书

配置完运行一下nginx.exe服务(如果没有设置开机自启动,每次重启都要手动运行)

注:这里的证书别搞混了,一级域名一个证书,二级域名也有证书。我这里是使用的二级域名代理后端服务器应用,所以证书是二级域名的证书,千万别搞混!!!

后端服务器开发(这里是C#的后端服务器代码,如果你的不是C#写的,可以问GTP4怎么实现,或者查查资料)

注意:后端服务器应用,绑定的地址,必须是http的。因为Nginx配置中就是http://开头。所以服务器URL绑定就需要使用http来监听绑定,不然外网请求没办法通过Nginx反向代理到服务器上

服务器代码:

internal class WebSocketServer_Server
{
    private readonly HttpListener _listener;
    private readonly ConcurrentDictionary<string, WebSocketClient> _clients;

    private string _url;
    public static int clientNum;
    public WebSocketServer_Server(string url)
    {
        _listener = new HttpListener();
        _listener.Prefixes.Add(url);
        _url = url;
        _clients = new ConcurrentDictionary<string, WebSocketClient>();
    }

    // 启动WebSocket服务器
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _listener.Start();
        while (!cancellationToken.IsCancellationRequested)
        {
            var context = await _listener.GetContextAsync();
            if (context.Request.IsWebSocketRequest)
            {
                await HandleWebSocketRequestAsync(context, cancellationToken);
            }
            else
            {
                context.Response.StatusCode = 400; // 不是WebSocket请求
                context.Response.Close();
            }
        }
    }

    // 处理 WebSocket 请求
    private async Task HandleWebSocketRequestAsync(HttpListenerContext context, CancellationToken cancellationToken)
    {
        WebSocket webSocket = null;
        WebSocketClient client = null;

        try
        {
            // 接受 WebSocket 连接
            WebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
            webSocket = webSocketContext.WebSocket;

            // 创建客户端对象并加入管理列表
            client = new WebSocketClient(webSocket);
            _clients.TryAdd(client.ClientId, client);
            Console.WriteLine($"Client {client.ClientId} connected.");
            ++clientNum;
            // 持续读取客户端发来的消息
            _=client.ListenForMessagesAsync(cancellationToken);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error handling WebSocket request: {ex.Message}");
        }
    }

    // 停止WebSocket服务器
    public void Stop()
    {
        _listener.Stop();
        Console.WriteLine("WebSocket server stopped.");
    }
}

WebSocketClient客户端接入代码:

public class WebSocketClient
{
    private static int _nextClientId = 0;
    private readonly WebSocket _webSocket;
    private readonly string _clientId;
    private readonly CancellationTokenSource _cancellationTokenSource;
    private int cacheNum = 0;
    private byte[] cacheBytes = new byte[1024];
    private PlayerWXData playerWXData;
    public string ClientId => _clientId;

    public WebSocketClient(WebSocket webSocket)
    {
        _webSocket = webSocket;
        _clientId = (++_nextClientId).ToString();
        _cancellationTokenSource = new CancellationTokenSource();
    }

    // 向客户端发送消息
    public async Task SendMessageAsync(string message)
    {
        if (_webSocket.State == WebSocketState.Open)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(message);
            await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
        }
    }
    public async Task SendMessageAsync(byte[] message)
    {
        if (_webSocket.State == WebSocketState.Open)
        {
            await _webSocket.SendAsync(new ArraySegment<byte>(message), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token);
        }
    }

    // 监听客户端发送的消息
    public async Task ListenForMessagesAsync(CancellationToken cancellationToken)
    {
        byte[] buffer = new byte[1024];
        try
        {
            while (_webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
            {
                var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);

                if (result.MessageType == WebSocketMessageType.Binary)
                {
                    //string receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
                    //Console.WriteLine($"Client {ClientId} sent: {receivedMessage}");
                    //// 处理接收到的消息,示例中是回声响应
                    //await SendMessageAsync($"Echo: {receivedMessage}");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error with client {ClientId}: {ex.Message}");
        }
        finally
        {
            await CloseConnectionAsync();
        }
    }



    // 处理客户端连接的关闭
    public async Task CloseConnectionAsync()
    {
        await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client disconnected", _cancellationTokenSource.Token);
        _webSocket.Dispose();

    }
}

开启服务:

  //开始服务
  private void button1_Click(object sender, EventArgs e)
  {
      //IP = "http://127.0.0.1:11111/";
      var server = new WebSocketServer_Server(IP);
      cts = new CancellationTokenSource();
      // 启动 WebSocket 服务器
      Ranking_RedisServer.Instance.OpenRedisServer();
      var serverTask = server.StartAsync(cts.Token);
  }

宝塔配置Web(windows系统)

下载地址:宝塔面板下载,免费全能的服务器运维软件

可以本地下载,然后复制到云服务器上进行安装。

云服务器上安装完成后(会默认生成一个用户名和密码,记得先保存)

复制面板地址,然后再自己的电脑上打开链接,输入云服务器安装宝塔后给的用户和密码,然后登录,登录后会有一个提示安装插件的面板,直接关了就行,什么都别安装!

面板左侧,点击网站,选择PHP项目,添加站点,如果弹出选择安装服务,就选择LLS,安装完成后,应该会自动创建一个默认站点,不想用就直接删除就好,然后自己添加个站点,域名就是留给web使用的域名(别和CDN的域名搞混了),目录自己选择,别的不用管,直接创建就行,创建完站点后,有个设置填一下

这里记得填了,这里是上面说的,如果打包的时候,需要压缩首包资源,那么这里必须填写!

填完后重启一下lls服务,然后点击站点名称,在打开的面板中找到ssl,把之前下载的证书配置进去(别搞错证书,这里的是web域名的证书)

剩下的就是测试网站是否正常打开(默认打开后应该会看见Nginx的欢迎,如果看见这个提示就证明网站配置没问题)

注:如果打开网页失败,看看自己宝塔是不是安装了别的什么软件,打开云服务器,查看站点目录是否存在,里面是否有内容。仔细检查下。还有就是域名的DNS解析看看加没加。

都没问题后,宝塔就可以关了,后续也基本用不到了。

打开云服务器,点开站点的文件夹。除了web.config、.htaccess、web_config。这三个文件,别的都可以删了。这三个文件不知道有没有用,我没删,你们可以测试一下,最多就是把站点删了重新创建。

后面在打包,就是那个webgl文件夹,把里面的的5个文件复制到这个目录里面就行了。

不要复制webgl文件夹,点进去,复制里面的文件!

至此结束!个人开发,每一步都是一点一点扣出来的,希望能帮到大家,加油吧!!!

如果有遗漏,可以留言,我再补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清酒词话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值