sta/websocket-sharp

GitHub: sta/websocket-sharp

websocket-sharp 是一个 C# 编写的 WebSocket 协议库,提供完整的客户端与服务器实现,解决 .NET 环境下的实时双向通信需求。

Stars: 6069 | Forks: 1684

![Logo](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/1d3211d9a2233415.png) ## 欢迎使用 websocket-sharp! websocket-sharp 支持: - [RFC 6455](#supported-websocket-specifications) - [WebSocket 客户端](#websocket-client)和[服务器](#websocket-server) - [逐消息压缩](#per-message-compression)扩展 - [安全连接](#secure-connection) - [HTTP 身份验证](#http-authentication) - [查询字符串、Origin 头、Cookies 和用户头](#query-string-origin-header-cookies-and-user-headers) - [通过 HTTP 代理服务器连接](#connecting-through-the-http-proxy-server) - .NET Framework **3.5** 或更高版本的 .NET Framework(包含兼容环境,如 [Mono]) ## 分支 - [master] 用于正式发布版本。 - [hybi-00] 用于较旧的 [draft-ietf-hybi-thewebsocketprotocol-00]。不再维护。 - [draft75] 用于更旧的 [draft-hixie-thewebsocketprotocol-75]。不再维护。 ## 构建 websocket-sharp 被构建为单个程序集,**websocket-sharp.dll**。 websocket-sharp 是使用 [MonoDevelop] 开发的。因此,一种简单的构建方式是在 MonoDevelop 中打开 **websocket-sharp.sln**,并使用任意构建配置(例如 `Debug`)对 **websocket-sharp project** 运行构建。 ## 安装 ### 自行构建 您应该将您的 websocket-sharp.dll(例如 `/path/to/websocket-sharp/bin/Debug/websocket-sharp.dll`)添加到您项目的库引用中。 如果您想在您的 [Unity] 项目中使用该 dll,您应该在 **Unity Editor** 中将其添加到您项目的任意文件夹中(例如 `Assets/Plugins`)。 ### NuGet Gallery websocket-sharp 已在 [NuGet Gallery] 上提供,目前仍为 **预发布** 版本。 - [NuGet Gallery: websocket-sharp] 您可以使用 NuGet Package Manager,在 Package Manager Console 中执行以下命令,将 websocket-sharp 添加到您的项目中。 ``` PM> Install-Package WebSocketSharp -Pre ``` ## 用法 ### WebSocket 客户端 ``` using System; using WebSocketSharp; namespace Example { public class Program { public static void Main (string[] args) { using (var ws = new WebSocket ("ws://dragonsnest.far/Laputa")) { ws.OnMessage += (sender, e) => Console.WriteLine ("Laputa says: " + e.Data); ws.Connect (); ws.Send ("BALUS"); Console.ReadKey (true); } } } } ``` #### 步骤 1 所需的命名空间。 ``` using WebSocketSharp; ``` `WebSocket` 类存在于 `WebSocketSharp` 命名空间中。 #### 步骤 2 使用要连接的 WebSocket URL 创建 `WebSocket` 类的新实例。 ``` var ws = new WebSocket ("ws://example.com"); ``` `WebSocket` 类继承了 `System.IDisposable` 接口,因此您可以使用 `using` 语句来创建它。 ``` using (var ws = new WebSocket ("ws://example.com")) { ... } ``` 当控制流离开 `using` 块时,这将使用状态码 `1001`(going away)**关闭** WebSocket 连接。 #### 步骤 3 设置 `WebSocket` 的事件。 ##### WebSocket.OnOpen 事件 当 WebSocket 连接建立时,将触发此事件。 ``` ws.OnOpen += (sender, e) => { ... }; ``` `e` 会传入 `System.EventArgs.Empty`,因此您不需要使用它。 ##### WebSocket.OnMessage 事件 当 `WebSocket` 实例接收到消息时,将触发此事件。 ``` ws.OnMessage += (sender, e) => { ... }; ``` `e` 会传入一个 `WebSocketSharp.MessageEventArgs` 实例。 如果您想获取消息数据,您应该访问 `e.Data` 或 `e.RawData` 属性。 `e.Data` 属性返回一个 `string`,因此它主要用于获取**文本**消息数据。 `e.RawData` 属性返回一个 `byte[]`,因此它主要用于获取**二进制**消息数据。 ``` if (e.IsText) { // Do something with e.Data. ... return; } if (e.IsBinary) { // Do something with e.RawData. ... return; } ``` 此外,如果您想通过此事件通知已接收到 **ping**,您应该将 `WebSocket.EmitOnPing` 属性设置为 `true`。 ``` ws.EmitOnPing = true; ws.OnMessage += (sender, e) => { if (e.IsPing) { // Do something to notify that a ping has been received. ... return; } }; ``` ##### WebSocket.OnError 事件 当 `WebSocket` 实例发生错误时,将触发此事件。 ``` ws.OnError += (sender, e) => { ... }; ``` `e` 会传入一个 `WebSocketSharp.ErrorEventArgs` 实例。 如果您想获取错误消息,您应该访问 `e.Message` 属性。 `e.Message` 属性返回一个表示错误消息的 `string`。 如果错误是由异常引起的,`e.Exception` 属性将返回一个表示错误原因的 `System.Exception` 实例。 ##### WebSocket.OnClose 事件 当 WebSocket 连接已关闭时,将触发此事件。 ``` ws.OnClose += (sender, e) => { ... }; ``` `e` 会传入一个 `WebSocketSharp.CloseEventArgs` 实例。 如果您想获取关闭的原因,您应该访问 `e.Code` 或 `e.Reason` 属性。 `e.Code` 属性返回一个表示关闭状态码的 `ushort`。 `e.Reason` 属性返回一个表示关闭原因的 `string`。 #### 步骤 4 连接到 WebSocket 服务器。 ``` ws.Connect (); ``` 如果您想异步连接到服务器,您应该使用 `WebSocket.ConnectAsync ()` 方法。 #### 步骤 5 向 WebSocket 服务器发送数据。 ``` ws.Send (data); ``` `WebSocket.Send` 方法已被重载。 您可以使用 `WebSocket.Send (string)`、`WebSocket.Send (byte[])`、`WebSocket.Send (System.IO.FileInfo)` 或 `WebSocket.Send (System.IO.Stream, int)` 方法来发送数据。 如果您想异步发送数据,您应该使用 `WebSocket.SendAsync` 方法。 ``` ws.SendAsync (data, completed); ``` 此外,如果您想在发送完成后执行某些操作,您应该将 `completed` 设置为任意的 `Action` 委托。 #### 步骤 6 关闭 WebSocket 连接。 ``` ws.Close (code, reason); ``` 如果您想显式关闭连接,您应该使用 `WebSocket.Close` 方法。 `WebSocket.Close` 方法已被重载。 您可以使用 `WebSocket.Close ()`、`WebSocket.Close (ushort)`、`WebSocket.Close (WebSocketSharp.CloseStatusCode)`、`WebSocket.Close (ushort, string)` 或 `WebSocket.Close (WebSocketSharp.CloseStatusCode, string)` 方法来关闭连接。 如果您想异步关闭连接,您应该使用 `WebSocket.CloseAsync` 方法。 ### WebSocket 服务器 ``` using System; using WebSocketSharp; using WebSocketSharp.Server; namespace Example { public class Laputa : WebSocketBehavior { protected override void OnMessage (MessageEventArgs e) { var msg = e.Data == "BALUS" ? "Are you kidding?" : "I'm not available now."; Send (msg); } } public class Program { public static void Main (string[] args) { var wssv = new WebSocketServer ("ws://dragonsnest.far"); wssv.AddWebSocketService ("/Laputa"); wssv.Start (); Console.ReadKey (true); wssv.Stop (); } } } ``` #### 步骤 1 所需的命名空间。 ``` using WebSocketSharp.Server; ``` `WebSocketBehavior` 和 `WebSocketServer` 类存在于 `WebSocketSharp.Server` 命名空间中。 #### 步骤 2 创建继承 `WebSocketBehavior` 类的类。 例如,如果您想提供 Echo 服务, ``` using System; using WebSocketSharp; using WebSocketSharp.Server; public class Echo : WebSocketBehavior { protected override void OnMessage (MessageEventArgs e) { Send (e.Data); } } ``` 如果您想提供聊天服务, ``` using System; using WebSocketSharp; using WebSocketSharp.Server; public class Chat : WebSocketBehavior { private string _suffix; public Chat () { _suffix = String.Empty; } public string Suffix { get { return _suffix; } set { _suffix = value ?? String.Empty; } } protected override void OnMessage (MessageEventArgs e) { Sessions.Broadcast (e.Data + _suffix); } } ``` 您可以通过创建继承 `WebSocketBehavior` 类的类来定义任何 WebSocket 服务的行为。 如果您重写 `WebSocketBehavior.OnMessage (MessageEventArgs)` 方法,当服务中某个会话使用的 `WebSocket` 接收到消息时,将会调用该方法。 如果您重写 `WebSocketBehavior.OnOpen ()`、`WebSocketBehavior.OnError (ErrorEventArgs)` 和 `WebSocketBehavior.OnClose (CloseEventArgs)` 方法,则当 `WebSocket` 的各个事件(`OnOpen`、`OnError` 和 `OnClose`)触发时,将会分别调用它们。 `WebSocketBehavior.Send` 方法可以向服务中某个会话的客户端发送数据。 如果您想获取服务中的会话,您应该访问 `WebSocketBehavior.Sessions` 属性(返回一个 `WebSocketSharp.Server.WebSocketSessionManager`)。 `WebSocketBehavior.Sessions.Broadcast` 方法可以向服务中的每个客户端发送数据。 #### 步骤 3 创建 `WebSocketServer` 类的新实例。 ``` var wssv = new WebSocketServer (4649); wssv.AddWebSocketService ("/Echo"); wssv.AddWebSocketService ("/Chat"); wssv.AddWebSocketService ("/ChatWithNyan", s => s.Suffix = " Nyan!"); ``` 您可以使用 `WebSocketServer.AddWebSocketService (string)` 或 `WebSocketServer.AddWebSocketService (string, Action)` 方法,通过指定的行为和服务的绝对路径,将任何 WebSocket 服务添加到您的 `WebSocketServer` 中。 `TBehavior` 的类型必须继承 `WebSocketBehavior` 类,并且必须具有公共的无参构造函数。 因此,您可以使用上面步骤 2 中的类来添加该服务。 如果您在不带端口号的情况下创建 `WebSocketServer` 类的新实例,它会将端口号设置为 **80**。因此,必须使用 root 权限运行。 ``` $ sudo mono example2.exe ``` #### 步骤 4 启动 WebSocket 服务器。 ``` wssv.Start (); ``` #### 步骤 5 停止 WebSocket 服务器。 ``` wssv.Stop (); ``` ### 带有 WebSocket 的 HTTP 服务器 我修改了 **[Mono]** 中的 `System.Net.HttpListener`、`System.Net.HttpListenerContext` 及其他一些类,以创建一个允许接受 WebSocket 握手请求的 HTTP 服务器。 因此,websocket-sharp 提供了 `WebSocketSharp.Server.HttpServer` 类。 您可以使用 `HttpServer.AddWebSocketService (string)` 或 `HttpServer.AddWebSocketService (string, Action)` 方法,通过指定的行为和服务的路径,将任何 WebSocket 服务添加到您的 `HttpServer` 中。 ``` var httpsv = new HttpServer (4649); httpsv.AddWebSocketService ("/Echo"); httpsv.AddWebSocketService ("/Chat"); httpsv.AddWebSocketService ("/ChatWithNyan", s => s.Suffix = " Nyan!"); ``` 了解更多信息,请查看 **[Example3]**? ### WebSocket 扩展 #### 逐消息压缩 websocket-sharp 支持 [Per-message Compression][rfc7692] 扩展(但不支持带有 [context take over] 的扩展)。 作为 WebSocket 客户端,如果您想启用此扩展,您应该在调用连接方法之前,将 `WebSocket.Compression` 属性设置为某种压缩方法。 ``` ws.Compression = CompressionMethod.Deflate; ``` 然后,客户端将在发送给服务器的握手请求中包含以下标头。 ``` Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover ``` 如果服务器支持此扩展,它将返回包含相应值的相同标头。 因此,最终当客户端在握手响应中接收到该标头时,此扩展将可用。 #### 忽略扩展 作为 WebSocket 服务器,如果您想忽略客户端请求的扩展,您应该在您的 `WebSocketBehavior` 构造函数或其初始化过程中将 `WebSocketBehavior.IgnoreExtensions` 属性设置为 `true`,如下所示。 ``` wssv.AddWebSocketService ( "/Chat", s => s.IgnoreExtensions = true // To ignore the extensions requested from a client. ); ``` 如果设置为 `true`,服务将不会在其握手响应中返回 Sec-WebSocket-Extensions 标头。 我认为当您在连接服务器时遇到错误,并希望排除扩展作为错误原因时,这会很有用。 ### 安全连接 websocket-sharp 支持基于 **SSL/TLS** 的安全连接。 作为 WebSocket 客户端,您应该使用带有 **wss** 方案的 WebSocket URL 来创建 `WebSocket` 类的新实例。 ``` var ws = new WebSocket ("wss://example.com"); ``` 如果您想对服务器证书设置自定义验证,您应该将 `WebSocket.SslConfiguration.ServerCertificateValidationCallback` 属性设置为此验证对应的回调。 ``` ws.SslConfiguration.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { // Do something to validate the server certificate. ... return true; // If the server certificate is valid. }; ``` 默认的回调始终返回 `true`。 作为 WebSocket 服务器,您应该创建一个带有安全连接相关设置的 `WebSocketServer` 或 `HttpServer` 类的新实例,如下所示。 ``` var wssv = new WebSocketServer (5963, true); wssv.SslConfiguration.ServerCertificate = new X509Certificate2 ( "/path/to/cert.pfx", "password for cert.pfx" ); ``` ### HTTP 身份验证 websocket-sharp 支持 [HTTP Authentication (Basic/Digest)][rfc2617]。 作为 WebSocket 客户端,您应该在调用连接方法之前,使用 `WebSocket.SetCredentials (string, string, bool)` 方法为 HTTP 身份验证设置一对用户名和密码。 ``` ws.SetCredentials ("nobita", "password", preAuth); ``` 如果 `preAuth` 为 `true`,客户端将在第一次握手请求中向服务器发送用于 Basic 身份验证的凭据。 否则,它将在第二次握手请求中向服务器发送用于 Basic 或 Digest(由第一次握手请求的未授权响应决定)身份验证的凭据。 作为 WebSocket 服务器,您应该在调用启动方法之前,设置 HTTP 身份验证方案、领域(realm)以及任何用于查找用户凭据的函数,如下所示。 ``` wssv.AuthenticationSchemes = AuthenticationSchemes.Basic; wssv.Realm = "WebSocket Test"; wssv.UserCredentialsFinder = id => { var name = id.Name; // Return user name, password, and roles. return name == "nobita" ? new NetworkCredential (name, "password", "gunfighter") : null; // If the user credentials are not found. }; ``` 如果您想提供 Digest 身份验证,您应该进行如下设置。 ``` wssv.AuthenticationSchemes = AuthenticationSchemes.Digest; ``` ### 查询字符串、Origin 标头、Cookies 和用户标头 #### 查询字符串 作为 WebSocket 客户端,如果您想在握手请求中发送查询字符串,您应该使用包含 [Query] string 参数的 WebSocket URL 来创建 `WebSocket` 类的新实例。 ``` var ws = new WebSocket ("ws://example.com/?name=nobita"); ``` 作为 WebSocket 服务器,如果您想获取握手请求中包含的查询字符串,您应该访问 `WebSocketBehavior.QueryString` 属性,如下所示。 ``` public class Chat : WebSocketBehavior { private string _name; ... protected override void OnOpen () { _name = QueryString["name"]; } ... } ``` #### Origin 标头 作为 WebSocket 客户端,如果您想在握手请求中发送 Origin 标头,您应该在调用连接方法之前,将 `WebSocket.Origin` 属性设置为作为 [Origin] 标头的允许值。 ``` ws.Origin = "http://example.com"; ``` 作为 WebSocket 服务器,如果您想验证 Origin 标头,您应该使用您的 `WebSocketBehavior` 为其设置验证逻辑,例如,使用 `WebSocketServer.AddWebSocketService (string, Action)` 方法并进行初始化,如下所示。 ``` wssv.AddWebSocketService ( "/Chat", s => { s.OriginValidator = val => { // Check the value of the Origin header, and return true if valid. Uri origin; return !val.IsNullOrEmpty () && Uri.TryCreate (val, UriKind.Absolute, out origin) && origin.Host == "example.com"; }; } ); ``` #### Cookies 作为 WebSocket 客户端,如果您想在握手请求中发送 Cookies,您应该在调用连接方法之前,使用 `WebSocket.SetCookie (WebSocketSharp.Net.Cookie)` 方法设置任意 cookie。 ``` ws.SetCookie (new Cookie ("name", "nobita")); ``` 作为 WebSocket 服务器,如果您想对 Cookies 作出响应,您应该使用您的 `WebSocketBehavior` 为其设置响应操作,例如,使用 `WebSocketServer.AddWebSocketService (string, Action)` 方法并进行初始化,如下所示。 ``` wssv.AddWebSocketService ( "/Chat", s => { s.CookiesResponder = (reqCookies, resCookies) => { foreach (var cookie in reqCookies) { cookie.Expired = true; resCookies.Add (cookie); } }; } ); ``` #### 用户标头 作为 WebSocket 客户端,如果您想在握手请求中发送用户标头,您应该在调用连接方法之前,使用 `WebSocket.SetUserHeader (string, string)` 方法设置任意用户定义的标头。 ``` ws.SetUserHeader ("RequestForID", "ID"); ``` 如果您想获取握手响应中包含的用户标头,您应该在握手完成后访问 `WebSocket.HandshakeResponseHeaders` 属性。 ``` var id = ws.HandshakeResponseHeaders["ID"]; ``` 作为 WebSocket 服务器,如果您想对用户标头作出响应,您应该使用您的 `WebSocketBehavior` 为其设置响应操作,例如,使用 `WebSocketServer.AddWebSocketService (string, Action)` 方法并进行初始化,所示。 ``` wssv.AddWebSocketService ( "/Chat", s => { s.UserHeadersResponder = (reqHeaders, userHeaders) => { var val = reqHeaders["RequestForID"]; if (!val.IsNullOrEmpty ()) userHeaders[val] = s.ID; }; } ); ``` ### 通过 HTTP 代理服务器连接 websocket-sharp 支持通过 HTTP 代理服务器进行连接。 如果您想通过 HTTP 代理服务器连接到 WebSocket 服务器,您应该在调用连接方法之前,使用 `WebSocket.SetProxy (string, string, string)` 方法设置代理服务器 URL,并在必要时设置用于代理服务器身份验证的一对用户名和密码。 ``` var ws = new WebSocket ("ws://example.com"); ws.SetProxy ("http://localhost:3128", "nobita", "password"); ``` 我已经使用 **[Squid]** 测试了此功能。必须在 **squid.conf**(例如 `/etc/squid/squid.conf`)中禁用以下选项。 ``` # 拒绝连接到除 SSL 端口以外的其他端口 #http_access deny CONNECT !SSL_ports ``` ### 日志记录 `WebSocket` 类拥有自己的日志记录功能。 您可以将其与 `WebSocket.Log` 属性(返回一个 `WebSocketSharp.Logger`)一起使用。 因此,如果您想更改当前的日志记录级别(默认为 `WebSocketSharp.LogLevel.Error`),您应该将 `WebSocket.Log.Level` 属性设置为 `LogLevel` 枚举值中的任意一个。 ``` ws.Log.Level = LogLevel.Debug; ``` 上面的代码表示低于 `LogLevel.Debug` 的日志不会被输出。 如果您想输出日志,您应该使用任意输出方法。以下代码输出一条级别为 `LogLevel.Debug` 的日志。 ``` ws.Log.Debug ("This is a debug message."); ``` `WebSocketServer` 和 `HttpServer` 类具有相同的日志记录功能。 ## 示例 使用 websocket-sharp 的示例。 ### 示例 [Example] 连接到由 [Example2] 或 [Example3] 执行的服务器。 ### Example2 [Example2] 启动一个 WebSocket 服务器。 ### Example3 [Example3] 启动一个允许接受 WebSocket 握手请求的 HTTP 服务器。 在 Example3 运行期间,您是否愿意使用您的 Web 浏览器访问 [http://localhost:4649](http://localhost:4649) 来进行 **WebSocket Echo Test**? ## 支持的 WebSocket 规范 websocket-sharp 支持 **RFC 6455**,它基于以下参考资料: - [The WebSocket Protocol][rfc6455] - [The WebSocket API][api] - [Compression Extensions for WebSocket][rfc7692] ## 许可证 websocket-sharp 基于 [The MIT License] 提供。
标签:WebSocket, 依赖分析, 内核驱动, 开发库, 网络协议, 网络通信