MoreRSS

site iconuptoz | 小小修改

软件开发工程师,INFP,比较喜欢研究数码和软件,想要探究在互联网上的事物是如何被创造和发展。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

uptoz | 小小的 RSS 预览

【CSharp】使用Fleck库实现WebSocket服务

2024-12-18 20:45:18

前言

最近公司有个项目需要用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;
    }
};

【CSharp】lock(this)与lock(private object)区别

2024-12-18 10:50:32

前言

在使用多线程编程时,我们会对代码关键部分确保其一次只由一个线程执行,对于防止争用条件和保持数据完整性至关重要。在C#中,lock 语句就是用于通过同步对共享资源的访问来实现此目的工具。本文介绍lock(this) 与lock(private object) 两种方法及区别。

简介

lock(this) lock(private object) 两种方法都可用于控制对代码块的访问,但在安全性和意外交互的可能性方面有所不同。正确选择对象进行锁定可能会对代码的安全性和可靠性产生重大影响。

1、lock(this)

使用 lock(this) 时,我们是锁定类的当前实例。也就是说锁被放置在代码当前正在操作的对象上。

public class MessageBox
{
    public void ShowMessage()
    {
        lock (this)
        {
            // 输出信息
            Console.WriteLine("感谢您的支持!");
        }
    }
}

上面示例,使用lock(this)确保ShowMessage方法一次只由一个线程对MessageBox的每个实例执行。这将存在被暴露于公开场合的问题。当有其他人也引用此对象,并对其锁定时,这这可能会导致死锁或其他问题。

2、lock(private object)

使用lock(private object)时,我们将锁定一个私有对象,该对象通常是专为此目的而创建的对象的实例。这是一种更安全、更常见的做法。

public class MessageBox
{
    // 定义锁定对象
    private readonly object lockobject = new object();
  
    public void ShowMessage()
    {
        lock (lockobject)
        {
            // 输出信息
            Console.WriteLine("感谢您的支持!");
        }
    }
}

上面示例,lock (lockobject) 确保一次只有一个线程可以执行 ShowMessage 方法,lockobject 是私有的,其他类或代码无法访问。

3、使用 lock(private object) 的好处

封装: 私有对象(private object) 对程序的其他部分是隐藏的,因此其他代码不会意外或有意地锁定它。

安全: 降低因外部代码尝试锁定同一对象而导致的死锁或其他同步问题的风险。

区别

lock(this) 为锁定类的当前实例。此法简单,但可能存在风险,因为其他代码可能会锁定同一对象。

lock(private object) 为锁定只有类知道的私有对象。这更安全,也是最常用且建议的首选方法。此法可以更好地控制并避免因锁定公共对象而导致的意外问题。

总结

以上是关于多线程编程安全中,使用 lock 对共享资源的访问锁定的方法及其二中使用方式的区别,希望对对您有所帮助。

解决Windows 11 24H2中使用NetSetMan无法访问WiFi管理

2024-12-18 10:22:25

前言

在Windows 11 24H2版本中NetSetMan中无法查看WiFi,显示 WLAN 已断开​,且自动切换无法正常工作。

解决

在 Windows 11 24H2 中,Microsoft 添加了一项新限制,将对 WiFi API 的访问隐藏在位置隐私设置后面。如果您禁用了桌面应用程序的位置访问权限,则需要手动启用它:
Windows >设置>隐私和安全>位置

如果您从未更改过该设置,则 Windows 会在桌面应用程序首次尝试访问 WiFi API 时询问您。

【CSharp】在WPF应用程序中捕获全局异常

2024-12-17 11:12:10

前言

在WPF(Windows Presentation Foundation)应用程序中捕获全局异常,通常可以通过以下几个步骤来实现:

  1. Application.DispatcherUnhandledException事件: 这是WPF中用于捕获未处理异常的主要事件。你可以在App.xaml.cs文件中订阅这个事件来处理全局异常。

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            this.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
        }
    
        private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            // 处理异常,例如显示错误消息
            // 可以在这里记录日志,或者显示一个友好的错误消息给用户
            // 注意:不要在这里直接处理异常,否则异常不会被外部的调试器捕获
            // e.Handled = true; // 如果你想要阻止异常继续传播,可以设置这个属性为true
    
            // 记录异常信息
            // LogException(e.Exception);
    
            // 显示错误消息
            // ShowErrorMessage(e.Exception.Message);
    
            // 可以在这里重启应用程序或者执行其他恢复操作
            // RestartApplication();
    
            // 如果你不希望异常被进一步传播,可以设置e.Handled为true
            // e.Handled = true;
        }
    }
    
  2. UnhandledException事件: 这是.NET Framework中用于捕获未处理异常的事件。你可以在App.xaml.cs文件中订阅这个事件。

    AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
    
    private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Exception ex = (Exception)e.ExceptionObject;
        // 处理异常
        // LogException(ex);
        // ShowErrorMessage(ex.Message);
    }
    
  3. TaskScheduler.UnobservedTaskException事件: 如果你的应用程序使用了异步编程和任务(Task),那么你可能还需要捕获未被观察到的任务异常。

    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
    
    private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
    {
        // 处理异常
        // LogException(e.Exception);
        // ShowErrorMessage(e.Exception.Message);
    
        // 如果你不希望异常被进一步传播,可以设置e.SetObserved()为true
        e.SetObserved();
    }
    
  4. 日志记录: 在捕获异常时,通常需要将异常信息记录到日志文件中,以便于后续的调试和分析。

  5. 用户友好的错误消息: 在捕获异常后,应该向用户提供一个用户友好的错误消息,而不是显示技术性的错误信息。

  6. 异常处理策略: 根据应用程序的需求,你可以决定是否需要重启应用程序、关闭应用程序或者执行其他恢复操作。

