(十二)C++之获取B站用户的粉丝数信息(二)

1.1、获取原理

请参考我的上一篇文章:
(十一)C++之获取B站用户的粉丝数信息(一)

1.2、讲一下程序思路

下面是我在写代码前构思一下整体思路。

我会创建两个类,一个类是专门处理网络问题,另外一个类是处理数据问题。

网络从初始化到链接发送数据,有四个地方容易出错:

1、加载lib出错。
2、获取IP出错。
3、没法连接到服务器。
4、发送数据出错、或者接受数据出错。

所以我们必须针对这四种情况做出处理。

第二个类主要是创建数据处理的问题,每个人的处理方式可能都不一样,所以我将他作为纯虚函数来写,这样就可以更好的兼容性问题了。

1.3、代码

这份代码我已经上传到CSDN中,可以点击下载, 代码链接。哈作为一点辛苦费,我设置了1个积分,如果你不嫌麻烦的话,可以粘贴下面的代码。

主要有五个文件:
在这里插入图片描述

先创建第一个类Network:

.h文件如下:

#pragma once

#include <winsock2.h>
#include <ws2def.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>

#include "GetData.h"

#pragma comment(lib,"ws2_32.lib")

#define  DEVICE_ONLINE	 0  //设备在线
#define  DEVICE_OFFLINE	-1  //设备离线
#define  DEVICE_NO_IP	-2  //没有获取到ip
#define  DEVICE_NO_INIT	-3  //没有初始化 基于window下的网络编程

#define NETWORK_OFFLINE -1 //网络离线
#define NETWORK_ONLINE	0  //网络在线

struct fan_struct{
	int device_status;		//设备状态
	int device_real_status;// 通过改参数来确定最终的设备是否离线
	int follower;					//关注你的人数
	int following;					//你关注的人数
	int likes;						//点赞人数
	int view;						//所有视频观看次数
};

class Network
{
public:
	Network(const char *puid);

	~Network();

	void fan_init(void);

	int lib_init(void);

	int domain_to_ip(void);

	void init(void);

	int network_connect(void);

	void socket_connect(void);

	int network_get_fan(void);

	void socket_online(void);
	void socket_other(void);

	void run(void);

	

private:
	char *puid;					//UID
	char ip[20];				//ip地址
	SOCKET socket_fd;           //网络句柄

	struct fan_struct fan;     //粉丝数据结构体

	GetData *pdata;			  //父类数据处理指针 

	GetJson get_data;        //这个是我自己实现获取数据的对象,然后将父类的指针指向子类的对象,就可以完成接口的形成

};

.cpp文件如下:

#define _CRT_SECURE_NO_WARNINGS

#include "Network.h"

#define HOST_NAME "api.bilibili.com"
#define PORT 80
#define BUFSIZE 1024
 
#define REFRESH_TIME_MS		2000  //默认2秒钟刷新一次
#define DETECTION_TIME_MS	10000 //默认10秒钟检测网络是否恢复



using namespace std;

Network::Network(const char *puid)
{
	int len = strlen(puid);

	this->puid = new char[len+1];
	if (this->puid == NULL){
		cout << "get uid err" << endl;
		return ;
	}

	strcpy(this->puid, puid);
	memset(this->ip,0x00,20);
	this->socket_fd = 0;

	//初始化粉丝结构体
	fan_init();

	//将创建的对象指向父类指针
	pdata = &get_data;
}


Network::~Network()
{
	if (this->puid != NULL){
		delete this->puid;
		this->puid = NULL;
	}
}



/*
 * description:初始化fan结构体
 * para:无
 * return:无
 */
void Network::fan_init(void)
{
	this->fan.device_real_status	= NETWORK_OFFLINE;
	this->fan.device_status = DEVICE_NO_INIT;
	this->fan.follower = 0;
	this->fan.following = 0;
	this->fan.likes = 0;
	this->fan.view = 0;
}

/*
 * description:socket库的一些初始化
 * para:无
 * return:
 *     0:成功 其他:失败
 */
int Network::lib_init(void)
{
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA data;
	if (WSAStartup(sockVersion, &data) != 0){
		return -1;
	}

	return 0;
}

/*
 * description:将域名转为ip地址
 * para:无
 * return:
 *     0:成功 其他:失败
 */
