C#与Nodejs的WebSocket通讯案例——WebSocket4Net的使用

/ C# / 没有评论 / 1438浏览

C#与Nodejs的WebSocket通讯案例——WebSocket4Net的使用

前言

学了一学期的C#,最后老师要求做一个简单的贪吃蛇,为了实现登录注册这种功能,于是我想要去做一个WebScoket通信的功能。

WebSocket通讯

C#

C#客户端这边我们用了一个非常便捷的第三方包——WebSocket4Net。我这边使用的vs直接使用Nuget包管理工具去下载安装即可。官方的说明非常短,只有一点.正如它提供的Demo,我们来简单解读一下

//引入命名空间
using WebSocket4Net;
//实例化WebSocket对象,指定连接地址
WebSocket websocket = new WebSocket("ws://localhost:2012/");
//给对应的情况委托绑定方法
websocket.Opened += new EventHandler(websocket_Opened);
websocket.Error += new EventHandler<ErrorEventArgs>(websocket_Error);
websocket.Closed += new EventHandler(websocket_Closed);
websocket.MessageReceived += new EventHandler(websocket_MessageReceived);
//建立连接
websocket.Open();

private void websocket_Opened(object sender, EventArgs e){
    //发送数据
    websocket.Send("Hello World!");
}

node js

回到nodejs服务端,我们通过npm包管理工具来安装

npm install nodejs-websocket

然后就可以通过事件监听来建立监听,例如:

var ws = require('nodejs-websocket');
var server = ws.createServer(function(conn){
    console.log("New connection")
    //监听文本发送事件
    conn.on("text", function (str) {
        console.log("Received "+str)
        conn.sendText(str.toUpperCase()+"!!!")
    })
    conn.on("error",function(code,reason){
        console.log("Connection error");
    })
    //监听连接关闭事件
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
    })
}).listen(3030); 

这样,就建立起来了监听。

通信测试

客户端

最近在写一个贪吃蛇的通信功能,现在在实现一个登陆功能的通信,这边来贴一下关键代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Big.Manager
{
    /// <summary>
    /// 单例模式老父亲(此单例模式不可应用于多线程情况)
    /// </summary>
    class Singleton<T> where T : new ()
    {
        private static T _instance ;
        public static T GetInstance()
        {
            if (_instance == null)
            {
                _instance = new T();
            }
            return _instance;
        }
    }
}
class WebSocketManager:Singleton<WebSocketManager>{
    private WebSocket _socket;
    private string _address = "ws://localhost:3030/";

    public WebSocketManager() {
        _socket = new WebSocket(_address);
        //开启连接
        _socket.Open();
    }
    public void Send(string key,string message)
    {
        _socket.Send(key+":"+message);
    }
}
class LoginManager : Singleton<LoginManager>{
    //登录通信规则:空格为间隔区别: key 用户名 密码
    public void Login(string username,string password) {
        WebSocketManager.GetInstance().Send("login",username+" "+password);
    }
}
private void LoginBtn_Click(object sender, EventArgs e){
    String username = UsernameText.Text;
    String password = PasswordText.Text;

    LoginManager.GetInstance().Login(username,password);
}

这里记录一下一个比较有趣的前端输入拦截,在Winform的TextBox中,右下角有个事件,给它添加一个事件。

private void UsernameText_KeyPress(object sender, KeyPressEventArgs e)
{
    int textLimit = 6;
    if (UsernameText.TextLength < textLimit || (e.KeyChar == 8))
    {
        //ASCII的8意为着退格
        if ((e.KeyChar >= '0' && e.KeyChar <= '9') 
            || (e.KeyChar >= 'A' && e.KeyChar <= 'Z') 
            || (e.KeyChar >= 'a' && e.KeyChar <= 'z') 
            || (e.KeyChar == 8) || (e.KeyChar == '_'))
        {
            e.Handled = false;
        }
        else
        {
            MessageBox.Show(" 用户名只能为字母、数字和下划线! ");
            e.Handled = true;
        }
    }
    else
    {
        MessageBox.Show(" 用户名长度不能超过" + textLimit + "个字符 ");
        e.Handled = true;
    }
}

服务端

var ws = require('nodejs-websocket');
var server = ws.createServer(function(conn){
    //监听文本发送事件
    conn.on("text", function (str) {
        //将收到的信息提取key
        var key=str.split(":");
        //数据传输标准:key:信息
        //通过:分隔从而得到key
        switch(key[0]){
            case 'login':
                console.log("登录信息");
                break;
            default:
                console.log("the key not match");
                break;
        }
        conn.sendText(str.toUpperCase()+"!!!")
    })
    conn.on("error",function(code,reason){
        console.log("Connection error");
    })
    //监听连接关闭事件
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
    })
}).listen(3030); 

效果

关于响应信息的接收我们在下面的贪吃蛇登录示例中应用。

与C#交互示例

登录通信

承接上例,我们来实现真正一个项目的登录效果:

服务端

下面是我们Nodejs的示例代码——index.js:

var ws = require('nodejs-websocket');
var LoginModule = require('./loginModule.js'); 

var server = ws.createServer(function(conn){
    //监听文本发送事件
    conn.on("text", function (str) {
        //将收到的信息提取key
        var key=str.split(":");
        //数据传输标准:key:信息
        //通过:分隔从而得到key
        switch(key[0]){
            case 'login':
                //登录处理
                new LoginModule().LoginHandle(conn,key[1]);
                break;
            default:
                console.log("the key not match");
                break;
        }
    })
    conn.on("error",function(code,reason){
        console.log("Connection error");
    })
    //监听连接关闭事件
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
    })
}).listen(3030); 

我们将登录的处理封装到了一个Module之中——loginModule.js

