详解-斗鱼弹幕API-接入(斗鱼弹幕服务器第三方接入协议)

本文基于"斗鱼弹幕服务器第三方接入协议V1.6.2"编写

基础准备

接入方式:Socket

API服务地址:openbarrage.douyutv.com:8601

斗鱼私有协议说明

如上图所示,每次发送的消息或者接收到的消息都会由 消息长度 + 消息长度 + 消息类型 + 真实消息内容 + 结尾标识 组成

消息 = 消息长度(4) + 消息长度(4) + 消息类型(4) + 真实消息内容(?) + 结尾标识长度(1)

其中这里要特别说明三个点

一、消息长度的算法,消息长度 = 消息长度(4) + 消息类型(4) + 真实消息内容长度 + 结尾标识长度(1)

二、虽然消息里有 两个消息长度字段,但是如上面的算法所示,计算消息长度是 只需要加 一个 消息长度(4) 字段的长度即可。

三、斗鱼要求的消息长度都为 4 字节小端整数,java中都是大端整数,所以需要通过特别方法转换,我下面都给处理对应的工具代码

int contenLeng = 4 + 4 + content.length() + 1; //消息长度算法

下面开始实际进行接入,先上代码

示例代码

    public static void main(String[] args) throws IOException, InterruptedException {
        Socket socket = new Socket("openbarrage.douyutv.com", 8601);

        //发送登录请求(登入9999房间)
        String loginCMD = "type@=loginreq/roomid@=9999/";
        send(loginCMD, socket);

        //读取登录请求消息
        byte[] bytes = read(socket);
        String msg = new String(Arrays.copyOfRange(bytes, 0, bytes.length));
        System.out.println(msg);


        //加入弹幕分组开始接收弹幕
        String joinGroupCMD =  "type@=joingroup/rid@=9999/gid@=-9999/";
        send(joinGroupCMD, socket);

        //循环读取弹幕消息开始
        while (true){
            byte[] msgBytes = read(socket);
            String s = new String(Arrays.copyOfRange(msgBytes, 0, msgBytes.length));
            System.out.println(s);
            Thread.sleep(1);
        }
        //关闭链接
        //socket.close();
    }

如上面的代码所示,根据斗鱼的弹幕协议的要求,每次想接收弹幕必须

1、进行房间的登录

2、加入任意分组(全量弹幕分组:-9999)

然后即可可进行弹幕消息的接收。

其中 登录 的 API 为 type@=loginreq/roomid@=9999/

加入分组的 API 为 type@=joingroup/rid@=9999/gid@=-9999/

rid 表示房间ID,一般直接用房间号即可

当然根据要求 每隔 45秒 还得发送一次心跳消息进行心跳保持,我这并没有实现,请注意自己补充

全部示例代码

import com.yycdev.douyu.sdk.exceptions.DouYuSDKException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;

public class Test1 {

    public static void main(String[] args) throws IOException, InterruptedException {
        Socket socket = new Socket("openbarrage.douyutv.com", 8601);

        //发送登录请求(登入9999房间)
        String loginCMD = "type@=loginreq/roomid@=9999/";
        send(loginCMD, socket);

        //读取登录请求消息
        byte[] bytes = read(socket);
        String msg = new String(Arrays.copyOfRange(bytes, 0, bytes.length));
        System.out.println(msg);


        //加入弹幕分组开始接收弹幕
        String joinGroupCMD =  "type@=joingroup/rid@=9999/gid@=-9999/";
        send(joinGroupCMD, socket);

        //循环读取弹幕消息开始
        while (true){
            byte[] msgBytes = read(socket);
            String s = new String(Arrays.copyOfRange(msgBytes, 0, msgBytes.length));
            System.out.println(s);
            Thread.sleep(1);
        }
        //关闭链接
        //socket.close();
    }

    /**
     * 发送消息
     *
     * @param content
     */
    public static void send(String content, Socket socket) {
        try {
            //计算消息长度 = 消息长度(4) + 消息类型(4) + 真实消息内容长度 + 结尾标识长度(1)
            int contenLeng = 4 + 4 + content.length() + 1;
            //小端模式转换init (长度1)
            byte[] contenLeng1 = intToBytesLittle(contenLeng);
            //小端模式转换init (长度2)
            byte[] contenLeng2 = intToBytesLittle(contenLeng);
            //小端模式转换init (消息类型) (689:客户端发送给弹幕服务器的文本格式数据)
            byte[] msgType = intToBytesLittle(689);
            //标识数据结尾
            int end = 0;

            ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
            //写入长度1
            byteArray.write(contenLeng1);
            //写入长度2(与长度1相同)
            byteArray.write(contenLeng2);
            //写入消息类型
            byteArray.write(msgType);
            //写入消息内容
            byteArray.write(content.getBytes("ISO-8859-1"));
            //写入数据结尾标识
            byteArray.write(end);

            //发送数据
            OutputStream out = socket.getOutputStream();
            out.write(byteArray.toByteArray());
            out.flush();
        } catch (IOException e) {
            throw new DouYuSDKException(e);
        }
    }