int Network::domain_to_ip(void)
{
	struct hostent *hptr;

	if ((hptr = gethostbyname(HOST_NAME)) == NULL)
	{
		cout << " gethostbyname error for host:" << HOST_NAME << endl;
		return -1;
	}
	cout << "official hostname:" << hptr->h_name << endl;

	switch (hptr->h_addrtype)
	{
	case AF_INET:
	case AF_INET6:
		memset(this->ip, 0x00, 32);
		strcpy(this->ip, inet_ntoa(*(struct in_addr *)*hptr->h_addr_list));
		cout << this->ip << endl;
		break;
	default:
		cout << "unknown address type" << endl;
		return -2;
		break;
	}

	return 0;
}

/*
 * description:网络整体初始化
 * para:无
 * return:无
 */
void Network::init(void)
{
	int ret = 0;
	ret = lib_init();
	if (ret < 0){
		cout << "socket init err" << endl;
		this->fan.device_status = DEVICE_NO_INIT;
		return;
	}
	ret = domain_to_ip();
	if (ret < 0){
		cout << "ip get err" << endl;
		this->fan.device_status = DEVICE_NO_IP;
		return;
	}
	this->fan.device_status = DEVICE_OFFLINE;
}

/*
 * description:连接到服务器
 * para:无
 * return:
 *     0:成功 其他:失败
 */
int Network::network_connect(void)
{
	SOCKADDR_IN seraddr;

	this->socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (this->socket_fd == INVALID_SOCKET){
		printf("invalid socket!\r\n");
		return -1;
	}

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(PORT);
	seraddr.sin_addr.S_un.S_addr = inet_addr(this->ip);

	if (connect(this->socket_fd, (SOCKADDR *)&seraddr, sizeof(seraddr)) == SOCKET_ERROR){
		printf("connect err!\r\n");
		closesocket(this->socket_fd);
		return -2;
	}

	return 0;

}

/*
 * description:整体网络的连接
 * para:无
 * return:无
 */
void Network::socket_connect(void)
{
	int ret = 0;

	//只有是离线才处理,其他模式说明没有过
	if (this->fan.device_status == DEVICE_OFFLINE){
		ret = network_connect();
		if (ret < 0){
			cout << "connect socket err" << endl;
			return;
		}

		cout << "connect TCP ok" << endl;
		this->fan.device_status = DEVICE_ONLINE;
	}
}

/*
 * description:获取粉丝信息
 * para:无
 * return:
 *     0:成功 其他:失败
 */
int Network::network_get_fan(void)
{
	char req[200];
	int req_len = 0;
	int  result;
	char strResponse[BUFSIZE] = { 0 };
	char ret_cout = 0;

	int follower, following, view, likes;


	//先请求关注人数
	memset(req, 0x00, 200);

	this->pdata->fill_information(this->puid, req, MODE_FLOWER);//拼凑出发送头部信息

#ifdef DEBUG
	cout << req << endl;
#endif

	req_len = strlen(req);
	result = send(this->socket_fd, req, req_len, 0);
	if (result == SOCKET_ERROR){
		return -1;
	}

	result = recv(this->socket_fd, strResponse, BUFSIZE - 1, 0);

	if (result > 0){

#ifdef DEBUG
		cout << strResponse<<endl;
#endif
		strResponse[result] = '\0';

		ret_cout += this->pdata->explanation_fan(strResponse, &follower, &following);
	}
	else{
		return -2;
	}

	//求点赞数和观看数
	memset(req, 0x00, 200);
	this->pdata->fill_information(this->puid, req, MODE_VIEW);
	req_len = strlen(req);

#ifdef DEBUG
	cout << req << endl;
#endif

	result = send(this->socket_fd, req, req_len, 0);
	if (result == SOCKET_ERROR){
		return -3;
	}

	result = recv(this->socket_fd, strResponse, BUFSIZE - 1, 0);

	if (result > 0){

#ifdef DEBUG
		cout << strResponse<<endl;
#endif
		strResponse[result] = '\0';

		ret_cout += this->pdata->explanation_view(strResponse, &view, &likes);
	}
	else{
		return -4;
	}
	if (ret_cout == 0){
		this->fan.follower = follower;
		this->fan.following = following;
		this->fan.view = view;
		this->fan.likes = likes;
	}
	return ret_cout;

}

/*
 * description:在线处理粉丝数据信息
 * para:无
 * return:无
 */
void Network::socket_online(void)
{
	int ret = 0;

	static int err_cout = 0;

	time_t rawtime;
	struct tm *info;

	ret = network_get_fan();

	if (ret < 0){
		err_cout++;
		if (err_cout >= 2){            //超过三次都失败可以判断为暂时离线,因为有可能是服务器踢掉的情况。
			printf("get fan err\r\n");
			closesocket(this->socket_fd);
			this->fan.device_status = DEVICE_OFFLINE;
			return;
		}
	}
	else {
		err_cout = 0;
		time(&rawtime);
		info = localtime(&rawtime);
		cout << asctime(info) << endl;
		cout << "你关注的人数:" << this->fan.following;
		cout << " 关注你的人数:" << this->fan.follower;
		cout << " 视频总播放量:" << this->fan.view;
		cout << " 点赞数:" << this->fan.likes << endl;

	}
	Sleep(REFRESH_TIME_MS);

}

/*
 * description:网络出错后,统一在这里处理
 * para:无
 * return:无
 */
void Network::socket_other(void)
{
	int ret = 0;
	switch (this->fan.device_status){
	case DEVICE_NO_INIT:
		ret = lib_init();
		if (ret == 0){
			this->fan.device_status = DEVICE_NO_IP;
		}
		else{
			break;
		}
	case DEVICE_NO_IP:
		ret = domain_to_ip();
		if (ret == 0){
			this->fan.device_status = DEVICE_OFFLINE;
		}
		else{
			break;
		}
	case DEVICE_OFFLINE:
		ret = network_connect();
		if (ret == 0){
			this->fan.device_status = DEVICE_ONLINE;
		}
		break;

	default:
		break;
	}

	Sleep(DETECTION_TIME_MS);
}

/*
 * description:最终运行的函数
 * para:无
 * return:无
 */
void Network::run(void)
{
	int real_offline_cout = 0; //主要是统计真实的掉线次数

	socket_connect();

	
	while (1){

		cout << "network status:" << this->fan.device_real_status << endl;

		if (this->fan.device_status == DEVICE_ONLINE){
			socket_online();
			real_offline_cout = 0;
			this->fan.device_real_status = NETWORK_ONLINE;
		}
		else{
			socket_other();
			real_offline_cout++;
			if (real_offline_cout >= 3){    //超过三次,就真的判断为设备真正的离线了
				real_offline_cout = 3;
				this->fan.device_real_status = NETWORK_OFFLINE;
			}
		}

	}
}

第二个类GetData:
.h文件如下:

#pragma once

#define MODE_FLOWER 0 //因为关注人数和点赞人数是分开请求的,所以需要两种模式来生成对应的访问信息
#define MODE_VIEW	1

class GetData
{
public:
	GetData();
	~GetData();

	//进行数据拼接
	void fill_information(char *puid, char *pdata, char mode);

	//提供两个获取数据的方法,这两个方法是我根据我自己的想法来实现的,如果你自己有更好的可以自己实现
	virtual int explanation_fan(char *precv, int *pfollower, int *pfollwing) = 0;
	virtual int explanation_view(char *precv, int *pview, int *plikes) = 0;

private:
};

class GetJson :public GetData
{
public:
	GetJson();
	~GetJson();

	virtual int explanation_fan(char *precv, int *pfollower, int *pfollwing);
	virtual int explanation_view(char *precv, int *pview, int *plikes);

private:
};

.cpp文件如下:

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <iostream>
#include "GetData.h"


GetData::GetData()
{
}


GetData::~GetData()
{
}


/*
 * description:填充请求信息
 * para:
 *   puid:B站用户对应的UID信息
 *   pdata:最后生成的信息
 *   mode:请求信息类型
 * return:无
 */
void GetData::fill_information(char *puid, char *pdata, char mode)
{
	char fill_data2[] = " HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";
	char fill_flower_data1[] = "GET /x/relation/stat?vmid=";
	char fill_view_data1[] = "GET /x/space/upstat?mid=";

	//char req[] = "GET /x/relation/stat?vmid=378528692 HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";
	//char req_like[] = "GET /x/space/upstat?mid=378528692 HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";


	if (mode == MODE_FLOWER){
		sprintf(pdata, "%s%s%s", fill_flower_data1, puid, fill_data2);
	}
	else if (mode == MODE_VIEW){
		sprintf(pdata, "%s%s%s", fill_view_data1, puid, fill_data2);
	}

}

//---------------------------------------------------------------------------------------

