天天看點

擷取鬥魚直播間的彈幕資訊

最近在知乎上看到這個話題,感覺很有趣,自己實驗了,果然可以,特此分享:https://www.zhihu.com/question/29027665

實驗準備:Wireshark、Linux、Chrome

實驗步驟:

1、打開Chrome,進入一個彈幕比較豐富的直播間,比如22519号房間,擷取server_config

F12審查元素,然後 Ctrl+F 查找 server_config,如果找不到查找 server 即可:

擷取鬥魚直播間的彈幕資訊

2、解碼 server_config

輕按兩下選中 server_config (上圖中紅框中的内容)複制,線上解碼:http://www.convertstring.com/EncodeDecode/UrlDecode

擷取鬥魚直播間的彈幕資訊

3、打開Wireshark監視網卡,并重新整理鬥魚房間

點此檢視Wireshark npf沒有啟動的解決方法

點選Wireshark左上角的 Interface List ,選擇目前聯網的網卡:

擷取鬥魚直播間的彈幕資訊
擷取鬥魚直播間的彈幕資訊

4、監聽指定端口,找到gid

tcp.port==8053||tcp.port==8075||tcp.port==8019||tcp.port==8069||tcp.port==8049||tcp.port==8056||tcp.port==8005||tcp.port==8026||tcp.port==8074||tcp.port==8054

擷取鬥魚直播間的彈幕資訊

這裡的端口都是上面根據 server_config 解碼得到的

我們看到資料包的最上面幾條,三次握手之後的第三條接收的資料裡面就有gid:

擷取鬥魚直播間的彈幕資訊
擷取鬥魚直播間的彈幕資訊

這裡看到gid=10

5、下載下傳源代碼,編譯運作

源碼在github上:https://github.com/fishioon/douyu/blob/master/danmu.cc

我們下載下傳後放在linux下用 g++ 編譯:

g++ danmu.cc -o danmu

然後運作:

./danmu 25519 10

上面的25519是主播的房間号,10是上面擷取的gid

運作結果:

擷取鬥魚直播間的彈幕資訊

6、源代碼解釋:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BUFFER_SIZE 1024
#define DANMU_PORT  8601                //伺服器彈幕發送端口
#define DANMU_IP    "125.88.176.8"      //彈幕伺服器
#define USERNAME    "username"          //預設使用者名
#define PASSWORD    "passwd"            //預設密碼

typedef struct {                        //資料包結構體
    int len;
    int code;
    int magic;
    char content[BUFFER_SIZE];
}MsgInfo;

//連接配接伺服器
int sock_conn(int port, const char* addr) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(addr);

    if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
        fprintf(stderr, "connect socket failed, port:%d, addr:%s\n", port, addr);
        return -1;
    }
    return sockfd;
}

int sock_close(int sockfd) {
    close(sockfd);
    return 0;
}

//彈幕處理
int process_danmu(int msg_len, MsgInfo* msg) {
    if (msg_len <= 12 || msg->len <= 8) {
        printf("msg_len:%d\n", msg_len);
        return -1;
    }
    char* p = strstr(msg->content, "[email protected]=");
    if (!p) return -1;
    char* content = p + sizeof("[email protected]=")-1;
    p = strchr(content, '/');
    if (!p) return -1;
    *p++ = 0;
    char* snick = p + sizeof("[email protected]=")-1;
    p = strchr(snick, '/');
    if (!p) return -1;
    *p++ = 0;
    printf("%s:\t%s\n", snick, content);
    return 0;
}


//收發彈幕資料包
int douyu_danmu(int sockfd, const char* username,
        const char* passwd, int room_id, int group_id) {
    MsgInfo msg;
    //登陸請求
    int ct_len = snprintf(msg.content, sizeof(msg.content),
            "[email protected]=loginreq/[email protected]%s=/[email protected]=%s/[email protected]=%d/[email protected]=2/",
            username, passwd, room_id);
    msg.len = ct_len + 1 + sizeof(msg.code) + sizeof(msg.magic);
    msg.code = msg.len;
    msg.magic = 0x2b1;
    //第一次握手
    send(sockfd, &msg, msg.len+sizeof(msg.len), 0);
    //第二次握手
    recv(sockfd, &msg, sizeof(msg), 0);
    //輸出資料包内容
    printf("recv:%s\n", msg.content);

    ct_len = snprintf(msg.content, sizeof(msg.content),
            "[email protected]=joingroup/[email protected]=%d/[email protected]=%d/", room_id, group_id);
    msg.len = ct_len + 1 + sizeof(msg.code) + sizeof(msg.magic);
    msg.code = msg.len;
    msg.magic = 0x2b1;
    //第三次握手
    send(sockfd, &msg, msg.len+sizeof(msg.len), 0);
    int ret = 0;
    while (true) {
        //接收彈幕
        ret = recv(sockfd, &msg, sizeof(msg), 0);
        //處理彈幕
        if (process_danmu(ret, &msg) == -1) {
            send(sockfd, &msg, 0, 0);
        }
    }
    return 0;
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("usage: danmu room_id group_id\n");
        return 0;
    }
    //可以不需要使用者名和密碼
    const char* username = ((argc == 4) ? argv[3] : USERNAME);
    const char* passwd = ((argc == 4) ? argv[4] : PASSWORD);
    //第一個參數是rid
    int room_id = atoi(argv[1]);
    //第二個參數是gid
    int group_id = atoi(argv[2]);
    //打開連接配接
    int sockfd = sock_conn(DANMU_PORT, DANMU_IP);
    //收發彈幕包
    douyu_danmu(sockfd, username, passwd, room_id, group_id);
    return 0;
}
           

繼續閱讀