.NET斗鱼直播弹幕客户端(2021)

.NET斗鱼直播弹幕客户端(2021)

离之前更新的两篇《.NET斗鱼直播弹幕客户端》已经有一段时间,近期有许多客户向我反馈刚好有这方面的需求,但之前的代码不能用了——但网上许多流传的Node.jsPython脚本却可以用,这岂能忍?(刚好我终于找回了我的发布密码????)因此我有动力重新对此进行好(xie)好(xie)研(bo)究(ke)。

为何之前的不能用了

重新运行之前的C#脚本,发现是在这一行报错的:

using var client = new TcpClient();
await client.ConnectAsync("openbarrage.douyutv.com", 8601); // 这里报错

网上查了查,发现斗鱼确实已经停止使用openbarrage.douyutv.com:8601了。进一步查资料显示,新url改成了danmuproxy.douyu.com,斗鱼已经统一使用WebSocket协议(之前为TCP协议),经过进一步对比新协议代码示例,发现协议过程没有任何区别,序列化也依然用的STT算法。

私货时间:
我认为斗鱼这样做合理,因为WebSocket性能不差,且不需再为浏览器和第三方接口各自维护两套不同的代码。

具体过程如下:

  • 建立WebSocket连接

  • 发送登录请求(可匿名)

  • 加入指定的房间号

  • 每隔45秒,响应一次心跳包

  • (此时,即可)正常接收弹幕数据

新代码实现

.NET中有许多提供WebSocket功能的库和第三方包,之前我经常用websocket-sharp,这是第三方包。现在我们尽量不用第三方包,官方提供的WebSocket客户端叫System.Net.WebSockets.ClientWebSocket,同时支持.NET 4.5.NET Core

按正常的思路,我们会这样写:

return Observable.Create<string>(async (roomId, cancellationToken) =>
{using var ws = new ClientWebSocket();await ws.ConnectAsync(new Uri("wss://danmuproxy.douyu.com:8506/"), cancellationToken);await MsgTool.LoginAsync(ws, roomId, cancellationToken);// other codes
});

但实际运行却不行,会报这个错:

WebSocketException:
The 'Sec-WebSocket-Accept' header value 'Kfh9QIsMVZcl6xEPYxPHzW8SZ8w=' is invalid.

相信我,如果你仔细对比Node/Python.NET代码,整个代码中没任何区别,但打开Fiddler仔细分析协议,发现事情没这么简单,这是一个无法成功连上服务器的包:

请求:
GET https://danmuproxy.douyu.com:8506/ HTTP/1.1
Host: danmuproxy.douyu.com:8506
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: VsPg1/SSskKrbYouGm3ROQ==响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: Kfh9QIsMVZcl6xEPYxPHzW8SZ8w=
Sec-WebSocket-Version: 13
EndTime: 09:37:44.958
ReceivedBytes: 0
SentBytes: 0

研究原因

其中请注意看请求中的Sec-WebSocket-Key项,和响应中的Sec-WebSocket-Accept项。

按照WebSocket协议(https://tools.ietf.org/html/rfc6455#p-11.3.3),服务器响应头Sec-WebSocket-Accept项的值,应该为请求头Sec-WebSocket-Key项字符串追加固定值"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",然后计算其SHA1哈希值,再求Base64,用C#代码说,这一过程如下:

string WebSocketComputeAccept(string key)
{using var sha = SHA1.Create();byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));return Convert.ToBase64String(hash);
}

如上的VsPg1/SSskKrbYouGm3ROQ==按这个计算过程,它应该返回VrPdUdxpPeBXDi1ttGN607h8ct0=,但实际却是Kfh9QIsMVZcl6xEPYxPHzW8SZ8w=,这就是为何C#会报错,因此服务端返回了错误值。

进一步研究原因

我尝试了许多次,C#用客户端连接时,总是会生成随机的Sec-WebSocket-Key值,但不管值如何,服务端总是会返回相同的值——但一旦切换为Node.js,返回的值就完全正常。

我仔细分析了其它语言的WebSocket头与.NET的区别,发现一个重要因素:.NET客户端请求中的Sec-WebSocket-Key项,一定是最后一条,但其它语言中不是最后一条。

如果我们使用Fiddler手动发送握手请求,将Sec-WebSocket-KeySec-WebSocket-Version顺序对调一下,发现响应值如下(服务器响应匹配):

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: VrPdUdxpPeBXDi1ttGN607h8ct0=
Sec-WebSocket-Version: 13

然而用ClientWebSocket是无法控制请求头顺序的,这一点可以在源代码中找到。

最终答案

虽然无法控制请求头顺序,但可以控制Sec-WebSocket-Key不是最后一个,只需添加一个子协议头,值无所谓:ws.Options.AddSubProtocol("-");,因此重点代码如下(完整代码请见LINQPad脚本——douyu-2020.linq):

using var ws = new ClientWebSocket();
ws.Options.AddSubProtocol("-");
await ws.ConnectAsync(new Uri("wss://danmuproxy.douyu.com:8506"), QueryCancelToken);
await ws.SendAsync(SerializeDouyu($"type@=loginreq/roomid@=74751/ver@=20190610/"), WebSocketMessageType.Binary, false, QueryCancelToken);
await ws.SendAsync(SerializeDouyu($"type@=joingroup/rid@=74751/gid@=-9999/"), WebSocketMessageType.Binary, false, QueryCancelToken);
_ = Task.Run(async () =>
{while (!QueryCancelToken.IsCancellationRequested){await Task.Delay(45000, QueryCancelToken);await ws.SendAsync(SerializeDouyu($"type@=mrkl/"), WebSocketMessageType.Binary, false, QueryCancelToken);}
});while (!QueryCancelToken.IsCancellationRequested)
{var buffer = new byte[4096];WebSocketReceiveResult r = await ws.ReceiveAsync(buffer, QueryCancelToken);string result = DeserializeDouyu(new Memory<byte>(buffer, 0, r.Count), QueryCancelToken);DecodeStringToJObject(result).Dump();
}

运行效果:

封装优化

之前我是基于System.Reactive库做的封装,但C# 9.0已经发布许久,这次我重新基于IAsyncEnumerable写了一版,这个以C# 9.0作为异步流的基础,扩展可以用System.Linq.Async,从而获得与正常的LINQ完全一致的体验,核心代码如下:

public class DouyuBarrage
{static HttpClient http = new HttpClient();public static async IAsyncEnumerable<string> RawFromUrl(string url, [EnumeratorCancellation] CancellationToken cancellationToken = default){HttpResponseMessage html = await http.GetAsync(url, cancellationToken);var roomId = Regex.Match(await html.Content.ReadAsStringAsync(), @"\$ROOM.room_id[ ]?=[ ]?(\d+);").Groups[1].Value;using var ws = new ClientWebSocket();ws.Options.AddSubProtocol("-");await ws.ConnectAsync(new Uri("wss://danmuproxy.douyu.com:8506/"), cancellationToken);await MsgTool.LoginAsync(ws, roomId, cancellationToken);await MsgTool.JoinGroupAsync(ws, roomId, cancellationToken);var task = Task.Run(async () =>{while (!cancellationToken.IsCancellationRequested){await MsgTool.SendTick(ws, cancellationToken);await Task.Delay(45000, cancellationToken);}}, cancellationToken);while (ws.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested){yield return await MsgTool.RecieveAsync(ws, cancellationToken);}GC.KeepAlive(task);await MsgTool.Logout(ws, cancellationToken);}public static IAsyncEnumerable<JToken> JObjectFromUrl(string url) => RawFromUrl(url).Select(MsgTool.DecodeStringToJObject);public static IAsyncEnumerable<Barrage> ChatMessageFromUrl(string url) => JObjectFromUrl(url).Where(x => x["type"].Value<string>() == "chatmsg").Select(Barrage.FromJToken);
}

见最后两个方法JObjectFromUrlChatMessageFromUrl,基于IAsyncEnumerable,可以获得与LINQSystem.Reactive完全一致的开发体验,一行代码即可完成异步流的筛选、数据转换。

说在最后

以上所有的完整代码和示例,都已经上传到我的博客专用Github仓库,各位可以自行前往下载:https://github.com/sdcb/blog-data/tree/master/2021/20191011-douyu-barrage-with-dotnet

喜欢的朋友 请关注我的微信公众号:【DotNet骚操作】

DotNet骚操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Exceptionless服务端本地化部署

