最近在给某个主播开发斗鱼直播间辅助工具,为了程序的高效稳定,也搜索了大量的资料,经过大量什么百度,谷歌搜索。。。

虽然有很多Python的脚本及JS脚本实现了拉取斗鱼弹幕信息,但是这些年来的开发职业病告诉我,这满足不了对系统的控制欲望。。

后来,找啊。。。找啊。。。意外间发现这个文档。。。。废话不多说了,说正题吧。

斗鱼很人性化的提供了一个基于Socket TCP传输协议的标准文档,通过接口我们可以安全稳定高效的获取斗鱼直播间弹幕信息,实现多种多样化的辅助功能。

一、协议组成

  众所周知,受TCP最大传输单(MTU)限制及连包机制影响,应用层协议需自己设计协议头,以保证不同消息的隔离性和消息完整性。

  斗鱼后台协议头设计如下:

字节 Byte0 Byte 1 Byte 2 Byte 3
长度 消息长度
头部 消息长度
消息类型 加密字段 保留字段
数据部 数据部分(结尾必须为 '\0')

  斗鱼消息协议格式如上所示,其中字段说明如下:

  消息长度:4字节小端整数,表示整条消息(包括自身)长度(字节数)。消息长度出现两遍,二者相同。

  消息类型:2字节小端整数,表示消息类型。取值如下:

    689  客户端发送给弹幕服务器的文本格式数据

    690  弹幕服务器发送给客户端的文本格式数据。

  加密字段:暂未使用,默认为0。

  保留字段:暂未使用,默认为0。

  数据部分:斗鱼独创序列化文本数据,结尾必须为 ‘\0’。详细序列化、反序列化算法见下节。(所有协议内容均为UTF-8编码)

二、序列化

  为增强兼容性、可读性斗鱼后台通讯协议采用文本形式的明文数据。同时针对平台数据特点,斗鱼自创序列化、反序列化算法。即STT序列化。下面详细

  介绍STT序列化和反序列化。STT序列化支持键值对类型、数组类型(意外发现有的报文还有JSON类型)。规定如下:

  1、键key和值value直接采用 '@='分割

  2、数组采用 '/' 分割

  3、如果key或者value中含有字符 '/',则使用 '@S' 转义

  4、如果key或者value中含有字符 '@',则使用 '@A' 转义

  举例:

    (1)多个键值对数据:key1@=value1/key2@=value2/key3@=value3/

    (2)数组数组:value1/value2/value3/

  不同消息有相同的协议头、序列化方式。

三、客户端消息格式(部分)

  1、登录请求消息

    该消息用于完成登陆授权,完整的数据部分应包含的字段如下:

    type@=loginreq/roomid@=58839/

    type:表示登陆请求消息,固定为loginreq

    roomid:登陆房间的ID

  2、客户端心跳消息

    该消息用于维持与后台间的心跳,完整的数据部分应包含的字段如下:

    type@=mrkl/

    type:表示心跳消息,固定为mrkl

  3、加入房间分组消息

    该消息用于完成加入房间分组,完整的数据部分应包含的字段如下:

    type@=joingroup/rid@=59872/gid@=-9999/

    type:表示为加入房间分组消息,固定为joingroup

    rid:所登录的房间号

    gid:分组号,第三方平台建议选择-9999(即海量弹幕模式)

  4、登出消息

    type@=logout/

    该消息用于完成登出后台服务,完整的数据部分应包含的字段如下:

    type:表示为登出消息,固定为logout