GetJson::GetJson()
{

}
GetJson::~GetJson()
{

}


/*
 * description:解释粉丝数和你关注的人数
 * para:
 *   precv	:请求返回的数据
 *   pfollower	:粉丝数
 *   pfollwing   :你关注的人数
 * return*   0:成功 小于0:失败
 */
int GetJson::explanation_fan(char *precv, int *pfollower, int *pfollwing)
{
	char *pdata = NULL;
	char *pdata_end = NULL;
	char data_tmp[10];
	pdata = strstr(precv, "\"following\":");
	if (pdata == NULL){
		return -1;
	}
	else {
		pdata_end = strstr(pdata, ",\"");
		if (pdata_end == NULL){
			return -2;
		}
		else{
			memset(data_tmp, 0x00, 10);
			memcpy(data_tmp, pdata + 12, pdata_end - pdata - 12);
			*pfollwing = atoi(data_tmp);
		}
	}
	pdata = strstr(precv, "\"follower\":");
	if (pdata == NULL){
		return -3;
	}
	else {
		pdata_end = strstr(pdata, "}}");
		if (pdata_end == NULL){
			return -4;
		}
		else{
			memset(data_tmp, 0x00, 10);
			memcpy(data_tmp, pdata + 11, pdata_end - pdata - 11);
			*pfollower = atoi(data_tmp);
		}
	}
	return 0;
}

/*
 * description:解释观看数和点赞数
 * para:
 *   precv	:请求返回的数据
 *   pview	:观看数
 *   plikes   :点赞数
 * return:
 *   0:成功 小于0:失败
 */
int GetJson::explanation_view(char *precv, int *pview, int *plikes)
{
	char *pdata = NULL;
	char *pdata_end = NULL;
	char data_tmp[10];
	pdata = strstr(precv, "\"view\":");
	if (pdata == NULL){
		return -1;
	}
	else {
		pdata_end = strstr(pdata, "},");
		if (pdata_end == NULL){
			return -2;
		}
		else{
			memset(data_tmp, 0x00, 10);
			memcpy(data_tmp, pdata + 7, pdata_end - pdata - 7);

			*pview = atoi(data_tmp);
		}
	}
	pdata = strstr(precv, "\"likes\":");
	if (pdata == NULL){
		return -3;
	}
	else {
		pdata_end = strstr(pdata, "}}");
		if (pdata_end == NULL){
			return -4;
		}
		else{
			memset(data_tmp, 0x00, 10);
			memcpy(data_tmp, pdata + 8, pdata_end - pdata - 8);

			*plikes = atoi(data_tmp);
		}
	}
	return 0;

}

main.cpp文件如下:

#include "Network.h"


int main(void)
{
	Network net("250858633"); //传递进去你想要查询的up主的UID即可获取得到你想要得数据

	net.init();

	net.run();

	return 0;
}

这个是华农兄弟的B站的UID号。

最终的运行结果如下:
在这里插入图片描述

1.4、一些关键点解释

其实这里最主要的就是判断有没有掉线,之前我遇到过,设备会出现周期性的访问不到数据,其实就是所谓的掉线。

掉线有三种情况,第一种就是服务器把我给踢了,第二种运营商限制(需要增加心跳防止老化),第三种就是我自己断开。

第三种肯定排除了,因为我没有这不操作,第二种的话,如果是运营商的因为链路老化,这个解决办法很好解决,就是将刷新频率调大一点,但是我发现还是存在周期性无法访问,那么就剩第一种,访问一段时间后被服务器给踢了,看下面这种图,周期性访问不到数据,但是在下一次尝试重连服务器发现又可以连接上去了。

在这里插入图片描述

所以我们不能一旦访问不到数据就认为是网络出问题了,有可能是被服务器给踢掉了。

所以我在结构体里面设置两个变量,一个是真正的网络标志位,另外一个是设备标志位,设备离线不代表网络出问题。

但是如果网络出问题了,设备一定会离线。

我具体处理的方式清查开代码,其他的都没什么好说的了,逻辑问题,主要是不断访问,判断有没有出现问题,如果超过三次连续的没有拿到数据,在尝试重新登录,如果尝试三次重新登录都失败,那么判断为网络离线。

发布了29 篇原创文章 · 获赞 0 · 访问量 410

猜你喜欢

转载自blog.csdn.net/weixin_42547950/article/details/104441960