背景分布式异常日志收集框架Exceptionless是开源的工具&#xff0c;根据官方给出的说明&#xff1a;Exceptionless提供两种使用方式&#xff0c;一种是官网创建账号,需要付费&#xff0c;免费版有限制&#xff1b;一种是自己搭建本地项目&#xff0c;无任何限制。准备安装包准备…

ABP vnext模块化架构的最佳实践的实现

在上一篇文章《手把手教你用Abp vnext构建API接口服务》中&#xff0c;我们用ABP vnext实现了WebAPI接口服务&#xff0c;但是并非ABP模块化架构的最佳实践。我本身也在学习ABP&#xff0c;我认为ABP新手应该从最佳实践开始学习&#xff0c;可以少走很多弯路&#xff0c;所以写…

iphone查看删除的短信_手机资讯:iPhone手机可以批量删除短信吗如何操作

如今使用IT数码设备的小伙伴们是越来越多了&#xff0c;那么IT数码设备当中是有很多知识的&#xff0c;这些知识很多小伙伴一般都是不知道的&#xff0c;就好比最近就有很多小伙伴们想要知道iPhone手机可以批量删除短信吗如何操作&#xff0c;那么既然现在大家对于iPhone手机可…

如何在 C# 8 中使用 模式匹配

模式匹配 是在 C# 7 中引入的一个非常????的特性&#xff0c;你可以在任何类型上使用 模式匹配&#xff0c;甚至是自定义类型&#xff0c;而且在 C# 8 中得到了增强&#xff0c;引入了大量的新模式类型&#xff0c;这篇文章就来讨论如何在 C# 8 中使用模式匹配。C# 8 中的表…

Hadoop 中zoo_0基础如何入门HADOOP

原标题&#xff1a;0基础如何入门HADOOP学习一样东西&#xff0c;肯定先要了解这个东西是什么&#xff0c;那什么是HADOOP呢&#xff1f;我们就来看看什么是HADOOP和如何学习HADOOP及学习内容。一&#xff0c;什么是HADOOPHADOOP是apache旗下的一套开源软件平台HADOOP提供的功能…

.NET 5 程序高级调试-WinDbg

上周和大家分享了.NET 5开源工作流框架elsa&#xff0c;程序跑起来后&#xff0c;想看一下后台线程的执行情况。抓了个进程Dump后&#xff0c;使用WinDbg调试&#xff0c;加载SOS调试器扩展&#xff0c;结果无法正常使用了&#xff1a;0:000> .loadby sos clrUnable to find…

.Net在线编辑工具.NET Fiddle

介绍推荐工具&#xff1a;.NET Fiddle推荐理由&#xff1a;在线调试&#xff0c;编译&#xff0c;运行.net代码&#xff0c;同时支持C#&#xff0c;VB.NET&#xff0c;F#推荐说明&#xff1a;&#xff1a;对于.NET开发者来说是福音&#xff0c;因为我们可以不用再担心环境与庞大…

Typora markdown公式换行等号对齐_下了31个markdown编辑器,我就不信选不出一个好用的...

markdown编辑器测评标准总体标准渲染领域编辑领域数据管理其他TyporaVnoteMwebJoplinZettlrmacdownulyssesMarktextghostwriterfocusedbywordmarkedFarBoxNotablebear(熊掌笔记)iA writerMarxico(马克飞象)JetBrains系列的IDEsublime&#xff08;贫穷&#xff0c;没有插件&…

WSUS专题之二:部署与规划1

部署场景: 我们这里仅讨论和Internet完全物理隔离的企业内网的WSUS部署 Internet断开的WSUS服务器环境 部署WSUS服务时&#xff0c;并不要求你必须连接到Internet。对于没有连接到Internet的网络环境&#xff0c;你一样可以部署WSUS服务。通过在其他连接到Internet上的WSUS服务…

.Net Core in Docker - 使用阿里云Codepipeline及阿里云容器镜像服务实现持续交付/部署(CD)...

上一次演示了如何.Net Core in Docker - 使用阿里云Codepipeline及阿里云容器镜像服务实现持续集成&#xff08;CI&#xff09;&#xff0c;讲到这里我们push一下代码后就自动编译、自动跑单元测试、自动构建镜像、自动推送镜像到私仓。那么离我们最初设定的目标只差那么一小步…

spyder pyecharts不显示_我的显示器需要定时校色吗?

