您的位置:首页 > 其它

获取斗鱼直播间的弹幕信息

2016-01-15 23:50 399 查看
最近在知乎上看到这个话题,感觉很有趣,自己实验了,果然可以,特此分享: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, "content@=");
if (!p) return -1;
char* content = p + sizeof("content@=")-1;
p = strchr(content, '/');
if (!p) return -1;
*p++ = 0;
char* snick = p + sizeof("snick@=")-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),
"type@=loginreq/username@%s=/password@=%s/roomid@=%d/ct@=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),
"type@=joingroup/rid@=%d/gid@=%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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: