网站防止镜像,wordpress首页幻灯,警告欺骗网站模板,制作收费网站要花多少钱前言现在直播平台由于弹幕的存在#xff0c;主播与观众可以更轻松地进行互动#xff0c;非常受年轻群众的欢迎。斗鱼TV就是一款非常流行的直播平台#xff0c;弹幕更是非常火爆。看到有不少主播接入 弹幕语音播报器、 弹幕点歌等模块#xff0c;这都需要首先连接斗鱼弹幕。… 前言现在直播平台由于弹幕的存在主播与观众可以更轻松地进行互动非常受年轻群众的欢迎。斗鱼TV就是一款非常流行的直播平台弹幕更是非常火爆。看到有不少主播接入 弹幕语音播报器、 弹幕点歌等模块这都需要首先连接斗鱼弹幕。经常看到其它编程语言的开发者分享了他们斗鱼弹幕客户端的代码。.NET当然也能做还能做得更好只是不知为何很少见人分享?。本文将包含以下内容我将使用斗鱼TV官方公开的弹幕PDF文档使用 Socket/ TcpClient连续斗鱼弹幕分析如何利用 .NET强大的 ValueTask特性在保持代码简洁的同时轻松享受高性能异步代码的快乐然后将使用 ReactiveExtensions RX演示如何将一系列复杂的弹幕接入操作就像写 HelloWorld一般容易用我自制的“准游戏引擎” FlysEngine只需少量代码即可将斗鱼TV的弹幕显示左右飞过的效果本文内容可能比较多因此分上、下两篇阐述上篇将具体聊聊第1、2点第3、4点将在下篇进行整篇完成后最终效果如下斗鱼直播API现在网上可以轻松找到 斗鱼弹幕服务器第三方接入协议v1.6.2.pdf网上搜索该关键字即可找到。文档提到第三方接入弹幕服务的服务器为 openbarrage.douyutv.com:8601我们可以使用 TcpClient来方便连接using (var client new TcpClient())
{ client.ConnectAsync(openbarrage.douyutv.com, 8601).Wait(); Stream stream client.GetStream(); // do other works
}该文档中提到所有数据包格式如下注意前两个4字节的消息长度是完全一样的可以使用 Debug.Assert进行断言。其中所有数字都为小端整数刚好 .NET的 BinaryWriter类默认都以小端整数进行转换。可以利用起来。因此读取一个消息包的完整代码如下using (var reader new BinaryReader(stream, Encoding.UTF8, true))
{ var fullMsgLength reader.ReadInt32(); var fullMsgLength2 reader.ReadInt32(); Debug.Assert(fullMsgLength fullMsgLength2); var length fullMsgLength - 1 - 4 - 4; var packType reader.ReadInt16(); Debug.Assert(packType ServerSendToClient); var encrypted reader.ReadByte(); Debug.Assert(encrypted Encrypted); var reserved reader.ReadByte(); Debug.Assert(reserved Reserved); var bytes reader.ReadBytes(length); var zero reader.ReadByte(); Debug.Assert(zero ByteZero);
}其中 bytes既是数据部分根据 pdf文档中的规定该部分为 UTF-8编码在 C#中使用 Encoding.UTF8.GetString()即可获取其字符串该字符串长这样子typechatmsg/rid633019/ct1/uid124155/nn夜科扬羽/txt这不压个蜥蜴/cid602c7f1becf2419962a6520300000000/icavatarS000S12S41S55_avatar/level21/sahf0/cst1570891500125/bnn賊开心/bl8/brid5789561/hc21ebd5b2c86c01e0565453e45f14ca5b/el/lk/urlev10/ 该格式不是 JSON/ XML等但仔细分析又确实有逻辑有层次感根据文档该格式为所谓的 STT序列化该格式包含键值对、数组等多种格式。虽然不懂为什么不用 JSON。还好协议简单我可以通过寥寥几行代码即可转换为 Json.NET的 JToken格式public static JToken DecodeStringToJObject(string str)
{ if (str.Contains(//)) // 数组 { var result new JArray(); foreach (var field in str.Split(new[] { // }, StringSplitOptions.RemoveEmptyEntries)) { result.Add(DecodeStringToJObject(field)); } return result; } if (str.Contains()) // 对象 { var result new JObject(); foreach (var field in str.Split(new[] { / }, StringSplitOptions.RemoveEmptyEntries)) { var tokens field.Split(new[] { }, StringSplitOptions.None); var k tokens[0]; var v UnscapeSlashAt(tokens[1]); result[k] DecodeStringToJObject(v); } return result; } else if (str.Contains(A)) // 键值对 { return DecodeStringToJObject(UnscapeSlashAt(str)); } else { return UnscapeSlashAt(str); // 值 }
}
static string EscapeSlashAt(string str)
{ return str .Replace(/, S) .Replace(, A);
}
static string UnscapeSlashAt(string str)
{ return str .Replace(S, /) .Replace(A, );
}这样一来即可将 STT格式转换为 JSON格式因此只需像 JSON格式取出 nn字段和 txt字段即可还有一个 col字段可以用来确定弹幕颜色我可以将其转换为 RGB的 int32值Color (x[col] ?? new JValue(0)).Valueint() switch
{ 1 0xff0000, // 红 2 0x1e87f0, // 浅蓝 3 0x7ac84b, // 浅绿 4 0xff7f00, // 橙色 5 0x9b39f4, // 紫色 6 0xff69b4, // 洋红 _ 0xffffff, // 默认白色
}该代码使用了 C# 8.0的 switchexpression功能可以一个表达式转成整个颜色转换比 if/else和 switch/case语句都精简不少可谓一气呵成。支持异步/ ValueTask/ MemoryT优化C# 5.0提供了强大的异步 API—— async/await通过异步API以前难以用编程实现的操作现在可以像写串行代码一样轻松完成还能轻松加入取消任务操作。然后 C# 7.0发布了 ValueTask ValueTask是值类型因此在频繁调用异步操作如使用 Stream读取字节时不会因为创建过多的 Task而分配没必要的内存。这里我确实是使用 TCP连接流读取字节是使用 ValueTask的最佳时机。这里我们将尝试将代码切换为 ValueTask版本。首先第一个问题是 BinaryReader类该类提供了便利的字节操作方式且能确保字节端为小端但该类不提供异步 API因此需要作一些特殊处理public static async Taskstring RecieveAsync(Stream stream, CancellationToken cancellationToken)
{ int fullMsgLength await ReadInt32().ConfigureAwait(false); int fullMsgLength2 await ReadInt32().ConfigureAwait(false); Debug.Assert(fullMsgLength fullMsgLength2); int length fullMsgLength - 1 - 4 - 4; short packType await ReadInt16().ConfigureAwait(false); Debug.Assert(packType ServerSendToClient); short encrypted await ReadByte().ConfigureAwait(false); Debug.Assert(encrypted Encrypted); short reserved await ReadByte().ConfigureAwait(false); Debug.Assert(reserved Reserved); Memorybyte bytes await ReadBytes(length).ConfigureAwait(false); byte zero await ReadByte().ConfigureAwait(false); Debug.Assert(zero ByteZero); return Encoding.UTF8.GetString(bytes.Span);
}如代码所示我封装了 ReadInt16()和 ReadInt32()两个方法var intBuffer new byte[4];
var int32Buffer new Memorybyte(intBuffer, 0, 4);
async ValueTaskint ReadInt32()
{ var memory int32Buffer; int read 0; while (read 4) { read await stream.ReadAsync(memory.Slice(read), cancellationToken).ConfigureAwait(false); } Debug.Assert(read memory.Length); return (intBuffer[0] 0) (intBuffer[1] 8) (intBuffer[2] 16) (intBuffer[3] 24);
}如图我还使用了一个 while语句因为不像 BinaryReader如果一次无法读取所需的字节数4个字节 stream.ReadAsync()并不会堵塞线程。然后需要将 int32Buffer转换为 int类型。注意此处我没有使用 BitConverter.ToInt32()也不能使用该方法因为该方法不像 BinaryReader它在大端/小端的 CPU上会有不同的行为。其中在大端 CPU上将有错误的行为涉及二进制序列化需要传输的不能使用 BitConverter类。同样的写 TCP流也需要有相应的变化static async Task SendAsync(Stream stream, byte[] body, CancellationToken cancellationToken)
{ var buffer new byte[4]; await stream.WriteAsync(GetBytesI32(4 4 body.Length 1), cancellationToken).ConfigureAwait(false); await stream.WriteAsync(GetBytesI32(4 4 body.Length 1), cancellationToken).ConfigureAwait(false); await stream.WriteAsync(GetBytesI16(ClientSendToServer), cancellationToken).ConfigureAwait(false); await stream.WriteAsync(new byte[] { Encrypted}, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(new byte[] { Reserved}, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(body, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(new byte[] { ByteZero}, cancellationToken).ConfigureAwait(false); Memorybyte GetBytesI32(int v) { buffer[0] (byte)v; buffer[1] (byte)(v 8); buffer[2] (byte)(v 16); buffer[3] (byte)(v 24); return new Memorybyte(buffer, 0, 4); } Memorybyte GetBytesI16(short v) { buffer[0] (byte)v; buffer[1] (byte)(v 8);; return new Memorybyte(buffer, 0, 2); }
}总结最终运行效果如下这一篇【DotNet骚操作】文章介绍了如何使用斗鱼tv开放弹幕 API下篇将会共享本文所使用的所有完整的源代码介绍如何使用 ReactiveExtensions RX演示这一系列操作用起来就像写 HelloWorld一样简单用我自制的“准游戏引擎” FlysEngine只需少量代码即可实现桌面弹幕的效果敬请期待“刷一波666???”