    /**
     * 读取消息
     *
     * @return
     */
    public static byte[] read(Socket socket) {
        try {
            InputStream inputStream = socket.getInputStream();
            //下条信息的长度
            int contentLen = 0;

            //读取前4个字节,得到数据长度
            byte[] bytes1 = readStream(inputStream,0,4);
            contentLen = bytesToIntLittle(bytes1, 0); //用小端模式转换byte数组为
            //System.out.println("数据长度1:" + contentLen);

            //继续读取4个字节,得到第二个 数据长度
            byte[] bytes2 = readStream(inputStream,0,4);
            int contentLen2 = bytesToIntLittle(bytes2, 0);
            //System.out.println("数据长度2:" + contentLen2);

            //再次读取4个字节,得到消息类型
            byte[] bytes3 = readStream(inputStream,0,4);
            //将小端整数转换为大端整数
            int msgType = bytesToIntLittle(bytes3, 0);
            //System.out.println("消息类型:" + msgType);

            //
            contentLen = contentLen - 8;
            //继续读取真正的消息内容
            int len = 0;        //本次读取数据长度
            int readLen = 0;    //已读数据长度
            byte[] bytes = new byte[contentLen];
            ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
            while ((len = inputStream.read(bytes, 0, contentLen - readLen)) != -1) {
                byteArray.write(bytes, 0, len);
                readLen += len;
                if (readLen == contentLen) {
                    break;
                }
            }

            return byteArray.toByteArray();
        } catch (IOException e) {
            throw new DouYuSDKException(e);
        }
    }

    /**
     * 计算消息体长度
     */
    private static int calcMessageLength(String content) {
        return 4 + 4 + (content == null ? 0 : content.length()) + 1;
    }


    /**
     * 以小端模式将int转成byte[]
     *
     * @param value
     * @return
     */
    public static byte[] intToBytesLittle(int value) {
        byte[] src = new byte[4];
        src[3] = (byte) ((value >> 24) & 0xFF);
        src[2] = (byte) ((value >> 16) & 0xFF);
        src[1] = (byte) ((value >> 8) & 0xFF);
        src[0] = (byte) (value & 0xFF);
        return src;
    }


    /**
     * 以小端模式将byte[]转成int
     */
    public static int bytesToIntLittle(byte[] src, int offset) {
        int value;
        value = (int) ((src[offset] & 0xFF)
                | ((src[offset + 1] & 0xFF) << 8)
                | ((src[offset + 2] & 0xFF) << 16)
                | ((src[offset + 3] & 0xFF) << 24));
        return value;
    }


    /**
     * 从流中读取数据
     * @param inputStream
     * @param off
     * @param len
     * @return
     * @throws IOException
     */
    public static byte[] readStream(InputStream inputStream, int off, int len) throws IOException {
        byte[] bytes = new byte[len];
        inputStream.read(bytes, 0, 4);
        return bytes;
    }
}

对了顺便推广一下我已经封装好的斗鱼弹幕SDK,欢迎各位直接对接使用

gitee: https://gitee.com/yycdev/douyusdk

github: https://github.com/yyc-dev/douyu-sdk

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏祥子的故事

Tensorflow | 回归分析

先基于均值为0,方差为0.9的正态分布产生随机数X,再通过线性变换产生Y,再添加一个均值为0,方差为0.5的噪声。这样便得到数据X和Y。

13920
来自专栏米扑专栏

Clojure 学习入门(9)—— 连接redis

project.cli 添加redis依赖: [clj-redis "0.0.12"]

12630
来自专栏祥子的故事

github|fatal:unable to access|OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com 443

fatal: unable to access ‘https://github.com/xingbuxing/TA-Lib-in-chinese.git/‘: ...

64620
来自专栏祥子的故事

git常用的命令

git配置这篇博客中有对git配置的介绍,具体的请看图片。这里将讲当你把github在本地配置好后,然后将github上的库克隆到本地后的操作。

13030
来自专栏祥子的故事

GIT使用总结

git认证指使用git能与github或gitlab 进行通信。这里将以github为例来说明。

14720
来自专栏网络安全防护

墨者安全是如何通过流量清洗来防御DDoS攻击?

DDoS攻击是互联网企业面临的最复杂的网络安全威胁之一。攻击者通过大量僵尸网络模拟真实用户对服务器发起访问,企业必须确定这些流量哪些是合法流量哪些是恶意攻击流量...

17920
来自专栏祥子的故事

altify:用微软的深度学习理解图片

github上有个项目叫altify,使用微软的视觉学习来理解图片,地址:https://github.com/ParhamP/altify

12530
来自专栏csxiaoyao

红黑树算法的Java实现

32410
来自专栏米扑专栏

Ubuntu 安装 Redis

Redis,is an open source, BSD licensed, advanced key-value store. It is often ref...

38230
来自专栏祥子的故事

Tensorflow | 读取csv文件

结果好长,给出关键的部分: INFO:tensorflow:Saving evaluation summary for step 12001: accura...

39820

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励