【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)多个键值对数据:[email protected]=value1/[email protected]=value2/[email protected]=value3/

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

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

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

  1、登录请求消息

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

    [email protected]=loginreq/[email protected]=58839/

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

    roomid:登陆房间的ID

  2、客户端心跳消息

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

    [email protected]=mrkl/

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

  3、加入房间分组消息

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

    [email protected]=joingroup/[email protected]=59872/[email protected]=-9999/

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

    rid:所登录的房间号

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

  4、登出消息

    [email protected]=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("[email protected]=loginreq/[email protected]={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("[email protected]=mrkl/\0", ms);
                            }

                            Thread.Sleep(5);
                            continue;
                        }
                        #endregion

                        #region 发送心跳
                        if (Environment.TickCount - dwMrkl >= 45000)
                        {
                            dwMrkl = Environment.TickCount;

                            SocketHelper.LiveMessagePush("[email protected]=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("[email protected]=joingroup/[email protected]={0}/[email protected]=-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

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

原文地址:https://www.cnblogs.com/briny/p/12653625.html

时间: 2024-08-02 13:55:27

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

基于QT开发的第三方库

基于Qt开发的第三方库 分类: Qt2014-02-12 11:34 1738人阅读 评论(0) 收藏 举报 QT第三方库 目录(?)[+] 文章来源:http://blog.csdn.net/zhgn2/article/details/14613519 QxOrm QxOrm is a new open source ORM (Object Relational Mapping) C++ library designed to provide : * Persistence (based on

基于eclipse 的 SVN 重定向的方法 和 ibaits(mybaits)在变更了服务器地址之后需要修改的文件

今天,服务器更换了地址,于是简单总结一下基于eclipse 的 SVN  重定向的方法 和 ibaits(mybaits)在变更了服务器地址之后需要修改的文件. 1.首先调整到eclipse 的 svn视图下,然后对着原来的项目点击右键,选择重定向.如下图所示: 2.选择适当的项目,如下图 3.最后修改URL即可 4.因为使用了ibaits(Mybaits),因此还需要修改相关的文件,如下图所示,那个HQ的是我自己的,其他人应该没有

基于 HTTP/2 的全新 APNs 协议

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1 前言: APNs 协议在近两年的 WWDC 上改过两次,2015年12月17日更是推出了革命性的新特性.但在国内传播的博客.面试题里关于APNs的

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

anmu 是一个开源的直播平台弹幕接口,使用他没什么基础的你也可以轻松的操作各平台弹幕.使用不到三十行代码,你就可以使用Python基于弹幕进一步开发.支持斗鱼.熊猫.战旗.全民.Bilibili多平台弹幕.支持各版本Python,无平台依赖,方便各类开发者.爱好者使用.一次开启,主播上线自动连接,下线后上线自动重连. 给喜欢的主播来个爱的轰炸. 经过深度优化,几乎不漏过任何一条弹幕:使用抽象构架,方便修改与开发. 欢迎加入柯西学python群:725479218,欢迎大家加入!!!!! Doc

基于X.509证书和SSL协议的身份认证过程实现(OpenSSL可以自己产生证书,有TCP通过SSL进行实际安全通讯的实际编程代码)good

上周帮一个童鞋做一个数字认证的实验,要求是编程实现一个基于X.509证书认证的过程,唉!可怜我那点薄弱的计算机网络安全的知识啊!只得恶补一下了. 首先来看看什么是X.509.所谓X.509其实是一种非常通用的证书,什么是证书?唉!这么说吧!当两个人需要进行远程通信而又不想让第三个人知道时就必须建立一种安全措施,因为看不到对方的脸,又不能通过电话直接询问对方,就得想点别的办法,比如我设计一个密码,让后发短信告诉你,这样当我们在网上交流之前就可以对一下密码,暗号之类的.确认后就可以证明你的身份了.这

多个服务器总结: 将session保存到专门的一个服务器上,所有服务器都去请求数据共享Session

原理:多个服务器间想共享session,就相当于共享取多台主机上的一个变量,所以共享的思路就是让大家都能读取变量,实现的方法可以通过将session保存到专门的一个服务器上,所有服务器都去请求数据,也memcache实现session共享将这些服务器都配置成使用同一组Memcached服务器就可以, 一.提出问题: 为了满足足够大的应用,满足更多的客户,于是我们架设了N台Web服务器(N>=2),在多台Web服务器的情况下,我们会涉及到一个问题:用户登陆一台服务器以后,如果在跨越到另一台服务器的

Unity3d Android SDK接入解析(四)通用的Android SDK接入中间件

一.前言 接入Android SDK正式告一段落,在这段时间里面,依次接入了华为.应用宝.小米.360等等大大小小十来个SDK,也算对Unity接入渠道SDK有了较为全面的理解,对各个渠道的坑也算深有体会....在接入过程中时间比较紧张,没办法抽空来进行总结深思.今天正好有空,便对之前的接入SDK的代码进行了一次重构,写了一个比较通用的Unity接入Android SDK的中间件,前人栽树,后人乘凉. 进入正题 如果有对一些只是有疑问的,可以看看我之前的三篇文章: 传送门: Unity3d An

Tomcat服务器与HTTP协议

Tomcat服务器与HTTP协议 一.  Tomcat服务器 1.tomcat服务器 1.web :网页,它代表的是网络上的资源.(java技术开发动态的web资源,即动态web页面,在Java中,动态web资源开发技术统称为Javaweb.) 网络的资源分为两种 1.静态资源------主要用于展示信息     html 2.动态资源------交 互                 jsp asp php 2.javaweb开发中常见的服务器. weblogic  BEA公司------它是

基于apache和mysql,安装seafile - v1.0

seafile:下一代的开源云存储更好的文件同步,隐私保护和团队协作功能!本文基于官方文档,补充描述基于apache和mysql,安装seafile服务器. 原文网址:https://cloud.seafile.com/group/762/wiki/%E5%9C%A8apache%E7%8E%AF%E5%A2%83%E4%B8%8B%E9%83%A8%E7%BD%B2seafile/ 修改:陈海青(http://hhrz.org) 1.准备 下载安装预装了apache和mysql的虚拟机(基于d