三、实现斗鱼直播弹幕服务器API

  现在网上可以轻松找到《斗鱼弹幕服务器第三方接入协议v1.6.2》接口文档,在文档中有提到两个重要的数据:

    弹幕服务器地址:openbarrage.douyutv.com

    弹幕服务器端口:8601

  我们可以通过.NET Framework 提供的TcpClient类库来方便连接SOCKET弹幕服务器。为了实现服务的稳定性,我这里使用了异步SOCKET客户端完成连接。

  1、弹幕服务器报文头:

  1. /// <summary>
  2. /// 弹幕报文头
  3. /// </summary>
  4. [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
  5. public struct BARRAGE_PACKAGE
  6. {
  7. /// <summary>
  8. /// 长度
  9. /// </summary>
  10. public int dwLen;
  11. /// <summary>
  12. /// 长度
  13. /// </summary>
  14. public int dwLen2;
  15. /// <summary>
  16. /// 发送方向
  17. /// </summary>
  18. public Int16 bType;
  19. /// <summary>
  20. /// 加密字段(保留)
  21. /// </summary>
  22. public byte encrypt;
  23. /// <summary>
  24. /// 备注字段(保留)
  25. /// </summary>
  26. public byte reserved;
  27. }

  2、异步套接字格式

  1. // <summary>
  2. /// 套接字数据
  3. /// </summary>
  4. public class SOCKET_PACKAGE
  5. {
  6. /// <summary>
  7. /// Socket套接字主对象
  8. /// </summary>
  9. public Socket Socket = null;
  10. /// <summary>
  11. /// 缓冲区大小
  12. /// </summary>
  13. public const int BufferSize = 4;    // 说明一下,这里由于有的包并不够1024缓冲区,经过大量测试,缓冲区设置为4最合适了
  14. /// <summary>
  15. /// 套接字缓冲区
  16. /// </summary>
  17. public byte[] SocketBuffer = new byte[BufferSize];
  18. /// <summary>
  19. /// 套接字流缓存
  20. /// </summary>
  21. public NetworkStream Stream = null;
  22. }

  3、SOCKET帮助类

    这个类封装了直接通过NetworkStream对象并格式化报文向斗鱼发送报文(仅仅为了提高开发效率)

  1. #region SOCKET帮助类
  2. /// <summary>
  3. /// SOCKET帮助类
  4. /// </summary>
  5. public static class SocketHelper
  6. {
  7. /// <summary>
  8. /// 发送斗鱼报文
  9. /// </summary>
  10. /// <param name="message"></param>
  11. /// <param name="ms"></param>
  12. /// <returns></returns>
  13. public static void LiveMessagePush(string message, NetworkStream ms)
  14. {
  15. #region 斗鱼报文
  16. BARRAGE_PACKAGE package = new BARRAGE_PACKAGE();
  17. package.bType = 689;
  18. byte[] buffer = Encoding.UTF8.GetBytes(message);
  19. package.dwLen = buffer.Length + 8;
  20. package.dwLen2 = package.dwLen;
  21. package.encrypt = 0x00;
  22. package.reserved = 0x00;
  23. #endregion
  24.  
  25. #region 发送数据
  26. byte[] block = new byte[buffer.Length + 12];
  27. Array.Copy(StreamSerializationHelper.StructureToBytes(package), 0, block, 0, 12);
  28. Array.Copy(buffer, 0, block, 12, buffer.Length);
  29. ms.Write(block, 0, block.Length);
  30. ms.Flush();
  31. #endregion
  32. }
  33. }
  34. #endregion

  这里可能会有人问到 StreamSerializationHelper这个类库从哪里来的,这个是自己写的一个实现对struct结构体序列化的方法。下面也提供一下,如果有更好的可自行更换:)

  1. /// <summary>
  2. /// 本基类提供和二进制结构体数据处理的相关函数,这里包含的所有方法都是与标准语言二进制结构体操作
  3. /// 相关函数
  4. /// </summary>
  5. /// <remarks>
  6. /// 本基类提供和二进制结构体数据处理的相关函数。这里采用静态方法的形式提供出各种数据对象进行互转
  7. /// 的方法
  8. /// <list type="bullet">
  9. /// <item>二进制文件到结构体的转换</item>
  10. /// <item>结构体文件转换为二进制数据</item>
  11. /// </list>
  12. /// </remarks>
  13. public static class StreamSerializationHelper
  14. {
  15. /// <summary>
  16. /// 将托管格式结构体转换为byte数组格式
  17. /// </summary>
  18. /// <param name="graph">源数据</param>
  19. /// <returns></returns>
  20. public static byte[] StructureToBytes(object graph)
  21. {
  22. // 获取数据结构体大小(非托管)
  23. int dwStructureSize = Marshal.SizeOf(graph);
  24. // 从进程的非托管内存中分配内存
  25. IntPtr iter = Marshal.AllocHGlobal(dwStructureSize);
  26. // 将数据从托管对象封装送往非托管内存块
  27. Marshal.StructureToPtr(graph, iter, true);
  28. // 分配指定大小数组块
  29. byte[] mBytes = new byte[dwStructureSize];
  30. // 将数据从非托管内存复制到托管数组中
  31. Marshal.Copy(iter, mBytes, 0, dwStructureSize);
  32. Marshal.FreeHGlobal(iter);
  33. return mBytes;
  34. }
  35. /// <summary>
  36. /// 将非托管数组转换至托管结构体
  37. /// </summary>
  38. /// <typeparam name="T">数据类型</typeparam>
  39. /// <param name="graph">非托管数组</param>
  40. /// <returns></returns>
  41. public static T BytesToStructure<T>(byte[] graph)
  42. {
  43. // 获取数据结构体大小(托管)
  44. int dwStructureSize = Marshal.SizeOf(typeof(T));
  45. // 从进程的非托管内存中分配内存
  46. IntPtr iter = Marshal.AllocHGlobal(dwStructureSize);
  47. // 将数据从托管内存数组复制到非托管内存指针
  48. Marshal.Copy(graph, 0, iter, dwStructureSize);
  49. // 将数据从非托管内存块送到新分配并指定类型的托管对象并返回
  50. T obj = (T)Marshal.PtrToStructure(iter, typeof(T));
  51.  
  52. Marshal.FreeHGlobal(iter);
  53. return obj;
  54. }
  55.  
  56. /// <summary>
  57. /// 通过序列化复制对象
  58. /// </summary>
  59. /// <param name="graph"></param>
  60. /// <returns></returns>
  61. public static object CloneObject(object graph)
  62. {
  63. ExceptionHelper.FalseThrow<ArgumentNullException>(graph != null, "graph");
  64.  
  65. using (MemoryStream memoryStream = new MemoryStream(1024))
  66. {
  67. BinaryFormatter formatter = new BinaryFormatter();
  68.  
  69. formatter.Serialize(memoryStream, graph);
  70.  
  71. memoryStream.Position = 0;
  72.  
  73. return formatter.Deserialize(memoryStream);
  74. }
  75. }
  76. }

  4、实现登陆弹幕服务器代码如下:

  

  1. #region 私有变量
  2. int dwMrkl = Environment.TickCount; // 记录执行的时间,因为斗鱼规定每45秒要向斗鱼发送一次心跳消息(否则踢下线)
  3. #endregion
  4.  
  5. #region 连接弹幕
  6. TcpClient tcpClient = new TcpClient();
  7. tcpClient.Connect("openbarrage.douyutv.com",8601);
  8. #endregion
  9.  
  10. #region 网络数据
  11. using (NetworkStream ms = tcpClient.GetStream())
  12. {
  13. #region 登陆请求
  14. SocketHelper.LiveMessagePush(string.Format("type@=loginreq/roomid@={0}/\0", 99999), ms);
  15. #endregion
  16.  
  17. #region 接收数据
  18. while (environment_semaphore && tcpClient.Connected)
  19. {
  20. #region 发送心跳
  21. if (!ms.DataAvailable && tcpClient.Connected)
  22. {
  23. // 不管是否有数据,只要SOCKET连接那么就进行心跳判断
  24.  
  25. if (Environment.TickCount - dwMrkl >= 45000)
  26. {
  27. dwMrkl = Environment.TickCount; // 重新计算心跳消息时间
  28.  
  29. SocketHelper.LiveMessagePush("type@=mrkl/\0", ms);
  30. }
  31.  
  32. Thread.Sleep(5);
  33. continue;
  34. }
  35. #endregion
  36.  
  37. #region 发送心跳
  38. if (Environment.TickCount - dwMrkl >= 45000)
  39. {
  40. dwMrkl = Environment.TickCount;
  41.  
  42. SocketHelper.LiveMessagePush("type@=mrkl/\0", ms);
  43. }
  44.  
  45. #region 数据处理
  46. byte[] buffer = new byte[SOCKET_PACKAGE.BufferSize];
  47.  
  48. ms.Read(buffer, 0, buffer.Length);
  49.  
  50. int dwLen = BitConverter.ToInt32(buffer, 0);
  51.  
  52. int unReadOfBytes = dwLen;
  53. #endregion
  54.  
  55. #region 报文处理
  56. using (MemoryStream s = new MemoryStream())
  57. {
  58. #region 粘包处理
  59. // 大家都知道TCP有粘包数据,因为不是优雅的一问一答式,所以要自行处理,这是我想到的最简单处理粘包的办法咯
  60. do
  61. {
  62. buffer = new byte[unReadOfBytes >= 1024 ? 1024 : unReadOfBytes];
  63.  
  64. int dwBytesOfRead = ms.Read(buffer, 0, buffer.Length);
  65.  
  66. s.Write(buffer, 0, dwBytesOfRead);
  67.  
  68. unReadOfBytes -= dwBytesOfRead;
  69.  
  70. } while (unReadOfBytes > 0);
  71.  
  72. s.Position = 0;
  73. #endregion
  74.  
  75. #region 报文处理
  76. if (true)
  77. {
  78. string content = Encoding.UTF8.GetString(s.ToArray(), 8, dwLen - 8);
  79.  
  80. foreach (string target in Regex.Split(content, "/", RegexOptions.IgnoreCase))
  81. {
  82. if (!string.IsNullOrWhiteSpace(target))
  83. {
  84. string[] items = Regex.Split(target, "@=", RegexOptions.IgnoreCase);
  85.  
  86. if (string.Compare("type", items[0], true) == 0 && string.Compare("loginres", items[1], true) == 0)
  87. {
                              // 当我们收到loginres消息后再发送加入房间分组消息
  88. SocketHelper.LiveMessagePush(string.Format("type@=joingroup/rid@={0}/gif@=-9999/\0", 99999), ms);
  89. }
  90.  
  91. if (string.Compare("type", items[0], true) == 0 && string.Compare("loginres", items[1], true) != 0)
  92. {
  93. string message_type = items[1].Replace("@S", "/").Replace("@A", "@");
  94.  
  95. if (!string.IsNullOrWhiteSpace(message_type) && string.Compare("mrkl", message_type, true) != 0)
  96. {
  97. // 这里拿到的content数据就是不含心跳报文的数据,具体要怎么处理看你自己需求了
  98. // TO DO :
  99. }
  100. }
  101. }
  102. }
  103. }
  104. #endregion
  105. }
  106. #endregion
  107. }
  108. #endregion
  109. }
  110. #endregion

  好了,上面就是基本全部代码了,具体的自行研究吧,有空的话提供大家一些报文的详情数据。

