实现客户端与服务器进行WebSocket通讯(简易)
简介
- 客户端使用的是Layabox,语言为TypeScript,利用Laya封装好的webSocket进行网络通信。
- 服务端使用的是C#简单控制台应用,利用Fleck第三方库封装好的WebSocketServer进行网络通信。
项目效果
客户端代码
-
SocketTest 类,封装LayaSocket ,并且暴露出对应的接口操作。
-
WsTestView 类,对应UI界面的逻辑处理
-
WsTestItem 类,消息队列中对应的文本
-
EventMgr 类,事件管理器。
import { EventMgr } from "./EventMgr"; /** Socket辅助类 */ export default class SocketTest { /** 单例 */ private static instance: SocketTest; /** socket实例 */ private socket: Laya.Socket; /** 消息列表 */ private msgList: { key: number, value: string }[] /** 消息列表最大长度 */ private msgMaxCount: number = 100; /** 服务器地址 */ private host: string; /** 服务器端口 */ private port: number; /** 是否连接 */ private isConnect: boolean = false; constructor() { // 懒汉式实例 if (this.socket == null) { this.socket = new Laya.Socket(); } if (!this.msgList) { this.msgList = []; } // 监听事件 this.socket.on(Laya.Event.OPEN, this, this.onOpenHandler); this.socket.on(Laya.Event.CLOSE, this, this.onCloseHandler); this.socket.on(Laya.Event.MESSAGE, this, this.onMessageHandler); this.socket.on(Laya.Event.ERROR, this, this.onErrorHandler); } /** * 监听服务器连接成功 * @param event 事件 */ private onOpenHandler(event: any) { Laya.LocalStorage.setItem("socket", `ws://${this.host}:${this.port}`); this.isConnect = true; this.saveMsg("连接服务器成功"); } /** * 监听服务器连接断开 */ private onCloseHandler() { this.saveMsg("连接已断开!"); this.isConnect = false; } /** * 监听服务器消息 * @param msg 消息 */ private onMessageHandler(msg) { this.saveMsg(msg, 1); } /** * 监听服务器错误 * @param e 错误信息 */ private onErrorHandler(e) { this.isConnect = false; this.saveMsg("通信错误"); } /** * 保存消息 * @param msg 消息 * @param key 1:服务器 2:客户端 */ private saveMsg(msg: string, key?: number) { if (this.msgList) { let value = key ? ((key == 2 ? "客户端:" : "服务器:") + msg) : msg; var msgItem = { key, value } if (this.msgList.length < this.msgMaxCount) { this.msgList.push(msgItem); } else { this.msgList.shift(); this.msgList.push(msgItem); } EventMgr.dispatchEvent("updateMsgList", this.msgList); } } /** * 连接 * @param url 地址 */ public connect(url: string) { if (this.validateAddress(url)) { const regex = /^wss?:\/\/([^:/\s]+)(?::(\d+))?/; const match = url.match(regex); if (match) { this.host = match[1]; this.port = match[2] ? parseInt(match[2]) : 0; this.socket.connect(this.host, this.port); } } else { alert("地址不合法"); } } /** * 关闭 */ public close() { this.socket.close(); this.isConnect = false; } /** * 发送 */ public send(msg: string) { if (msg.length > 150) { console.error("输入内容不可超过150个字符"); return; } if (this.msgList) { this.socket.send(msg); this.saveMsg(msg, 2); } } /** * 获取实例 */ public static getInstance() { if (SocketTest.instance == null) { SocketTest.instance = new SocketTest(); } return SocketTest.instance; } /** * 获取消息列表 */ public getMsgList() { let arr = Array.from(this.msgList); return arr; } /** * 验证地址 */ public validateAddress(url: string): boolean { const regex = /^wss?:\/\/[^\s/$.?#].[^\s]*$/; return regex.test(url); } /** * 是否连接 */ public isConnecting() { return this.isConnect; } /** * 清空消息 */ public clear() { this.msgList = []; } }
import { ui } from "../ui/layaMaxUI";
import { EventMgr } from "./EventMgr";
import SocketTest from "./SocketTest";
import WsTestItem from "./WsTestItem";
/** socket测试界面 */
export default class WsTestView extends ui.WsTestViewUI {
/** socket */
private socketTest: SocketTest;
/** 消息列表 */
private msgList: { key: number, value: string }[]
constructor() {
super();
}
onAwake(): void {
this.btn_connection.on(Laya.Event.CLICK, this, this.onConnection);
this.btn_send.on(Laya.Event.CLICK, this, this.onSend);
this.btn_close.on(Laya.Event.CLICK, this, this.onClose);
this.btn_clear.on(Laya.Event.CLICK, this, this.onClear);
this.list_msgs.selectEnable = true;
this.list_msgs.vScrollBarSkin = "";
this.list_msgs.itemRender = WsTestItem;
this.list_msgs.renderHandler = new Laya.Handler(this, this.updateItem);
this.list_msgs.array = this.msgList;
EventMgr.addEvent("updateMsgList", this.updateMsgList, this);
}
onEnable(): void {
let url = Laya.LocalStorage.getItem("socket");
if (url) {
this.input_link.text = url;
}
}
/** 连接 */
private onConnection() {
this.socketTest = SocketTest.getInstance();
this.msgList = this.socketTest.getMsgList();
let url = this.input_link.text;
this.socketTest.connect(url);
}
/** 发送 */
private onSend() {
if (!this.socketTest || !this.socketTest.isConnecting) return;
if (this.input_msg.text.length == 0) return;
this.socketTest.send(this.input_msg.text);
}
/** 关闭 */
private onClose() {
if (!this.socketTest.isConnecting) return;
this.socketTest.close();
this.clear();
}
/** 清空 */
private onClear() {
this.clear();
this.updateMsgList();
}
/** 更新消息队列 */
private updateMsgList(data: { key: number, value: string }[] = []) {
this.btn_connection.disabled = this.socketTest.isConnecting();
this.list_msgs.array = data;
this.list_msgs.scrollTo(data.length - 1);
}
/** 刷新数组itemUI */
private updateItem(cell: WsTestItem, index: number): void {
cell.setLabel(this.list_msgs.array[index]);
}
/** 清空 */
private clear() {
this.msgList = null;
this.socketTest.clear();
this.btn_connection.disabled = this.socketTest.isConnecting();
}
}
import { ui } from "../ui/layaMaxUI";
/** socket测试item类 */
export default class WsTestItem extends ui.WsTestItemUI {
constructor() {
super();
}
/** 设置文本 */
public setLabel(data: { key: number, value: string }) {
this.lab.text = data.value;
this.lab.color = this.getColor(data.key);
}
/** 获取颜色 */
private getColor(key: number): string {
if (key) {
return key == 2 ? "#0000ff" : "#ff0000";
} else {
return "#000000";
}
}
}
export class EventMgr {
private static eventList: any = {};
/**
* 添加事件侦听
* type 事件ID
* callback 事件处理
* thisObject 对象
*/
public static addEvent(type: number | string, callback: Function, thisObject: Object): void {
if (this.eventList[type] == null) {
this.eventList[type] = [];
}
// let num = this.eventList[type].length;
if (!this.hasEvent(type, callback, thisObject)) {
this.eventList[type].push({ "callback": callback, "thisObject": thisObject });
}
}
/**
* 删除静态对象使用 其他对象使用(removeEventByObject)
* 【注意】 同一个类多个实例 回调函数值相同 (移除会移除监听列表 位置0的回调) 导致难以发现的bug
* 所以只用做静态对象的移除(静态对象 通常只会调用一次监听)
* 删除事件侦听
*/
public static removeStaticEvent(type: number | string, callback: Function): void {
let events: { callback: Function, thisObject: Object }[] = this.eventList[type];
if (events != null) {
for (let i = events.length - 1; i >= 0; i--) {
let evt = events[i];
if (evt["callback"] == callback) {
events.splice(i, 1);
}
}
if (events.length == 0)
delete this.eventList[type]
}
}
/**
* 删除事件侦听
*/
public static removeEventByObject(type: number | string, thisObject: any): void {
let events: { callback: Function, thisObject: any }[] = this.eventList[type];
if (events != null) {
for (let i = events.length - 1; i >= 0; i--) {
let evt = events[i];
if (evt["thisObject"] == thisObject) {
events.splice(i, 1);
}
}
if (events.length == 0)
delete this.eventList[type]
}
}
/**
* 派发事件
* type 事件ID
* data 自定义数据
*/
public static dispatchEvent(type: number | string, data: any = null) {
let events = this.eventList[type];
if (events) {
let num: number = events.length;
for (let i: number = 0; i < num; i++) {
let evt = events[i];
if (evt) {
let callback: Function = <Function>evt["callback"];
let obj: Object = evt["thisObject"];
callback.apply(obj, [data]);
}
}
}
}
/**
* 判断EventMgr是否有该事件的某个回调函数S
* type 事件ID
* callback 回调函数
*/
public static hasEvent(type: number | string, callback: Function, thisObject: any): boolean {
let events = this.eventList[type];
if (events != null) {
let num: number = events.length;
for (let i: number = 0; i < num; i++) {
let evt = events[i];
let cb: Function = <Function>evt["callback"];
let obj: any = evt["thisObject"];
if (cb == callback) {
if (obj && thisObject && obj == thisObject)
return true;
}
}
}
return false;
}
}
服务端代码
- WebSocket 类,封装WebSocketServer,同时暴露一些基础接口给外部调用。
- Program 类,项目主入口。
using Fleck;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace webSocket
{
public class WebSocket
{
/// <summary>
/// socket连接池
/// </summary>
private List<IWebSocketConnection> connectSocketPool;
/// <summary>
/// WebSocketServer 对应实例
/// </summary>
private WebSocketServer server;
/// <summary>
/// 连接地址
/// </summary>
private const string wsLink = "ws://127.0.0.1:9999";
/// <summary>
/// 消息队列
/// </summary>
private List<KeyValuePair<string, string>> msgListPool;
/// <summary>
/// 最大消息队列数
/// </summary>
private int maxMsgCount;
/// <summary>
/// 连接器
/// </summary>
private IWebSocketConnection connection;
public WebSocket()
{
connectSocketPool = new List<IWebSocketConnection>();
server = new WebSocketServer(wsLink);
msgListPool = new List<KeyValuePair<string, string>>();
maxMsgCount = 100;
}
public void StartSocket()
{
if (server == null)
{
server = new WebSocketServer(wsLink);
}
server.Start(socket =>
{
this.connection = socket;
this.connection.OnOpen = () =>
{
Console.WriteLine("开启服务");
connectSocketPool.Add(this.connection);
};
this.connection.OnClose = () =>
{
Console.WriteLine("关闭服务");
this.connection.Close();
connectSocketPool.Remove(this.connection);
};
this.connection.OnMessage = msg =>
{
Console.WriteLine(msg);
if (msgListPool != null)
{
var time = new DateTime();
if (msgListPool.Count > maxMsgCount)
msgListPool.RemoveAt(0);
msgListPool.Add(new KeyValuePair<string, string>(time.ToString(), msg));
}
this.connection.Send($"这是接收到的消息{msg},这是返回的消息{"=========" + msg}");
};
});
}
public void StopSocket()
{
if (server != null && this.connection != null)
{
this.connection.Close();
connectSocketPool.Remove(connection);
msgListPool = null;
}
}
}
}
using Fleck;
namespace webSocket
{
internal class Program
{
static void Main(string[] args)
{
var ws = new WebSocket();
ws.StartSocket();
Console.WriteLine("服务已开启,如需关闭请按下任意键...");
Console.ReadLine();
ws.StopSocket();
}
}
}