//将登陆处理封装到该Login模块中
function Login() { 
    //userMess的前段为用户名、后段为密码
    //解析用户信息,得到用户名和密码 
    this.parseUserMess = function(userMess){
        var mess=userMess.split(" ");
        var obj={
            username:mess[0],
            password:mess[1]
        }
        return obj;
    } 
    //登录测试,得到登录结果
    this.LoginResult = function(userMess) {
        var obj=this.parseUserMess(userMess);
        var username=obj.username;
        var password=obj.password;
        if(username=="batman"&&password=="123456"){
            return true;
        }else{
            return false;
        }
    }; 
    //登录处理,验证登录且进行相应的通信处理
    this.LoginHandle=function(conn,userMess){
        //登陆成功
        if(this.LoginResult(userMess)){
            console.log("用户登录成功");
            conn.send("login:success");
        }else{
            console.log("用户登录失败");
            conn.send("login:failed");
        }
    }
}; 
module.exports = Login;

这样我们就完成了服务端的登录检测逻辑,当然,这里是固定了写法,检测用户名和密码为:batman、123456,后期可以根据需要连接数据库来进行处理。

客户端

我们这里实现了一个单例模式,作为总管理的基类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Big.Manager
{
    /// <summary>
    /// 单例模式老父亲(此单例模式不可应用于多线程情况)
    /// </summary>
    class Singleton<T> where T : new ()
    {
        private static T _instance ;
        public static T GetInstance()
        {
            if (_instance == null)
            {
                _instance = new T();
            }
            return _instance;
        }
    }
}

然后设置了一个WebSocket的总管理:

using System;
using WebSocket4Net;

using System.Collections.Generic;

namespace Big.Manager
{
    /// <summary>
    /// WebSocket通信管理总类(单例)
    /// </summary>
    class WebSocketManager:Singleton<WebSocketManager>
    {
        #region 通信配置属性
        private WebSocket _socket;
        private string _address = "ws://localhost:3030/";
        #endregion
        #region 响应事件属性
        //value对应的是监听这个事件对应的委托方法们(重点圈住:们)
        private Dictionary<string, Action<string>> eventDic
            = new Dictionary<string, Action<string>>();
        #endregion
        #region 初始化和连接
        public WebSocketManager() {
            _socket = new WebSocket(_address);
            //开启连接
            _socket.Open();

            //绑定方法
            this._socket.MessageReceived += WebSocket_MessageReceived;

        }
        #endregion
        #region 发送信息
        public void Send(string key,string message)
        {
            if (_socket != null && _socket.State == WebSocket4Net.WebSocketState.Open)
            {
                _socket.Send(key + ":" + message);
            }
        }
        #endregion
        #region 接收消息
        /// <summary>
        /// 消息收到总事件分发方法
        /// </summary>
        void WebSocket_MessageReceived(object o,MessageReceivedEventArgs e)
        {
            //得到回传数据的key
            string key=e.Message.Split(':')[0];
            //字典中存在相应的key对应的委托记录
            if (eventDic.ContainsKey(key))
            {
                //调用委托,将传回的参数传过去
                eventDic[key](e.Message.Split(':')[1]);
            }
            //没有对应分发处理的情况
            else
            {

            }
        }
        /// <summary>
        /// 添加事件监听
        /// </summary>
        public void AddEventListener(string key,Action<string> action) {
            //有没有对应的事件监听
            //有的情况
            if (eventDic.ContainsKey(key))
            {
                eventDic[key] += action;
            }
            //没有的情况
            else
            {
                eventDic.Add(key, new Action<string>(action));
            }
        }
        /// <summary>
        /// 移除字典中的事件监听
        /// </summary>
        public void RemoveEventListener(string key, Action<string> action)
        {
            if (eventDic.ContainsKey(key))
            {
                //移除这个委托
                eventDic[key] -= action;
            }
        }

        /// <summary>
        /// 清空所有事件监听(主要用在切换场景时)
        /// </summary>
        public void Clear()
        {
            eventDic.Clear();
        }
        #endregion
    }
}

然后我们在form的登录提交按钮对应的响应事件中这样写道:

private void LoginBtn_Click(object sender, EventArgs e)
{
    String username = UsernameText.Text;
    String password = PasswordText.Text;

    LoginManager.GetInstance().Login(username,password);
}

调用到的这个LoginManager中这样处理:

using Big.common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;
using log4net.Config;

[assembly: XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]
namespace Big.Manager{
    /// <summary>
    /// 登录、注册管理
    /// </summary>
    class LoginManager : Singleton<LoginManager>
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(LoginManager));
        public LoginManager() {
            //给webscoket管理器添加事件绑定
            WebSocketManager.GetInstance().
                AddEventListener(WebSocketKey.Login.ToString(),LoginHandle);
        }
        
        ~LoginManager() {
            WebSocketManager.GetInstance().
                RemoveEventListener(WebSocketKey.Login.ToString(), LoginHandle);
        }

        //登录通信规则:空格为间隔区别: key:用户名 密码
        public void Login(string username,string password) {
            WebSocketManager.GetInstance().Send(WebSocketKey.Login,username+" "+password);
        }
        
        /// <summary>
        /// 登录回调处理
        /// </summary>
        /// <param name="Mess">回调参数</param>
        private void LoginHandle(string Mess) {
            if (Mess.Equals("success")) {
                log.Info("登录成功!");
            }
            else if (Mess.Equals("failed")) {
                log.Info("登录失败!");
            }
        }
    }
}

这里我们进行了日志输出,用到了一个.Net这边的组件,叫做:log4net。如果你不了解,可以参考这篇文章,使用起来非常简单。好了,接下来我们来测试一下。

测试结果