第七天项目日记

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 v;//容器用来存放QLabel

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::iterator it = v.begin();

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 v;//容器用来存放QLabel

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::iterator it = v.begin();

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过程中,写和读的编码要保持一致,并且在传输过程中的长度也要是转换字符格式之后的长度

还有列表私聊、送礼物、聊天框、视频直播几个功能没有写。

这一秒不放弃,下一秒就有希望!!!!!!