【Visual C#】基于《斗鱼弹幕服务器第三方接入协议v1.6.2》实现斗鱼弹幕服务器接入的更多相关文章

  1. 基于QT开发的第三方库

    基于Qt开发的第三方库 分类: Qt2014-02-12 11:34 1738人阅读 评论(0) 收藏 举报 QT第三方库   目录(?)[+]   文章来源:http://blog.csdn.net ...

  2. 基于OAuth2.0的第三方认证

    浅显易懂的解释 来源 yahoo OAuth认证 原理 理解OAuth 2.0:原理.分类 一张图搞定OAuth2.0:是什么,怎么用 应用自身,完成用户认证: 缺点: 1.不同的访问Web应用提供不 ...

  3. Python基于Python实现批量上传文件或目录到不同的Linux服务器

    基于Python实现批量上传文件或目录到不同的Linux服务器   by:授客 QQ:1033553122 实现功能 1 测试环境 1 使用方法 1 1. 编辑配置文件conf/rootpath_fo ...

  4. 下载远程(第三方服务器)文件、图片,保存到本地(服务器)的方法、保存抓取远程文件、图片 将图片的二进制字节字符串在HTML页面以图片形式输出 asp.net 文件 操作方法

    下载远程(第三方服务器)文件.图片,保存到本地(服务器)的方法.保存抓取远程文件.图片   将一台服务器的文件.图片,保存(下载)到另外一台服务器进行保存的方法: 1 #region 图片下载 2 3 ...

  5. 基于 HTTP/2 的全新 APNs 协议

    https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotifi ...

  6. 基于Socket通讯(C#)和WebSocket协议(net)编写的两种聊天功能(文末附源码下载地址)

    今天我们来盘一盘Socket通讯和WebSocket协议在即时通讯的小应用——聊天. 理论大家估计都知道得差不多了,小编也通过查阅各种资料对理论知识进行了充电,发现好多demo似懂非懂,拷贝回来又运行 ...

  7. 每个大主播都是满屏弹幕,怎么做到的?Python实战无限刷弹幕!

    anmu 是一个开源的直播平台弹幕接口,使用他没什么基础的你也可以轻松的操作各平台弹幕.使用不到三十行代码,你就可以使用Python基于弹幕进一步开发.支持斗鱼.熊猫.战旗.全民.Bilibili多平 ...

  8. 服务器标配 SSH 协议,你了解多少?

    年初,新冠肺炎疫情的出现,全国数千万名员工在家远程办公,使用个人设备通过家庭网络访问公司资料.因此,IT 安全团队面临了众多新挑战:如何实施更加安全的身份验证方案,以确保只有授权人员和设备才能访问公司 ...

  9. VMware vSphere 服务器虚拟化之十七 桌面虚拟化之安装View链接服务器

    VMware vSphere 服务器虚拟化之十七 桌面虚拟化之安装View链接服务器 View链接服务器(View Connection Server)是Vmware Horizon View桌面虚拟 ...

  10. Linux 高性能服务器编程——TCP协议详解

    问题聚焦:     本节从如下四个方面讨论TCP协议:     TCP头部信息:指定通信的源端端口号.目的端端口号.管理TCP连接,控制两个方向的数据流     TCP状态转移过程:TCP连接的任意一 ...

随机推荐

  1. bug单建单规范

      bug提单保证,清晰.简单.明了. 标题: [版本][服务器][模块][必现/偶现]bug标题(最短的话描述bug) 例:[0.9.0][dev][系统][必现]点击商店,跳转到仓库页面 bug模 ...

  2. [转]sublime text 4注册

    1.打开浏览器进入网站https://hexed.it2.打开sublime text4安装目录选择文件sublime_text.exe3.搜索80 78 05 00 0f 94 c1更改为c6 40 ...

  3. Windows系统下找到占用当前端口的进程

    在进行服务调试时如果遇到端口冲突而不自知,可能会出现莫名其妙的错误.因此在不确定的情况下最好先查看要用的端口是否被占用. 下面介绍查看端口占用情况方法. 比如现在是要找到端口为8088的占用,在cmd ...

  4. 关于office 16

    word是office的组件之一,Excel也是其中之一. 一用有八大组件.  

  5. python打包成exe过程中遇到的问题

    先描述下初始状况: python版本为3.7.3,直接在cmd中运行pip安装pyinstaller失败,应该是最开始安装python时没有把目录添加到环境变量中(我很懒).直接在python的安装目 ...

  6. linux查看已知进程PID所在的目录

    pwdx 命令 pwdx PID [was@CMTRMWAS1 ~]$ pwdx 31996 31996: /was/AppServer/profiles/AppSrv03

  7. PHP 文件和文件夹操作

    文件夹操作 创建文件夹 mkdir(名称,权限,递归创建):创建文件 例如: #创建文件夹 mkdir('./aa') # 创建 aa 文件夹 mkdir('./aa/bb') # 在 aa 目录下创 ...

  8. DVWA-Insecure CAPTCHA(不安全的验证码)

    Insecure CAPTCHA,意思为不安全的验证码 全称为Completely Automated Public Turing Test to Tell Computers and Humans ...

  9. (原创)【B4A】一步一步入门07:EditText,文本框、密码框、键盘样式、提示文本(控件篇03)

    一.前言 本篇教程,我们来讲一下常用的控件:EditText(文本输入框). 本篇教程将会讲解文本框的基本使用,如:提示文本.密码文本.键盘样式等. 相信看完的你,一定会有所收获! 本文地址:http ...

  10. springsecurity-jwt整合

    2 springsecurity-jwt整合 欢迎关注博主公众号「Java大师」, 专注于分享Java领域干货文章http://www.javaman.cn/sb2/jwt 2.1整合springse ...