注意

捕获全局异常是一个重要的错误处理机制,但是它不应该被用来隐藏错误或者代替良好的异常处理实践。正确的做法是尽可能在代码中处理可能发生的异常,并且提供清晰的错误信息给用户。全局异常捕获应该作为最后一道防线,用来处理那些无法预料的异常情况。

【CSharp】在WPF程序中防止触笔(Stylus)输入

2024-11-11 09:46:56

前言

在WPF中使用WebView2时,发现无法在触摸屏中对WebView2打开的网页进行滑动操作,经过研究发现,WPF内置的触笔和触摸支持与WebView2中的触笔和触摸存在冲突,需要禁用掉WPF内置的触笔和触摸支持才能解决。

解决方案

方法一

使用AppContextSwitchOverrides禁用WPF内置的触笔和触摸支持

在你的应用程序的app.config​文件中添加以下配置,可以关闭WPF内置的实时触摸(RealTimeStylus)支持,从而改用Windows触摸消息(WM_TOUCH​):

<configuration>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.Windows.Input.Stylus.DisableStylusAndTouchSupport=true" />
  </runtime>
</configuration>

方法二

使用反射禁用WPF的RealTimeStylus

public static void DisableWPFTabletSupport()
{
    // Get a collection of the tablet devices for this window.
    TabletDeviceCollection devices = System.Windows.Input.Tablet.TabletDevices;
    if (devices.Count > 0)
    {
        // Get the Type of InputManager.
        Type inputManagerType = typeof(System.Windows.Input.InputManager);
        // Call the StylusLogic method on the InputManager.Current instance.
        object stylusLogic = inputManagerType.InvokeMember("StylusLogic", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, InputManager.Current, null);
        if (stylusLogic != null)
        {
            // Get the type of the stylusLogic returned from the call to StylusLogic.
            Type stylusLogicType = stylusLogic.GetType();
            // Loop until there are no more devices to remove.
            while (devices.Count > 0)
            {
                // Remove the first tablet device in the devices collection.
                stylusLogicType.InvokeMember("OnTabletRemoved", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, null, stylusLogic, new object[] { (uint)0 });
            }
        }
    }
}

注意

如果你的WPF程序中还包含了其他的需要滑动操作的控件,如果禁用了WPF内置的触笔输入会导致其他控件无法进行滑动操作,此方案仅适用于WPF程序中只有WebView2一个需要滑动操作的控件。

【SQL Server】插入数据时存在即跳过,不存在则插入

2024-10-15 09:46:05

方法一

MERGE​语句允许你同时执行插入、更新和删除操作。

MERGE INTO YourTable AS target
USING (SELECT @YourKeyColumn AS KeyColumn, @YourValueColumn AS ValueColumn) AS source
ON target.KeyColumn = source.KeyColumn
WHEN NOT MATCHED THEN
    INSERT (KeyColumn, ValueColumn)
    VALUES (source.KeyColumn, source.ValueColumn);

在这个例子中,YourTable​是你想要插入数据的表,KeyColumn​是用于检查记录是否存在的键列,ValueColumn​是你想要插入的值列。@YourKeyColumn​@YourValueColumn​是你要插入的数据的变量。

方法二

另一种方法是使用IF NOT EXISTS​语句来检查记录是否存在,如果不存在,则执行插入操作。

IF NOT EXISTS (SELECT 1 FROM YourTable WHERE KeyColumn = @YourKeyColumn)
BEGIN
    INSERT INTO YourTable (KeyColumn, ValueColumn)
    VALUES (@YourKeyColumn, @YourValueColumn);
END

在这个例子中,如果YourTable​表中不存在KeyColumn​等于@YourKeyColumn​的记录,那么就会执行INSERT​语句来插入新的记录。

区别

  • MERGE​语句

  1. 语法简洁:MERGE​语句通过一个操作就可以完成检查和插入的逻辑,代码更简洁。

  2. 操作原子性:MERGE​语句通常在一个事务中完成,这保证了操作的原子性。

  3. 网络往返:使用MERGE​可以减少与数据库的网络往返次数,因为它在一个操作中完成了检查和插入。

  4. 性能:对于大量数据的批量操作,MERGE​通常比多次执行IF NOT EXISTS​和INSERT​组合更高效。

  5. 复杂性:MERGE​语句的语法相对复杂,对于初学者来说可能不太直观。

  6. 锁和并发:MERGE​操作可能会涉及更多的锁,尤其是在有大量数据更新时,可能会影响数据库的并发性能。

  • IF NOT EXISTS​INSERT​语句

  1. 简单直观:使用IF NOT EXISTS​INSERT​的组合通常更容易理解和维护。

  2. 灵活性:这种方法可以很容易地扩展,例如在插入之前进行额外的逻辑处理。

  3. 性能:对于单条记录的插入,这种方法通常足够快,但如果需要插入大量数据,可能会因为多次网络往返而变得低效。

  4. 并发:由于每次检查和插入都是单独的操作,可能会受到并发插入的影响,导致重复插入的风险。

  5. 事务控制:需要手动管理事务,以确保数据的一致性和完整性。

总结

  • 如果你需要执行批量操作,或者希望减少网络往返次数,MERGE​语句可能是更好的选择。

  • 如果你的操作相对简单,或者你需要在插入前进行复杂的逻辑处理,使用IF NOT EXISTS​INSERT​语句可能更合适。

  • 在高并发的环境中,使用MERGE​语句时需要特别注意锁和事务的管理,以避免性能瓶颈。

  • 对于单条记录的插入,IF NOT EXISTS​INSERT​组合通常足够高效,且代码更易于理解和维护。