前言

最近公司有个项目需要用C/S架构的桌面应用程序与B/S架构的网页程序进行通信做数据的交互功能。在网上查了一下资料,发现 Fleck 实现一个WebSocket服务竟然如此简单明了,于是在此记录和整理了一下 Fleck 实现WebSocket服务的简单应用,希望对你有所帮助。

简介

Fleck 是一个用C#编写的轻量级WebSocket服务器库,它易于使用且高性能,同时保持代码的简洁性。

特点:

  • 无需继承:Fleck不需要你继承任何类,也不需要依赖于容器或额外的引用。

  • 无依赖:Fleck不依赖于HttpListener​HTTP.sys​,这意味着它可以在Windows 7和Server 2008主机上工作。

  • 跨平台:由于不依赖于HttpListener​,Fleck可以在非Windows平台上运行。

使用

安装Fleck

通过NuGet包管理器安装Fleck库

Install-Package Fleck

创建WebSocket服务器

以下是一个简单的WebSocket服务器示例:

using Fleck;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace WebSocketServiceDemo
{
    public class WebSocketService
    {
        /// <summary> 客户端url以及其对应的Socket对象字典 </summary>
        public IDictionary<string, IWebSocketConnection> dic_Sockets = new Dictionary<string, IWebSocketConnection>();
      
        private readonly object _lockObject = new object();

        private WebSocketServer _server;

        public WebSocketService()
        {
            //创建WebSocket服务端实例
            _server = new WebSocketServer("ws://0.0.0.0:9997");
            _server.RestartAfterListenError = true; //出错后进行重启
            _server.Start(ws=>
            {
                ws.OnOpen = () =>
                {   
                    WebSocket_OnOpen(ws);
                };
                ws.OnClose = () =>
                {
                    WebSocket_OnClose(ws);
                };
                ws.OnMessage = message =>
                {
                    Task.Run(() => { WebSocket_OnMessage(ws, message); });
                };
                ws.OnError = exp => 
                {
                    WebSocket_OnError(ws, exp);
                };
            });
        }

        private void WebSocket_OnOpen(IWebSocketConnection wsConnection)
        {
            string clientUrl = wsConnection.ConnectionInfo.ClientIpAddress + ":" + wsConnection.ConnectionInfo.ClientPort;
            AddSocket(clientUrl, wsConnection);
            Debug.WriteLine($"服务器和客户端网页:{clientUrl} 建立WebSock连接!当前连接数量:{dic_Sockets.Count}");
        }

        private void WebSocket_OnClose(IWebSocketConnection wsConnection)
        {
            string clientUrl = wsConnection.ConnectionInfo.ClientIpAddress + ":" + wsConnection.ConnectionInfo.ClientPort;
            if (dic_WSSockets.ContainsKey(clientUrl))
            RemoveSocket(clientUrl);
            Debug.WriteLine($"服务器和客户端网页:{clientUrl} 断开WebSock连接!当前连接数量:{dic_Sockets.Count}");
        }

        private void WebSocket_OnError(IWebSocketConnection wsConnection, Exception exception)
        {
            string clientUrl = wsConnection.ConnectionInfo.ClientIpAddress + ":" + wsConnection.ConnectionInfo.ClientPort;
            if (dic_WSSockets.ContainsKey(clientUrl))
            {
                RemoveSocket(clientUrl);
                Debug.WriteLine($"服务器和客户端网页:{clientUrl} 意外断开WebSock连接!当前连接数量:{dic_Sockets.Count}");
            }
        }

        private void WebSocket_OnMessage(IWebSocketConnection wsConnection, string msg)
        {
            string clientUrl = wsConnection.ConnectionInfo.ClientIpAddress + ":" + wsConnection.ConnectionInfo.ClientPort;
            Debug.WriteLine($"服务器:【收到】来客户端网页:{clientUrl}的信息:\n{msg}");
        }

        private bool RemoveSocket(string key)
        {
            lock (_lockObject)
            {
                return dic_WSSockets.Remove(key);
            }
        }

        private void AddSocket(string key, IWebSocketConnection socket)
        {
            lock (_lockObject)
            {
                dic_Sockets.Add(key, socket);
            }
        }
    }
}

安全WebSockets (wss://)

要启用安全连接,需要使用wss​而不是ws​,并指向包含公钥和私钥的x509证书:

var server = new WebSocketServer("wss://0.0.0.0:9997");
server.Certificate = new X509Certificate2("MyCert.pfx");
server.Start(ws =>
{
    //...use as normal
});

子协议协商

Fleck允许你指定支持的子协议,并在WebSocketServer.SupportedSubProtocols​属性上进行协商。如果客户端请求中没有找到支持的子协议,连接将被关闭:

var server = new WebSocketServer("ws://0.0.0.0:9997");
server.SupportedSubProtocols = new []{ "superchat", "chat" };
server.Start(socket =>
{
    //socket.ConnectionInfo.NegotiatedSubProtocol 被填充
});

禁用Nagle算法

你可以通过设置WebSocketConnection.ListenerSocket.NoDelay​true​来禁用Nagle算法:

var server = new WebSocketServer("ws://0.0.0.0:9997");
server.ListenerSocket.NoDelay = true;
server.Start(socket =>
{
    //子连接将不使用Nagle算法
});

监听错误后自动重启

你可以通过设置WebSocketServer.RestartAfterListenError​true​来在监听错误后自动重启服务器:

var server = new WebSocketServer("ws://0.0.0.0:9997");
server.RestartAfterListenError = true;
server.Start(socket =>
{
    //...正常使用
});

自定义日志记录

Fleck可以记录到Log4Net或任何其他第三方日志系统,只需覆盖FleckLog.LogAction​属性:

ILog logger = LogManager.GetLogger(typeof(FleckLog));
FleckLog.LogAction = (level, message, ex) =>
{
    switch (level)
    {
        case LogLevel.Debug:
            logger.Debug(message, ex);
            break;

        case LogLevel.Error:
            logger.Error(message, ex);
            break;

        case LogLevel.Warn:
            logger.Warn(message, ex);
            break;

        default:
            logger.Info(message, ex);
            break;
    }
};