【Visual C#】基于《斗鱼弹幕服务器第三方接入协议v1.6.2》实现斗鱼弹幕服务器接入
最近在给某个主播开发斗鱼直播间辅助工具,为了程序的高效稳定,也搜索了大量的资料,经过大量什么百度,谷歌搜索。。。
虽然有很多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、弹幕服务器报文头:
/// <summary>
/// 弹幕报文头
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct BARRAGE_PACKAGE
{
/// <summary>
/// 长度
/// </summary>
public int dwLen;
/// <summary>
/// 长度
/// </summary>
public int dwLen2;
/// <summary>
/// 发送方向
/// </summary>
public Int16 bType;
/// <summary>
/// 加密字段(保留)
/// </summary>
public byte encrypt;
/// <summary>
/// 备注字段(保留)
/// </summary>
public byte reserved;
}
2、异步套接字格式
// <summary>
/// 套接字数据
/// </summary>
public class SOCKET_PACKAGE
{
/// <summary>
/// Socket套接字主对象
/// </summary>
public Socket Socket = null;
/// <summary>
/// 缓冲区大小
/// </summary>
public const int BufferSize = 4; // 说明一下,这里由于有的包并不够1024缓冲区,经过大量测试,缓冲区设置为4最合适了
/// <summary>
/// 套接字缓冲区
/// </summary>
public byte[] SocketBuffer = new byte[BufferSize];
/// <summary>
/// 套接字流缓存
/// </summary>
public NetworkStream Stream = null;
}
3、SOCKET帮助类
这个类封装了直接通过NetworkStream对象并格式化报文向斗鱼发送报文(仅仅为了提高开发效率)
#region SOCKET帮助类
/// <summary>
/// SOCKET帮助类
/// </summary>
public static class SocketHelper
{
/// <summary>
/// 发送斗鱼报文
/// </summary>
/// <param name="message"></param>
/// <param name="ms"></param>
/// <returns></returns>
public static void LiveMessagePush(string message, NetworkStream ms)
{
#region 斗鱼报文
BARRAGE_PACKAGE package = new BARRAGE_PACKAGE();
package.bType = 689;
byte[] buffer = Encoding.UTF8.GetBytes(message);
package.dwLen = buffer.Length + 8;
package.dwLen2 = package.dwLen;
package.encrypt = 0x00;
package.reserved = 0x00;
#endregion #region 发送数据
byte[] block = new byte[buffer.Length + 12];
Array.Copy(StreamSerializationHelper.StructureToBytes(package), 0, block, 0, 12);
Array.Copy(buffer, 0, block, 12, buffer.Length);
ms.Write(block, 0, block.Length);
ms.Flush();
#endregion
}
}
#endregion
这里可能会有人问到 StreamSerializationHelper这个类库从哪里来的,这个是自己写的一个实现对struct结构体序列化的方法。下面也提供一下,如果有更好的可自行更换:)
/// <summary>
/// 本基类提供和二进制结构体数据处理的相关函数,这里包含的所有方法都是与标准语言二进制结构体操作
/// 相关函数
/// </summary>
/// <remarks>
/// 本基类提供和二进制结构体数据处理的相关函数。这里采用静态方法的形式提供出各种数据对象进行互转
/// 的方法
/// <list type="bullet">
/// <item>二进制文件到结构体的转换</item>
/// <item>结构体文件转换为二进制数据</item>
/// </list>
/// </remarks>
public static class StreamSerializationHelper
{
/// <summary>
/// 将托管格式结构体转换为byte数组格式
/// </summary>
/// <param name="graph">源数据</param>
/// <returns></returns>
public static byte[] StructureToBytes(object graph)
{
// 获取数据结构体大小(非托管)
int dwStructureSize = Marshal.SizeOf(graph);
// 从进程的非托管内存中分配内存
IntPtr iter = Marshal.AllocHGlobal(dwStructureSize);
// 将数据从托管对象封装送往非托管内存块
Marshal.StructureToPtr(graph, iter, true);
// 分配指定大小数组块
byte[] mBytes = new byte[dwStructureSize];
// 将数据从非托管内存复制到托管数组中
Marshal.Copy(iter, mBytes, 0, dwStructureSize);
Marshal.FreeHGlobal(iter);
return mBytes;
}
/// <summary>
/// 将非托管数组转换至托管结构体
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="graph">非托管数组</param>
/// <returns></returns>
public static T BytesToStructure<T>(byte[] graph)
{
// 获取数据结构体大小(托管)
int dwStructureSize = Marshal.SizeOf(typeof(T));
// 从进程的非托管内存中分配内存
IntPtr iter = Marshal.AllocHGlobal(dwStructureSize);
// 将数据从托管内存数组复制到非托管内存指针
Marshal.Copy(graph, 0, iter, dwStructureSize);
// 将数据从非托管内存块送到新分配并指定类型的托管对象并返回
T obj = (T)Marshal.PtrToStructure(iter, typeof(T)); Marshal.FreeHGlobal(iter);
return obj;
} /// <summary>
/// 通过序列化复制对象
/// </summary>
/// <param name="graph"></param>
/// <returns></returns>
public static object CloneObject(object graph)
{
ExceptionHelper.FalseThrow<ArgumentNullException>(graph != null, "graph"); using (MemoryStream memoryStream = new MemoryStream(1024))
{
BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(memoryStream, graph); memoryStream.Position = 0; return formatter.Deserialize(memoryStream);
}
}
}
4、实现登陆弹幕服务器代码如下:
#region 私有变量
int dwMrkl = Environment.TickCount; // 记录执行的时间,因为斗鱼规定每45秒要向斗鱼发送一次心跳消息(否则踢下线)
#endregion #region 连接弹幕
TcpClient tcpClient = new TcpClient();
tcpClient.Connect("openbarrage.douyutv.com",8601);
#endregion #region 网络数据
using (NetworkStream ms = tcpClient.GetStream())
{
#region 登陆请求
SocketHelper.LiveMessagePush(string.Format("type@=loginreq/roomid@={0}/\0", 99999), ms);
#endregion #region 接收数据
while (environment_semaphore && tcpClient.Connected)
{
#region 发送心跳
if (!ms.DataAvailable && tcpClient.Connected)
{
// 不管是否有数据,只要SOCKET连接那么就进行心跳判断 if (Environment.TickCount - dwMrkl >= 45000)
{
dwMrkl = Environment.TickCount; // 重新计算心跳消息时间 SocketHelper.LiveMessagePush("type@=mrkl/\0", ms);
} Thread.Sleep(5);
continue;
}
#endregion #region 发送心跳
if (Environment.TickCount - dwMrkl >= 45000)
{
dwMrkl = Environment.TickCount; SocketHelper.LiveMessagePush("type@=mrkl/\0", ms);
} #region 数据处理
byte[] buffer = new byte[SOCKET_PACKAGE.BufferSize]; ms.Read(buffer, 0, buffer.Length); int dwLen = BitConverter.ToInt32(buffer, 0); int unReadOfBytes = dwLen;
#endregion #region 报文处理
using (MemoryStream s = new MemoryStream())
{
#region 粘包处理
// 大家都知道TCP有粘包数据,因为不是优雅的一问一答式,所以要自行处理,这是我想到的最简单处理粘包的办法咯
do
{
buffer = new byte[unReadOfBytes >= 1024 ? 1024 : unReadOfBytes]; int dwBytesOfRead = ms.Read(buffer, 0, buffer.Length); s.Write(buffer, 0, dwBytesOfRead); unReadOfBytes -= dwBytesOfRead; } while (unReadOfBytes > 0); s.Position = 0;
#endregion #region 报文处理
if (true)
{
string content = Encoding.UTF8.GetString(s.ToArray(), 8, dwLen - 8); foreach (string target in Regex.Split(content, "/", RegexOptions.IgnoreCase))
{
if (!string.IsNullOrWhiteSpace(target))
{
string[] items = Regex.Split(target, "@=", RegexOptions.IgnoreCase); if (string.Compare("type", items[0], true) == 0 && string.Compare("loginres", items[1], true) == 0)
{
// 当我们收到loginres消息后再发送加入房间分组消息
SocketHelper.LiveMessagePush(string.Format("type@=joingroup/rid@={0}/gif@=-9999/\0", 99999), ms);
} if (string.Compare("type", items[0], true) == 0 && string.Compare("loginres", items[1], true) != 0)
{
string message_type = items[1].Replace("@S", "/").Replace("@A", "@"); if (!string.IsNullOrWhiteSpace(message_type) && string.Compare("mrkl", message_type, true) != 0)
{
// 这里拿到的content数据就是不含心跳报文的数据,具体要怎么处理看你自己需求了
// TO DO :
}
}
}
}
}
#endregion
}
#endregion
}
#endregion
}
#endregion
好了,上面就是基本全部代码了,具体的自行研究吧,有空的话提供大家一些报文的详情数据。
【Visual C#】基于《斗鱼弹幕服务器第三方接入协议v1.6.2》实现斗鱼弹幕服务器接入的更多相关文章
- 基于QT开发的第三方库
基于Qt开发的第三方库 分类: Qt2014-02-12 11:34 1738人阅读 评论(0) 收藏 举报 QT第三方库 目录(?)[+] 文章来源:http://blog.csdn.net ...
- 基于OAuth2.0的第三方认证
浅显易懂的解释 来源 yahoo OAuth认证 原理 理解OAuth 2.0:原理.分类 一张图搞定OAuth2.0:是什么,怎么用 应用自身,完成用户认证: 缺点: 1.不同的访问Web应用提供不 ...
- Python基于Python实现批量上传文件或目录到不同的Linux服务器
基于Python实现批量上传文件或目录到不同的Linux服务器 by:授客 QQ:1033553122 实现功能 1 测试环境 1 使用方法 1 1. 编辑配置文件conf/rootpath_fo ...
- 下载远程(第三方服务器)文件、图片,保存到本地(服务器)的方法、保存抓取远程文件、图片 将图片的二进制字节字符串在HTML页面以图片形式输出 asp.net 文件 操作方法
下载远程(第三方服务器)文件.图片,保存到本地(服务器)的方法.保存抓取远程文件.图片 将一台服务器的文件.图片,保存(下载)到另外一台服务器进行保存的方法: 1 #region 图片下载 2 3 ...
- 基于 HTTP/2 的全新 APNs 协议
https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotifi ...
- 基于Socket通讯(C#)和WebSocket协议(net)编写的两种聊天功能(文末附源码下载地址)
今天我们来盘一盘Socket通讯和WebSocket协议在即时通讯的小应用——聊天. 理论大家估计都知道得差不多了,小编也通过查阅各种资料对理论知识进行了充电,发现好多demo似懂非懂,拷贝回来又运行 ...
- 每个大主播都是满屏弹幕,怎么做到的?Python实战无限刷弹幕!
anmu 是一个开源的直播平台弹幕接口,使用他没什么基础的你也可以轻松的操作各平台弹幕.使用不到三十行代码,你就可以使用Python基于弹幕进一步开发.支持斗鱼.熊猫.战旗.全民.Bilibili多平 ...
- 服务器标配 SSH 协议,你了解多少?
年初,新冠肺炎疫情的出现,全国数千万名员工在家远程办公,使用个人设备通过家庭网络访问公司资料.因此,IT 安全团队面临了众多新挑战:如何实施更加安全的身份验证方案,以确保只有授权人员和设备才能访问公司 ...
- VMware vSphere 服务器虚拟化之十七 桌面虚拟化之安装View链接服务器
VMware vSphere 服务器虚拟化之十七 桌面虚拟化之安装View链接服务器 View链接服务器(View Connection Server)是Vmware Horizon View桌面虚拟 ...
- Linux 高性能服务器编程——TCP协议详解
问题聚焦: 本节从如下四个方面讨论TCP协议: TCP头部信息:指定通信的源端端口号.目的端端口号.管理TCP连接,控制两个方向的数据流 TCP状态转移过程:TCP连接的任意一 ...
随机推荐
- dotNetCore创建Windows服务程序并安装服务
一.创建控制台程序 二.在项目中添加新建项,选择Windows服务类型. 此时会出现一个错误提示,这是因为尚未添加windows服务控制引用造成的. 三.添加Nuget包,System.Service ...
- MySQLdb安装
yum seach MySQL-Python sudo yum install MySQL-python.x86_64 import MySQLdb
- eclipse console 控制台输出乱码解决办法
一.console输出日志显示乱码 二.在类编辑处点击右键 Run As --> Run Configurations 三.在Common中设置字符集 gbk 四.restart 搜索 复制
- 还不知道如何在java中终止一个线程?快来,一文给你揭秘
目录 简介 Thread.stop被禁用之谜 怎么才能安全? 捕获异常之后的处理 总结 简介 工作中我们经常会用到线程,一般情况下我们让线程执行就完事了,那么你们有没有想过如何去终止一个正在运行的线程 ...
- GO语言学习笔记-并发篇 Study for Go ! Chapter seven - Concurrency
持续更新 Go 语言学习进度中 ...... GO语言学习笔记-类型篇 Study for Go! Chapter one - Type - slowlydance2me - 博客园 (cnblogs ...
- ASP.NET Core Web API Swagger 按标签Tags分组排序显示
需求 swagger页面按标签Tags分组显示. 没有打标签Tags的接口,默认归到"未分组". 分组内按接口路径排序 说明 为什么没有使用GroupName对接口进行分组? 暂时 ...
- 如何基于 React Native 快速实现一个视频通话应用
今天,我们将会一起开发一个包含 RTE (实时互动)场景的 Flutter 应用. 项目介绍 靠自研开发包含实时互动功能的应用非常繁琐,你要解决维护服务器.负载均衡等难题,同时还要保证稳定的低延迟. ...
- Unity实现3D物体遮挡血条
Unity 实现3D物体遮挡血条 前言:在游戏开发中,我们经常会遇到UI和3D物体的层级遮挡问题,最常见的比如血条跟随敌人的时候,多个敌人的血条会遮挡住玩家或者3D物体,去网上查了一下也没有很好的解决 ...
- TCP三次握手一二三问
下面整理下TCP握手和挥手的几个问题,参考资料小林图解计算机网络 1.什么是三次握手? Client端向Server端发送SYN为1的报文段,携带一个初始序列号x,client端进入SYN_SENT状 ...
- Go语言:一文看懂什么是DI依赖注入(dependency injection)设计模式
前言: 本文主要介绍的是Goalng中关于 DI 的部分,前一部分会先通过典型的面向对象语言Java引入DI这个概念 仅供初学者理解使用,文章如有纰漏敬请指出 本文涉及到的知识面较为零散,其中包含面向 ...