第七天项目日记
1、今日总结
完成有关弹幕的相关内容的开发 用户发送弹幕之后,开播的人显示弹幕,观看直播的人也显示弹幕 自己发的弹幕用绿色的框框起来 别人发的弹幕直接显示
2、设计思路
参考连接. 对于弹幕的实现,这里使用了定时器,弹幕的队列用vector显示,然后vector中存入Qlabel类,发送或者接收到弹幕之后就给vector中添加对应的label 对于弹幕的移动用QTimer 每20ms移动一次移动到边界之后自动消失 弹幕的长度根据字符串来自适应,弹幕移动到边界之后自动消失并且清除缓存
弹幕的发送流程,对于加入直播间的客户端,服务器端进行记录加入的房间号,对于开播的直播间来说,记录开播的房间号。
发送弹幕之后,客户端发送消息给服务器,服务器转发弹幕消息,转发给对于房间号开播的客户端以及正在观看该直播间的用户。 客户端收到消息之后,随机现在弹幕的位置在视频界面。 (后面有时间会整理一个流程图出来)
3、代码说明
服务器
服务器主要是对消息的接收和发送的修改
首先是加入直播间和离开直播间 离开直播间只需要清空自定义tcpsocket的类的加入直播间的房间名称即可 然后是弹幕消息的格式
主要修改以上四个文件
message.h
增加了三种消息
myjson.h
消息的打包和解析
#ifndef MYJSON_H
#define MYJSON_H
#include
#include
#include
#include
#include
#include
class MyJson
{
public:
MyJson();
//解析用户名和密码
QString name_pswd_info(QString message,QString &pswd,QString &name);
//打包直播刷新消息
QString pack_live_flush(QString action,QStringList name_list);
//解析加入直播json
QString join_info(QString message,QString &room_name);
//解析弹幕消息
QString barrage_info(QString message,QString &from, QString &room_name, QString &desc);
//打包返回的弹幕消息
QString pack_ret_barrage_info(QString from, QString desc);
};
#endif // MYJSON_H
myjson.cpp
#include "myjson.h"
MyJson::MyJson()
{
}
QString MyJson::name_pswd_info(QString message, QString &pswd, QString &name)
{
QByteArray bytes = message.toUtf8();
QJsonParseError jsonError;
QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) { // 解析未发生错误
if (doucment.isObject()){
QJsonObject object = doucment.object(); // 转化为对象
if (object.contains("Name")) {
QJsonValue value = object.value("Name");
if (value.isString()) {
name = value.toString();
qDebug() << "Name : " << name;
}
}else{
return "json_error_no_name";
}
if (object.contains("password")) {
QJsonValue value = object.value("password");
if (value.isString()) {
pswd = value.toString();
qDebug() << "pswd : " << pswd;
}
}else{
return "json_error_no_pswd";
}
}else{
return "json is no object";
}
}else{
return "json error";
}
return "success";
}
QString MyJson::pack_live_flush(QString action, QStringList name_list)
{
QJsonObject json;
json.insert("action",action);
QJsonArray json_array;
for (int i = 0; i < name_list.size(); ++i){
json_array.insert(i,name_list.at(i));
}
json.insert("name",json_array);
QJsonDocument document;
document.setObject(json);
QByteArray byteArray = document.toJson(QJsonDocument::Compact);
QString strJson(byteArray);
return strJson;
}
QString MyJson::join_info(QString message, QString &room_name)
{
QByteArray bytes = message.toUtf8();
QJsonParseError jsonError;
QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) { // 解析未发生错误
if (doucment.isObject()){
QJsonObject object = doucment.object(); // 转化为对象
if (object.contains("join")) {
QJsonValue value = object.value("join");
if (value.isString()) {
room_name = value.toString();
qDebug() << "join : " << room_name;
}
}else{
return "json_error_no_join";
}
}else{
return "json is no object";
}
}else{
return "json error";
}
return "success";
}
QString MyJson::barrage_info(QString message, QString &from, QString &room_name, QString &desc)
{
QByteArray bytes = message.toUtf8();
QJsonParseError jsonError;
QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) { // 解析未发生错误
if (doucment.isObject()){
QJsonObject object = doucment.object(); // 转化为对象
if (object.contains("from")) {
QJsonValue value = object.value("from");
if (value.isString()) {
from = value.toString();
}
}else{
return "json_error_no_from";
}
if (object.contains("room_name")) {
QJsonValue value = object.value("room_name");
if (value.isString()) {
room_name = value.toString();
// qDebug() << "room_name : " << room_name;
}
}else{
return "json_error_no_room_name";
}
if (object.contains("desc")) {
QJsonValue value = object.value("desc");
qDebug()<<"?????";
if (value.isString()) {
desc = value.toString().toUtf8();
qDebug() << "desc : " << desc;
}
}else{
return "json_error_no_desc";
}
}else{
return "json is no object";
}
}else{
return "json error";
}
return "success";
}
QString MyJson::pack_ret_barrage_info(QString from, QString desc)
{
QJsonObject json;
json.insert("from",from);
json.insert("desc",desc);
QJsonDocument document;
document.setObject(json);
QByteArray byteArray = document.toJson(QJsonDocument::Compact);
QString strJson(byteArray);
return strJson;
}
tcpsocket.h
tcpsocket.cpp
对于新增消息的处理 由于中文弹幕编码不正常显示问号,所以把接收函数进行了修改
tcpserver.cpp
只需要转发弹幕消息
客户端
客户端均有改动
消息打包以及处理
clientjson.h
#ifndef CLIENTJSON_H
#define CLIENTJSON_H
#include "clientjson.h"
#include
#include
#include
#include
#include
#include
typedef struct live_flush{
QString action;
QStringList name;
}Live_Flush_S;
typedef struct rcv_barrage{
QString from;
QString desc;
}Rcv_Barrage_S;
class ClientJson
{
public:
ClientJson();
//打包账号密码
QString pack_name_pswd(QString name,QString pswd);
//解析直播间刷新消息
QString live_flush_info(QString message_buf,Live_Flush_S &live_msg);
//打包加入直播间的内容
QString pack_join_room(QString room_name);
//打包弹幕内容
QString pack_barrage(QString name,QString room_name,QString message);
//解析收到的弹幕消息
QString rcv_barrage(QString message_buf,Rcv_Barrage_S &barrage_msg);
};
#endif // CLIENTJSON_H
clientjson.cpp
#include "clientjson.h"
ClientJson::ClientJson()
{
}
QString ClientJson::pack_name_pswd(QString name, QString pswd)
{
QJsonObject json;
json.insert("Name",name);
json.insert("password",pswd);
QJsonDocument document;
document.setObject(json);
QByteArray byteArray = document.toJson(QJsonDocument::Compact);
QString strJson(byteArray);
return strJson;
}
QString ClientJson::live_flush_info(QString message_buf, Live_Flush_S &live_msg)
{
QByteArray bytes = message_buf.toUtf8();
QJsonParseError jsonError;
QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) { // 解析未发生错误
if (doucment.isObject()){
QJsonObject object = doucment.object(); // 转化为对象
if (object.contains("action")) {
QJsonValue value = object.value("action");
if (value.isString()) {
live_msg.action = value.toString();
qDebug() << "action : " << live_msg.action;
}
}else{
return "json_error_no_name";
}
if (object.contains("name")) {
QJsonValue value = object.value("name");
if (value.isArray()) {
QJsonArray array = value.toArray();
int size = array.size();
for (int i = 0; i < size; ++i) {
QJsonValue value = array.at(i);
if (value.isString()) {
live_msg.name< } } qDebug() << "name : " << live_msg.name; }else{ return "name_json_error"; } }else{ return "json_error_no_action"; } }else{ return "json is no object"; } }else{ return "json error"; } return "success"; } QString ClientJson::pack_join_room(QString room_name) { QJsonObject json; json.insert("join",room_name); QJsonDocument document; document.setObject(json); QByteArray byteArray = document.toJson(QJsonDocument::Compact); QString strJson(byteArray); return strJson; } QString ClientJson::pack_barrage(QString name, QString room_name, QString message) { // {"from":"123",room_name:"121","desc":"hahaha"} QJsonObject json; json.insert("from",name); json.insert("room_name",room_name); json.insert("desc",message); QJsonDocument document; document.setObject(json); QByteArray byteArray = document.toJson(QJsonDocument::Compact); QString strJson(byteArray); return strJson; } QString ClientJson::rcv_barrage(QString message_buf, Rcv_Barrage_S &barrage_msg) { QByteArray bytes = message_buf.toUtf8(); QJsonParseError jsonError; QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError); if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) { // 解析未发生错误 if (doucment.isObject()){ QJsonObject object = doucment.object(); // 转化为对象 if (object.contains("from")) { QJsonValue value = object.value("from"); if (value.isString()) { barrage_msg.from = value.toString(); } }else{ return "json_error_no_from"; } if (object.contains("desc")) { QJsonValue value = object.value("desc"); if (value.isString()) { barrage_msg.desc = value.toString(); } }else{ return "json_error_no_desc"; } }else{ return "json is no object"; } }else{ return "json error"; } return "success"; } room.h 直播间弹幕的实现和观看直播间弹幕的实现方式一样,都是利用了定时器 每20ms弹幕的label向右移动一个单位 提供了增加弹幕的接口,这样可以在widget种实现弹幕的增加 #ifndef LIVEROOM_H #define LIVEROOM_H #include #include #include #include #include #include namespace Ui { class LiveRoom; } class LiveRoom : public QWidget { Q_OBJECT public: explicit LiveRoom(QWidget *parent = nullptr); void add_barrage(QString from,QString desc); ~LiveRoom(); signals: void sendlivemsg(QString); public slots: void barrage_move(); private slots: void on_pushexitlive_clicked(); protected: virtual void closeEvent(QCloseEvent *ev); private: Ui::LiveRoom *ui; QVector QPoint movep;//移动的位移 QTimer *timer; }; #endif // LIVEROOM_H room.cpp 注意label的一个函数,adjustSize 自适应text长度 #include "room.h" #include "ui_room.h" Room::Room(QWidget *parent) : QWidget(parent), ui(new Ui::Room) { ui->setupUi(this); movep.setX(1); movep.setY(0); timer = new QTimer; ui->labelvideo->setStyleSheet("QLabel{border:2px solid rgb(0, 255, 0);}"); this->setAttribute(Qt::WA_DeleteOnClose); connect(timer,SIGNAL(timeout()),this,SLOT(barrage_move())); //点击enter时也可以发送弹幕 connect(ui->lineBarrage,SIGNAL(returnPressed()),this,SLOT(on_pushBarrage_clicked())); timer->start(20); } QString Room::getroom_name() { return room_name; } void Room::setroom_name(QString name) { room_name = name; } void Room::add_barrage(QString from, QString desc) { QLabel *l = new QLabel(this); l->setText(desc); int y =30 +rand()%370;//随机值y在30-400之间 //设置label的坐标、长宽 l->adjustSize(); int length = l->width(); l->setGeometry(20,y,length+10,20); l->show(); v.push_back(l);//把label存到容器中 } Room::~Room() { qDebug()<<"quit"; delete ui; } void Room::barrage_move() { QVector for(;it!=v.end();){ int len = (*it)->width(); //向右移动 QPoint p = (*it)->pos() + movep; (*it)->move(p); //移动到400的时候删除创建的label if(p.x() == 620 - len){ (*it)->clear(); (*it)->setStyleSheet(""); v.erase(it); }else{ it++; } } } void Room::on_pushexit_clicked() { this->close(); } void Room::closeEvent(QCloseEvent *ev) { Q_UNUSED(ev); while(v.size()!=0){ delete v.last(); v.pop_back(); } emit roommsg("close"); } void Room::on_pushBarrage_clicked() { QString str = ui->lineBarrage->text(); if(!str.isEmpty()){ //给widget发信号,发弹幕 emit barrage_send(str); //必须将this传进去,不然就会在新的窗口中显示 QLabel *l = new QLabel(this); l->setText(str); int y =30 +rand()%370;//随机值y在30-400之间 //设置label的坐标、长宽 l->adjustSize(); int length = l->width(); l->setGeometry(20,y,length+10,20); l->setStyleSheet("QLabel{border:2px solid rgb(0, 255, 0);}"); l->show(); v.push_back(l);//把label存到容器中 } ui->lineBarrage->clear();//清空输入框 } liveroom.h #ifndef LIVEROOM_H #define LIVEROOM_H #include #include #include #include #include #include namespace Ui { class LiveRoom; } class LiveRoom : public QWidget { Q_OBJECT public: explicit LiveRoom(QWidget *parent = nullptr); void add_barrage(QString from,QString desc); ~LiveRoom(); signals: void sendlivemsg(QString); public slots: void barrage_move(); private slots: void on_pushexitlive_clicked(); protected: virtual void closeEvent(QCloseEvent *ev); private: Ui::LiveRoom *ui; QVector QPoint movep;//移动的位移 QTimer *timer; }; #endif // LIVEROOM_H liveroom.cpp #include "liveroom.h" #include "ui_liveroom.h" LiveRoom::LiveRoom(QWidget *parent) : QWidget(parent), ui(new Ui::LiveRoom) { ui->setupUi(this); movep.setX(1); movep.setY(0); timer = new QTimer; this->setAttribute(Qt::WA_DeleteOnClose); ui->labelvideo->setStyleSheet("QLabel{border:2px solid rgb(0, 255, 0);}"); connect(timer,SIGNAL(timeout()),this,SLOT(barrage_move())); timer->start(20); } void LiveRoom::add_barrage(QString from, QString desc) { QLabel *l = new QLabel(this); l->setText(desc); int y =50 +rand()%550;//随机值y在50-600之间 //设置label的坐标、长宽 l->adjustSize(); int length = l->width(); l->setGeometry(50,y,length+10,20); l->show(); v.push_back(l);//把label存到容器中 } LiveRoom::~LiveRoom() { qDebug()<<"liveroom quit"; delete ui; } void LiveRoom::barrage_move() { QVector for(;it!=v.end();){ int len = (*it)->width(); //向右移动 QPoint p = (*it)->pos() + movep; (*it)->move(p); //移动到400的时候删除创建的label if(p.x() == 750 - len){ (*it)->clear(); (*it)->setStyleSheet(""); v.erase(it); }else{ it++; } } } void LiveRoom::on_pushexitlive_clicked() { emit sendlivemsg("close"); this->close(); } void LiveRoom::closeEvent(QCloseEvent *ev) { Q_UNUSED(ev); emit sendlivemsg("close"); } main.cpp 用来获取随机数 #include "widget.h" #include #include #include int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; srand(time(0)); w.show(); return a.exec(); } widget.cpp 增加弹幕的发送和接收的处理函数 加入房间时绑定信号,用户发送弹幕之后会发送信号给widget,widget调用线程的发送给服务器发送弹幕。 发送弹幕给服务器 tcpthread.cpp 对于服务器接收到的弹幕消息 tcp线程接收到解析之后直接发消息给widget,然后widget调用界面的弹幕添加函数即可 由于编码的问题,对线程种的发送和接收的编码也进行了统一,统一成了utf8 void TcpThread::onReadMsg() { if(m_TcpSocket->bytesAvailable() <= 0) { // 判定连接失败 m_TcpSocket->disconnectFromHost(); } while (m_TcpSocket->bytesAvailable() > 0) { // 接收数据 QByteArray tmpQBA; tmpQBA.clear(); tmpQBA= m_TcpSocket->readAll(); QString str_tcp_receive = QString::fromUtf8(tmpQBA); qDebug()<<"recv "< parse_msg(str_tcp_receive); qDebug()<<"read "< // msleep(100); } } void TcpThread::onSendTcp(QString str_info) { if((!m_TcpSocket)||m_TcpSocket->state()!=QAbstractSocket::ConnectedState) return; m_iSendData = m_TcpSocket->write(str_info.toUtf8(), str_info.toUtf8().length()+1); m_TcpSocket->flush(); if (m_iSendData < 0) { m_TcpSocket->disconnectFromHost(); return; } msleep(100); } 然后widget接收到弹幕消息之后进行显示 先添加信号和槽 然后添加弹幕即可 4、项目源码 第一个是服务器剩下的都是客户端 客户端都一样 是为了测试,所以用qt打开多个项目 项目源码. 5、效果展示 测试时利用三个客户端和一个服务器进行测试 测试一个开播两个加入 测试发送弹幕(中文和英文)以及弹幕到右边之后自动消失 自己发的弹幕有框,别人的弹幕没有框 直播间之后用户列表没显示,是因为还没做 测试完成 6、总结 tcp write和read过程中,写和读的编码要保持一致,并且在传输过程中的长度也要是转换字符格式之后的长度 还有列表私聊、送礼物、聊天框、视频直播几个功能没有写。 这一秒不放弃,下一秒就有希望!!!!!!