在对图像色彩有要求的领域中&#xff0c;显示器的色彩准确是相当重要的。专业的显示器&#xff0c;能够具有更大的色域&#xff0c;更大的色深&#xff0c;以及更精确的ΔE色准值。这也是一个专业显示器所应有的品质。但是&#xff0c;我们在讨论色彩准确性的同时&#xff0c;往…

外观模式(Façade Pattern)

概述 在软件开发系统中&#xff0c;客户程序经常会与复杂系统的内部子系统之间产生耦合&#xff0c;而导致客户程序随着子系统的变化而变化。那么如何简化客户程序与子系统之间的交互接口&#xff1f;如何将复杂系统的内部子系统与客户程序之间的依赖解耦&#xff1f;这就是要说…

WTM5.0发布,全面支持.net5

点击上方蓝字关注我们WTM5.0全面支持.net5WTM5.0是WTM框架开源2年以来最大的一次升级&#xff0c;全面支持.net5&#xff0c;大幅重构了底层代码&#xff0c;针对广大用户提出的封装过度&#xff0c;不够灵活&#xff0c;性能不高等问题进行了彻底的修改。这次升级使WTM继续保持…

rsa 模数 指数转换 c语言_模数转换,你必须知道的8个经典ADC转换电路方案

模数转换器即A/D转换器&#xff0c;或简称ADC&#xff0c;通常是指一个将模拟信号转变为数字信号的电子元件。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义&#xff0c;仅仅表示一个相对大小。故任何一个模数转换器都需要一…

linux定时关机命令_win10电脑定时关机命令

电脑定时关机命令可以帮助用户们很好的去设置电脑自动关机等&#xff0c;自己无需操作&#xff0c;电脑也会在对应的时间自动关机&#xff0c;使用起来还是非常方便的&#xff0c;现在就来看看电脑定时关机命令教程吧~电脑定时关机命令是什么&#xff1a;一、CMD设置关机1、点击…

为你的项目启用可空引用类型

为你的项目启用可空引用类型IntroC# 从 8.0 开始引入了可空引用类型&#xff0c;我们可以为项目启用可空引用类型来借助编译器来帮助我们更好的处理代码中的空引用的处理&#xff0c;可以避免我们写很多不必要 null 检查&#xff0c;提高我们的效率Why为什么我们要启用可空引用…

有哪些编辑软件可以编辑c语言,可以推荐一个手机上最好用且免费的c语言编辑器吗?...

C4droid(又名C编译器)呗&#xff0c;一个既可以编辑&#xff0c;还可以运行C语言的手机编程软件&#xff0c;下面我简单介绍一下这个软件的安装和使用&#xff1a;1.首先&#xff0c;安装C4droid&#xff0c;这个直接在手机应用中搜索就行&#xff0c;如下&#xff0c;大概也就…

cas 4.2.7 官方手册_海城市地区,保险手册核验的简单流程

最近海城市社保正在进行保险手册的核验工作&#xff0c;据说是要将当地社保数据并网&#xff0c;由省社保机构监督管理。我们这个百万人口的县级市&#xff0c;核验工作只由一个部门在固定的办事大厅里完成&#xff0c;工作量也是相当大了。核验工作自9月末开始&#xff0c;已进…

在 C# 中生成代码的四种方式——包括.NET 5中的Source Generators

Microsoft在最新的C#版本中引入了Source Generator。这是一项新功能&#xff0c;可以让我们在代码编译时生成源代码。在本文中&#xff0c;我将介绍四种C#中的代码生成方式&#xff0c;以简化我们的日常工作。然后&#xff0c;您可以视情况选择正确的方法。在 .NET 中&#xff…

powercfg -h off_驭鲛记的主演会是谁?肖战关系特别好的艺人朋友呢?白敬亭和吴映洁有没有故事啊?高伟光是不是隐婚生子了?讲讲管h和马司令呗?...

近期后台提问的比较多&#xff0c;没被翻牌的小可爱们不要着急&#xff0c;我会尽力把大家的问题都照顾到&#xff0c;笔芯1. 扒扒&#xff0c;想知道华策驭鲛记的主演会是谁&#xff1f;主演还没定&#xff0c;女主在接触热巴&#xff0c;男主还没接触&#xff0c;这个戏明年才…