Qt视频直播软件--项目实战(Day6)
第六天項目日記
1、今日總結
完成客戶端首頁的所有功能
完成客戶端加入直播和創建直播的界面設計
增加json打包函數和json解析函數
增加服務器的開播消息和關閉直播的消息
2、設計思路
對于創建直播來說,只需要相應的客戶發送開播消息給服務器,然后服務器轉發給其他客戶端
關閉直播也是如此
另外,在客戶端連接之后,要發送當前開播的房間給客戶端
對于客戶端界面,開播和觀看直播的界面不同
開播界面如下(并非最終版本)
觀看直播的畫面如下
開播只需要之間點擊開播即可,開播后發送相應的房間號給其他客戶端
點擊觀看時,需要選擇房間號進行觀看進一步的內容還沒有實現
3、代碼說明
服務器
服務器都略有修改
新增json消息打包和解析
服務器要解析下面的json,打包上面的json
myjson.h
#ifndef MYJSON_H #define MYJSON_H#include <QJsonDocument> #include <QJsonObject> #include <QJsonParseError> #include <QDebug> #include <QJsonArray> #include <QByteArray>class MyJson { public:MyJson();//解析用戶名和密碼QString name_pswd_info(QString message,QString &pswd,QString &name);//打包直播刷新消息QString pack_live_flush(QString action,QStringList name_list); };#endif // MYJSON_Hmyjson.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; }tcpsocket.h
tcpsocket.cpp
精簡了解析函數,新增了對接收到消息的處理
#include "tcpsocket.h"TcpSocket::TcpSocket(QObject *parent):QTcpSocket(parent) {mydb = dbhelper::getIntance(NULL);mylog = MyLog::getInstance(NULL);//本來想要在這里發送連接的信號給服務器的,結果發現在他狀態改變的時候還讀取不到端口號和ip地址,所以就放在房間socket的地方去發送消息了// connect(this,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(dataconnected()));//連接斷線函數/讀取函數和自定義的槽函數connect(this,SIGNAL(disconnected()),this,SLOT(datadisconnected()));connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived())); }QString TcpSocket::getName() {return name; }int TcpSocket::getlive_state() {return live_state; }void TcpSocket::setlive_state(int state) {live_state = state; }void TcpSocket::msgPrase(QString msg_buf) {int item;QString buf;buf.clear();item = msg_buf.section('|',0,0).trimmed().toInt();int item_len = QString::number(item).length();// qDebug()<<"len"<<item_len;buf = msg_buf.right(msg_buf.length()-item_len-1);// qDebug()<<"item"<<item<<" "<<buf;//把它轉換成消息發送給serverMyMessage message((MsgId)item,buf,buf.length());if(message.isInEnum()){if(message.getmsgid() == MSG_CLIENT_CLOSE || message.getmsgid() == MSG_CLITEN_CONNECT){emit messageToServer(message); //發射消息信號}else if(message.getmsgid() == MSG_LOGIC){QString ret = prase_MSG_LOGIC(message.getmsgbuf());message.setmsgbuf(ret);qDebug()<<message.toString();this->write(message.toString().toLatin1(),message.toString().length()+1);this->flush();if(ret == "success"){emit logicToServer(this->peerName()); //發射消息信號qDebug()<<" emit logicToServer(this->peerName())"<<this->peerName();}}else if(message.getmsgid() == MSG_REGISTER){message.setmsgbuf(prase_MSG_REG(message.getmsgbuf()));this->write(message.toString().toLatin1(),message.toString().length()+1);this->flush();}else if(message.getmsgid() == MSG_SHAKE_HAND){//握手信號message.setmsgbuf(" ");this->write(message.toString().toLatin1(),message.toString().length()+1);this->flush();}else if(message.getmsgid() == MSG_CREATE_LIVE){//創建直播live_state = 1;emit messageToServer(message);}else if(message.getmsgid() == MSG_CLOSE_LIVE){//關閉直播live_state = 0;emit messageToServer(message);}}else{qDebug()<<"無效數據";} }QString TcpSocket::prase_MSG_LOGIC(QString message_buf) {QString name;QString pswd;QString parse_ret;parse_ret = myjson.name_pswd_info(message_buf,pswd,name);if(parse_ret != "success"){return parse_ret;}QString ret = mydb->logic_judge(name,pswd);if(ret == "success") {this->setPeerName(name);MyMessage message(MSG_CLITEN_CONNECT,name,name.length());QString connectbuf = this->peerName() +" "+this->peerAddress().toString()+" "+QString::number(this->peerPort()) + " connected!!";mylog->LogWrite(connectbuf);emit messageToServer(message);}return ret; }QString TcpSocket::prase_MSG_REG(QString message_buf) {QString name;QString pswd;QString parse_ret;parse_ret = myjson.name_pswd_info(message_buf,pswd,name);if(parse_ret != "success"){return parse_ret;}QString ret;ret = mydb->user_reg(name,pswd);return ret; }void TcpSocket::datadisconnected() {//斷開之后會發送-1qDebug()<<"emit disconnected("<<this->peerName()<<")";emit disconnected(this->peerName()); }void TcpSocket::dataReceived() {//處理督導的信號while(this->bytesAvailable() > 0){char buf[1024] = {'\0'};int length = bytesAvailable();this->read(buf,length); //讀取接收msgPrase(buf); //解析收到的數據} }tcpserver.h
#ifndef TCPSERVER_H #define TCPSERVER_H#include <QObject> #include <QtNetwork> #include <QTcpServer> #include "tcpsocket.h" #include "mylog.h" #include "myjson.h"class TcpServer : public QTcpServer {Q_OBJECT public:explicit TcpServer(QObject* parent = 0,int port = 0);QList<TcpSocket*> tcpSocketList;//用來存儲客戶端指針的list容器MyLog *mylog;MyJson myjson;protected:void incomingConnection(int socketDescriptor);//重寫了連接函數signals:void msgToServer(MyMessage);//用來給main發送消息的信號public slots:void msgFromSocket(MyMessage);//用來處理從socket接收到信號的槽函數void tcpDisconnected(QString);//用來處理客戶端斷開的槽函數void tcpLogicSuccess(QString);//用來處理登錄成功的信號 };#endif // TCPSERVER_Htcpserver.cpp
#include "tcpserver.h"TcpServer::TcpServer(QObject *parent, int port):QTcpServer(parent) {mylog = MyLog::getInstance(NULL);listen(QHostAddress::AnyIPv4,port); //用來監聽ipv4的客戶端,port是傳進來的 }void TcpServer::incomingConnection(int socketDescriptor) {TcpSocket *tcpSocket = new TcpSocket(this);tcpSocket->setSocketDescriptor(socketDescriptor);connect(tcpSocket, SIGNAL(messageToServer(MyMessage)),this, SLOT(msgFromSocket(MyMessage)));connect(tcpSocket,SIGNAL(logicToServer(QString)),this,SLOT(tcpLogicSuccess(QString)));connect(tcpSocket, SIGNAL(disconnected(QString)),this, SLOT(tcpDisconnected(QString)));//把socket指針放到socketlist中tcpSocketList.append(tcpSocket); }void TcpServer::msgFromSocket(MyMessage message) {qDebug()<<message.getmsgbuf();// (||message.getmsgid() == MSG_CLITEN_CONNECT)if(message.getmsgid() == MSG_CLIENT_CLOSE){emit msgToServer(message);return;}else if(message.getmsgid() == MSG_CLITEN_CONNECT){emit msgToServer(message);//給該客戶端發送更新的消息return;}else if(message.getmsgid() == MSG_CREATE_LIVE){emit msgToServer(message);//給其他客戶端發送開播消息for(int i = 0; i < tcpSocketList.count(); i++){QTcpSocket *temp = tcpSocketList.at(i);if(temp->peerName() != message.getmsgbuf()){QStringList name_list;name_list<<message.getmsgbuf();QString buf = myjson.pack_live_flush("on",name_list);MyMessage live_message(MSG_LIVE_FLUSH,buf,buf.length()); //開播消息temp->write(live_message.toString().toLatin1(), live_message.toString().length()+1);temp->flush();qDebug()<<live_message.toString()<<" "<<live_message.toString().length();continue;}}return;}else if(message.getmsgid() == MSG_CLOSE_LIVE){emit msgToServer(message);//給其他客戶端發送下播消息for(int i = 0; i < tcpSocketList.count(); i++){QTcpSocket *temp = tcpSocketList.at(i);if(temp->peerName() != message.getmsgbuf()){QStringList name_list;name_list <<message.getmsgbuf();QString buf = myjson.pack_live_flush("off", name_list);MyMessage live_message(MSG_LIVE_FLUSH,buf,buf.length()); //開播消息temp->write(live_message.toString().toLatin1(), live_message.toString().length()+1);temp->flush();continue;}}return;}}void TcpServer::tcpDisconnected(QString name) {QString disconnectbuf;//關閉for(int i = 0; i < tcpSocketList.count(); i++){TcpSocket *temp = tcpSocketList.at(i);if(temp->peerName() == name){disconnectbuf = temp->peerAddress().toString() + " " + QString::number(temp->peerPort()) + " disconnected!!";temp->destroyed();tcpSocketList.removeAt(i);break;}}//發送該客戶端被關閉的消息QString msgbuf = name;MyMessage message(MSG_CLIENT_CLOSE,msgbuf,msgbuf.length());emit msgToServer(message);disconnectbuf = name+" "+ disconnectbuf;mylog->LogWrite(disconnectbuf);return; }void TcpServer::tcpLogicSuccess(QString name) {QStringList name_list;for(int i = 0; i < tcpSocketList.count(); i++){QTcpSocket *temp = tcpSocketList.at(i);if(temp->peerName() == name){for(int j = 0;j<tcpSocketList.count();j++){TcpSocket *other_temp = tcpSocketList.at(j);if(other_temp->getlive_state() == 1){name_list<<other_temp->peerName();// QString buf = myjson.pack_live_flush("on",other_temp->peerName());// MyMessage live_message(MSG_LIVE_FLUSH,buf,buf.length()); //開播消息// temp->write(live_message.toString().toLatin1(), live_message.toString().length()+1);// temp->waitForBytesWritten(100);// temp->flush();// qDebug()<<"to "<<temp->peerName()<<live_message.toString()<<" "<<live_message.toString().length();}}QString buf = myjson.pack_live_flush("on",name_list);MyMessage live_message(MSG_LIVE_FLUSH,buf,buf.length()); //開播消息temp->write(live_message.toString().toLatin1(), live_message.toString().length()+1);temp->flush();qDebug()<<"to "<<temp->peerName()<<live_message.toString()<<" "<<live_message.toString().length();return;}} }message.h
enum MsgId{MSG_START = 0,MSG_CLITEN_CONNECT, //連接消息MSG_READ_BYTES, //讀取接收到的消息MSG_CLIENT_CLOSE, //客戶端關閉的消息MSG_LOGIC, //客戶端登錄消息MSG_REGISTER,//客戶端注冊消息MSG_SHAKE_HAND, //握手MSG_CREATE_LIVE,//創建直播MSG_LIVE_FLUSH,//直播列表刷新MSG_CLOSE_LIVE,//直播間關閉MSG_END };mainwindow.cpp
主要實現主播列表實時更新的功能
客戶端
客戶端主要是消息的發送,消息的解析 和界面的跳轉
clientjson.h
#ifndef CLIENTJSON_H #define CLIENTJSON_H#include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QJsonParseError> #include <QDebug> #include <QByteArray>typedef struct live_flush{QString action;QStringList name; }Live_Flush_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); };#endif // CLIENTJSON_Hclientjson.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<<value.toString();}}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"; }tcpthread.cpp
主要是用來解析收到的直播刷新消息并且給界面發送信號
liveroom.cpp room.cpp
只是寫了關閉函數,并且重寫了關閉事件
widget.h
#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMessageBox> #include <QDebug> #include "tcpthread.h" #include "mymessage.h" #include "clientjson.h" #include "room.h" #include "liveroom.h"namespace Ui { class Widget; }class Widget : public QWidget {Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();void Logic_Init();//登錄初始化void User_Init();//使用界面初始化void connectToServer(); //用連接服務器void deleteListRoom(QString);public slots:void connect_state(QString); //用來控制連接狀態void connect_success(); //用來判斷連接狀態void logic_msg_recv(QString); //用來處理登錄消息void reg_msg_recv(QString); //用來處理注冊消息void liveroom_msg_recv(QString);void live_flush_recv(QString action,QStringList namelist);//用來處理直播間刷新void room_msg_recv(QString); //用來觀看直播的消息private slots:void on_radioshow_clicked(bool checked); //用來顯示密碼和隱藏密碼void on_pushlogic_clicked();//用來登錄void on_pushquit_clicked();//用來退出void on_pushregister_clicked();void on_pushCreate_clicked();void on_pushAdd_clicked();signals:void connect_enable(); //連接private:Ui::Widget *ui;TcpThread *tcpThread;//用來啟動socket線程 // bool status;QString userName; //賬號QString passWord; //密碼int Logic_Flag; //用來判斷是登錄還是注冊0注冊1登錄LiveRoom *liveroom;ClientJson myjson;Room *room;};#endif // WIDGET_Hwidget.cpp
#include "widget.h" #include "ui_widget.h" #include <QThread>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);tcpThread = nullptr;room = nullptr;liveroom = nullptr;// status = false;Logic_Init();QObject::connect(this,SIGNAL(connect_enable()),this,SLOT(connect_success())); }Widget::~Widget() {delete ui; }void Widget::Logic_Init() {this->setWindowTitle("登錄/注冊");ui->linepswd->show();ui->lineUser->show();ui->radioshow->show();ui->pushlogic->show();ui->pushregister->show();ui->linepswd->setEchoMode(QLineEdit::Password);ui->pushAdd->hide();ui->pushCreate->hide();ui->listRoom->hide();ui->pushquit->hide(); }void Widget::User_Init() {this->setWindowTitle("云木直播平臺");ui->linepswd->hide();ui->lineUser->hide();ui->radioshow->hide();ui->pushlogic->hide();ui->pushregister->hide();ui->label->hide();ui->label_2->hide();ui->label_3->hide();ui->pushAdd->show();ui->pushCreate->show();ui->listRoom->show();ui->pushquit->show(); }void Widget::connectToServer() {QString Address_Ip = "127.0.0.1";int port = 8010;if(!tcpThread){tcpThread = new TcpThread;QObject::connect(tcpThread,SIGNAL(send_tcpmsg(QString)),this,SLOT(connect_state(QString)));QObject::connect(tcpThread,SIGNAL(send_logic_msg(QString)),this,SLOT(logic_msg_recv(QString)));QObject::connect(tcpThread,SIGNAL(send_reg_msg(QString)),this,SLOT(reg_msg_recv(QString)));QObject::connect(tcpThread,SIGNAL(send_live_flush_msg(QString,QStringList)),this,SLOT(live_flush_recv(QString,QStringList)));}tcpThread->startThread(Address_Ip,port); }void Widget::deleteListRoom(QString name) {int row = 0;QString line;while(row < ui->listRoom->count()){line=ui->listRoom->item(row)->text();if(name==line){//qDebug()<<"刪除成功";ui->listRoom->takeItem(row);break;}row++;} }void Widget::connect_state(QString message) {qDebug()<<message;if(message == "connecting") {ui->label_connect_state->setText("連接中...");}else if(message == "connect"){ui->label_connect_state->clear();emit connect_enable();return;}else if(message == "disconnect"){tcpThread->stopThread();return;}else if(message == "overtime"){QMessageBox::warning(NULL,tr("無法連接"),tr("無法連接請稍后再試"));ui->label_connect_state->clear();tcpThread->stopThread();return;}else if(message.left(9) == "reconnect"){QString text = "第"+message.right(1)+"次重連中";ui->label_connect_state->setText(text);} }void Widget::connect_success() {QString strJson = myjson.pack_name_pswd(userName,passWord);MyMessage mymsg((Logic_Flag?MSG_LOGIC:MSG_REGISTER),strJson,strJson.length());qDebug()<<mymsg.toString();tcpThread->onSendTcp(mymsg.toString()); }void Widget::logic_msg_recv(QString message) {if(message == "success"){QMessageBox::about(NULL,"登錄","登錄成功");User_Init();return;}else if(message == "pswd_error"){QMessageBox::warning(NULL," 密碼錯誤","密碼輸入錯誤請重試");tcpThread->stopThread();return;}else if(message == "no_name"){QMessageBox::warning(NULL,"未注冊","請先注冊賬號");tcpThread->stopThread();return;} }void Widget::reg_msg_recv(QString message) {if(message == "success"){QMessageBox::about(NULL,"注冊","注冊成功");tcpThread->stopThread();return;}else if(message == "rename"){QMessageBox::warning(NULL,"用戶名重復","請輸入其他用戶名");tcpThread->stopThread();}else{tcpThread->stopThread();} }void Widget::liveroom_msg_recv(QString liveroommsg) {if(liveroommsg == "close"){this->show();liveroom = nullptr;MyMessage live_close(MSG_CLOSE_LIVE,userName,userName.length());qDebug()<<live_close.toString();tcpThread->onSendTcp(live_close.toString());} }void Widget::live_flush_recv(QString action, QStringList namelist) {if(action == "on"){for(int i = 0;i<namelist.count();i++){QString name = namelist.at(i);ui->listRoom->addItem(name);}}else if(action == "off"){for(int i = 0;i<namelist.count();i++){QString name = namelist.at(i);deleteListRoom(name);}} }void Widget::room_msg_recv(QString roommsg) {if(roommsg == "close"){this->show();room = nullptr;} }void Widget::on_radioshow_clicked(bool checked) {if(checked){ui->linepswd->setEchoMode(QLineEdit::Normal);}else{ui->linepswd->setEchoMode(QLineEdit::Password);} }void Widget::on_pushlogic_clicked() {if(ui->lineUser->text().isEmpty()){QMessageBox::warning(NULL,tr("輸入錯誤"),tr("賬號不能為空"));return;}if(ui->linepswd->text().isEmpty()){QMessageBox::warning(NULL,tr("輸入錯誤"),tr("密碼不能為空"));return;}userName = ui->lineUser->text();passWord = ui->linepswd->text();Logic_Flag = 1;//連接connectToServer(); }void Widget::on_pushquit_clicked() {tcpThread->stopThread();QMessageBox::about(NULL,tr("退出"),tr("點擊退出"));this->close(); }void Widget::on_pushregister_clicked() {if(ui->lineUser->text().isEmpty()){QMessageBox::warning(NULL,tr("輸入錯誤"),tr("賬號不能為空"));return;}if(ui->linepswd->text().isEmpty()){QMessageBox::warning(NULL,tr("輸入錯誤"),tr("密碼不能為空"));return;}userName = ui->lineUser->text();passWord = ui->linepswd->text();Logic_Flag = 0;//連接connectToServer(); }void Widget::on_pushCreate_clicked() {MyMessage message(MSG_CREATE_LIVE,userName,userName.length());tcpThread->onSendTcp(message.toString());if(liveroom == NULL){qDebug()<<"new";liveroom = new LiveRoom;connect(liveroom,SIGNAL(sendlivemsg(QString)),this,SLOT(liveroom_msg_recv(QString)));liveroom->show();QString room_name = userName + "的直播間";liveroom->setWindowTitle(room_name);this->hide();}else{liveroom->show();}return; }void Widget::on_pushAdd_clicked() {if(ui->listRoom->count() == 0){QMessageBox::warning(NULL,tr("無法加入"),tr("沒有主播"));return;}if(ui->listRoom->currentItem() == NULL){QMessageBox::warning(NULL,tr("沒有選擇"),tr("請選擇要加入的直播間"));return;}else{if(room == NULL){qDebug()<<"new";room = new Room();connect(room,SIGNAL(roommsg(QString)),this,SLOT(room_msg_recv(QString)));room->show();QString liver = ui->listRoom->currentItem()->text();QString room_name = liver + "的直播間";room->setWindowTitle(room_name);this->hide();}else{room->show();}} }4、項目文件
源代碼.
5、效果展示
1、測試開播下播時服務器的反應
2、測試開播時其他客戶端能否正常刷新
3、測試其他客戶端已經開播,用戶登錄后直播間信息是否正常刷新
4、測試沒有人直播時無法加入直播間,已經已經有人直播之后加入直播間
6、小結
今天對界面的跳轉,界面的自動釋放,以及QWidgetList的使用有了一定的了解,并且使用了一下
日日行,不怕千萬里;常常做,不怕千萬事。 !!!!!!
總結
以上是生活随笔為你收集整理的Qt视频直播软件--项目实战(Day6)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 适配器模式(Adapter Patter
- 下一篇: 测试淘宝购物流程图,梳理基本流和备选流,