一、说明
1、本文实验内容所涉及的开发环境说明:
- win11
- VisualStudio2022(.Net6.0)
- Unity2021.3.40
2、本文参考资料
【1】NativeWebSocketUnity包:
https://github.com/endel/NativeWebSocket
【2】asp.net core架设websocket国外小哥案例:
https://medium.com/bina-nusantara-it-division/implementing-websocket-client-and-server-on-asp-net-core-6-0-c-4fbda11dbceb
【3】感谢自动纺织机:ChatGPT/Claude
二、本文要实现的测试内容
- 1、实现一个ASP.NET CORE服务端,它上面跑着一个websocket服务
- 2、在【控制台应用程序】中与服务器websocket通信
- 3、在【Unity程序】中与服务器websocket通信
三、测试效果截图
1、服务端截图
有一个用户连入时,显示简单信息
2、控制台应用程序截图
连接成功后,提示。
服务器一旦有用户连入,广播用户信息,广播总用户数
3、Unity程序截图
连接成功后,提示。
服务器一旦有用户连入,广播用户信息,广播总用户数
四、服务端创建
1、打开vs2022 -> 【创建新项目】
2、选择【ASP.NET CORE 空】项目模版
3、配置新项目
设置完毕点击【下一步】
4、 其他信息
譬如设置框架
设置完毕,点击【创建】
5、编写服务端逻辑
服务端代码
using System.Diagnostics;
using System.Net;
using System.Net.WebSockets;
using System.Text;var builder = WebApplication.CreateBuilder(args); //
var connections = new List<WebSocket>(); //连接对象列表//builder.WebHost.UseUrls("http://localhost:6969");
builder.WebHost.UseUrls("http://192.168.0.173:6969");var app = builder.Build(); //构建web应用程序实例
app.UseWebSockets(); //启用websocket支持app.Map("/ws", async context => //app.Map() 把某类网络请求与某个处理函数关联起来:当用户请求的是【/ws】时,服务器响应后面的兰姆达方法
{/**************************每个ws连接请求发生的时候,服务器都会调用该处理流程 这是一个并发流程,每一个ws请求都会经过它的处理**************************/if (context.WebSockets.IsWebSocketRequest){var curName = context.Request.Query["id"];//有用户连入var id = context.Connection.Id;var ip = context.Connection.RemoteIpAddress;Debug.WriteLine($"有用户连入:用户名为[{curName}]【Id= [{context.Connection.Id}] ip = [{context.Connection.RemoteIpAddress}]】");using var ws = await context.WebSockets.AcceptWebSocketAsync();connections.Add(ws); //添加连接的用户//广播信息await Broadcast($"有用户连入:id = [{id}],ip = [{ip}]");await Broadcast($"当前连接数量:{connections.Count}");await ReceiveMessage(ws,async (result, buffer) =>{if (result.MessageType == WebSocketMessageType.Text) //UTF8 文本信息{string message = Encoding.UTF8.GetString(buffer, 0, result.Count);await Broadcast(ip + ":" + message);}else if (result.MessageType == WebSocketMessageType.Close || ws.State == WebSocketState.Aborted) //连接关闭{connections.Remove(ws);await Broadcast($"{ip}-{id}用户掉线了");await Broadcast($"当前用户连接数为{connections.Count}");await ws.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);}});}else{context.Response.StatusCode = (int)HttpStatusCode.BadRequest;}
});
await app.RunAsync();//接收消息
async Task ReceiveMessage(WebSocket socket, Action<WebSocketReceiveResult, byte[]> handleMessage)
{var buffer = new byte[4096];while (socket.State == WebSocketState.Open){var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);handleMessage(result, buffer);}
}//广播消息
async Task Broadcast(string message)
{var bytes = Encoding.UTF8.GetBytes(message);foreach (var socket in connections){if (socket.State == WebSocketState.Open){var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length);await socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);}}
}
五、控制台客户端创建
1、创建新项目
2、选择【C#控制台应用】
3、配置新项目
4、设置.NET框架
5、编辑客户端逻辑
完整代码
// See https://aka.ms/new-console-template for more informationusing System.Net.WebSockets;
using System.Text;string name;
while (true)
{Console.Write("请输入名字:");name = Console.ReadLine();break;
}var ws = new ClientWebSocket();Console.WriteLine("开始连接服务器!");
//await ws.ConnectAsync(new Uri("ws://localhost:6969/ws"),CancellationToken.None);
await ws.ConnectAsync(new Uri("ws://192.168.0.173:6969/ws"), CancellationToken.None);Console.WriteLine("连接成功!");var receiveTask = Task.Run(async () =>
{var buffer = new byte[1024];while (true){var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer),CancellationToken.None);if(result.MessageType == WebSocketMessageType.Close) { break; }var message = Encoding.UTF8.GetString(buffer, 0, result.Count);Console.WriteLine("收到信息:" + message);}
});var sendTask = Task.Run(async () =>
{while (true){var message =Console.ReadLine();if(message == "exit"){break;}var bytes = Encoding.UTF8.GetBytes(message);await ws.SendAsync(new ArraySegment<byte>(bytes),WebSocketMessageType.Text,true,CancellationToken.None);}
});//发送或接收
await Task.WhenAny(receiveTask, sendTask);
if (ws.State == WebSocketState.Closed)
{await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}//发送和接收
await Task.WhenAll(receiveTask, sendTask);
六、Unity客户端创建
1、UI设计
2、层级关系
3、行为脚本清单
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using NativeWebSocket;
using System;
using Cysharp.Threading.Tasks;public class ConnectTest : MonoBehaviour
{/// <summary>/// IP地址/// </summary>public TMP_InputField ipf_ip;/// <summary>/// 端口号/// </summary>public TMP_InputField ipf_port;/// <summary>/// 连接服务器btn/// </summary>public Button btn_connect;/// <summary>/// 提示信息/// </summary>public TMP_Text text_info;private WebSocket websocket;// Start is called before the first frame updatevoid Start(){//连接服务器-按钮-逻辑btn_connect.onClick.AddListener(async () =>{var ip = ipf_ip.text;var port = ipf_port.text; try{websocket = new WebSocket($"ws://{ip}:{port}/ws");Debug.Log($"开始连接服务——ws://{ip}:{port}/ws");text_info.text += $"\n 开始连接服务——ws://{ip}:{port}/ws \n";//连接打开时...websocket.OnOpen += () =>{text_info.text += "连上服务器\n";};//连接出错时...websocket.OnError += (e) =>{text_info.text += $"连接出错:{e}\n";};//连接关闭时...websocket.OnClose += (e) =>{text_info.text += $"连接关闭:{e}\n";};//收到二进制数据时...websocket.OnMessage += (bytes) =>{var message = System.Text.Encoding.UTF8.GetString(bytes); text_info.text += $"收到消息: {message} \n";};//开始连接await websocket.Connect(); }catch (Exception e){Debug.Log($"连接服务器出错:{e.Message}");text_info.text = $"连接服务器出错:{e.Message}"; }});}void Update(){
#if !UNITY_WEBGL || UNITY_EDITORwebsocket.DispatchMessageQueue();
#endif}}
至此,全部菜上齐!
后面会进行ASP.NET CORE 、websocket原理、应用和代码的讲解