目錄
一.UI界面的設(shè)計
二.服務(wù)器的啟動
三.實現(xiàn)自定義的TcpServer類
1.在widget中聲明自定義TcpServer類的成員變量
2.在TcpServer的構(gòu)造函數(shù)中對于我們聲明的m_widget進行初始化,m_widget我們用于后續(xù)的顯示消息等,說白了就是主界面的更新顯示等
四.實現(xiàn)自定義的TcpSocket類
1.TcpSocket.h?? 先忽略掉信號與槽函數(shù),關(guān)注構(gòu)造函數(shù)與qintptr類型的 m_sockDesc
五.實現(xiàn)自定義線程類
1.主要關(guān)注run函數(shù),其中run函數(shù)是繼承QThread中的虛函數(shù),需要我們進行重寫
2.實現(xiàn)某個客戶端斷開連接時通過信號與槽讓主界面改變
3.實現(xiàn)有新的客戶端連接時主界面更新
六.服務(wù)器收到多客戶端消息進行顯示的流程實現(xiàn)
七.服務(wù)器發(fā)送消息給某個客戶端流程
八.服務(wù)器發(fā)送信息后,要在主頁面信息消息更新顯示的流程
注意:
效果演示:
源碼下載地址:
在初學(xué)Qt 中Tcp服務(wù)器與客戶端的時候,發(fā)現(xiàn)每次服務(wù)器只能和最后一個連接的客戶端進行通信,因為沒有用到多線程以及TcpServer中虛函數(shù)incomingConnection(),當新的客戶端連接的時候,會自動調(diào)用incomingConnection函數(shù),在里面產(chǎn)生新的線程來處理通信。
以下來講講這個簡易的多線程Tcp服務(wù)器的實現(xiàn)
一.UI界面的設(shè)計
其中包括2個Label,一個LineEdit,兩個pushbutton,上面是一個TextBrowser用于服務(wù)器顯示通信記錄,下面一個TextEdit用于發(fā)送信息。這樣一個簡單的界面就搭建完成了~~~
二.服務(wù)器的啟動
首先我們肯定需要實現(xiàn)點擊啟動服務(wù)器按鈕來啟動服務(wù)器
1.在界面中右擊啟動按鈕 ----> 轉(zhuǎn)到槽
2.實現(xiàn)邏輯,這里直接放代碼,其中serverisworking 是我在widget.h 中聲明的一個bool類型,用于判斷服務(wù)器是否啟動,同時更改按鈕的文本顯示內(nèi)容,以及彈出對話框提示。
//點擊開始啟動服務(wù)器
void Widget::on_pushButton_StartServer_clicked()
{
//如果服務(wù)器沒有啟動
if (!this->serverisworking) {
if(this->m_tcpserver->listen(QHostAddress::Any,ui->lineEdit_Port->text().toUShort())){
QMessageBox::information(this,"成功!","啟動成功!");
ui->pushButton_StartServer->setText("關(guān)閉服務(wù)器");
this->serverisworking = true;
}
else {
QMessageBox::critical(this,"失敗!","服務(wù)器啟動失敗請檢查設(shè)置!");
}
}
//如果服務(wù)器正在啟動
else if(this->serverisworking) {
m_tcpserver->close();
if(!m_tcpserver->isListening()){
ui->pushButton_StartServer->setText("啟動服務(wù)器");
this->serverisworking = false;
QMessageBox::information(this,"提示","關(guān)閉成功!");
}
else {
QMessageBox::critical(this,"錯誤","關(guān)閉失敗,請重試!");
return;
}
}
}
三.實現(xiàn)自定義的TcpServer類
1.首先我們搞清楚,這個類是負責干嘛的?這個類我們要繼承QTcpServer,重寫虛函數(shù)incomingConnetion
2.這是頭文件,如果有報錯,注意看使用#pragma once沒有,因為可能涉及到頭文件重復(fù)包含
//tcpserver.h
#pragma once
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QObject>
#include<QTcpServer>
#include <widget.h>
#include"serverthread.h"
class TcpServer : public QTcpServer
{
Q_OBJECT
public:
explicit TcpServer(QObject *parent = nullptr);
private:
//重寫虛函數(shù)
void incomingConnection(qintptr sockDesc);
private:
//用來保存連接進來的套接字,存到ComboBox中
QList<qintptr> m_socketList;
//再包含一個widget對象
Widget *m_widget;
};
#endif // TCPSERVER_H
1.在widget中聲明自定義TcpServer類的成員變量
//widget.h
#pragma once
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QMessageBox>
#include"tcpserver.h"
namespace Ui {
class Widget;
}
class TcpServer;
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
public:
//當有新連接的時候在頁面上顯示
void showNewConnection(qintptr sockDesc);
//斷開時顯示
void showDisconnection(qintptr sockDesc);
//當服務(wù)器發(fā)送消息后,通知窗口更新消息
void UpdateServerMsg(qintptr Desc,const QByteArray &msg);
private slots:
//按鈕被觸發(fā)
void on_pushButton_StartServer_clicked();
void on_pushButton_Trans_clicked();
public slots:
//當服務(wù)器收到客戶端發(fā)送的消息后,更新消息
void RecvMsg(QString Desc,const QByteArray &msg);
signals :
void sendData(qintptr Desc ,const QByteArray &msg);
private:
Ui::Widget *ui;
TcpServer *m_tcpserver;
bool serverisworking;
};
#endif // WIDGET_H
其他的我們先不去討論,這里我們就聲明一個TcpServer類型的m_tcpserver 以及一個bool類型的serverisworking用來判斷服務(wù)器是否在工作
2.在TcpServer的構(gòu)造函數(shù)中對于我們聲明的m_widget進行初始化,m_widget我們用于后續(xù)的顯示消息等,說白了就是主界面的更新顯示等
TcpServer::TcpServer(QObject *parent) : QTcpServer(parent)
{
m_widget = dynamic_cast<Widget *>(parent);
}
那么,問題來了,我們要重寫TcpServer的incomingConnection函數(shù),里面要涉及到線程,那么我們需要去寫一個自定義的線程類
//當有新的連接進來的時候會自動調(diào)用這個函數(shù),不需要你去綁定信號槽
void TcpServer::incomingConnection(qintptr sockDesc)
{
//將標識符保存進list
m_socketList.append(sockDesc);
//產(chǎn)生線程用于通信
ServerThread *thread = new ServerThread(sockDesc);
//窗口中顯示有新的連接
m_widget->showNewConnection(sockDesc);
//線程中發(fā)出斷開tcp連接,觸發(fā)widget中顯示斷開
connect(thread, &ServerThread::disconnectTCP, this,[=]{
m_widget->showDisconnection(sockDesc);
});
//當socket 底層有readyread信號的時候 -> 發(fā)送socket_getmsg信號 -> 發(fā)送socket_getmsg_thread
//將socket_getmsg_thread 與 widget中 RecvMsg 綁定,RecvMsg 用于處理將收到的消息進行顯示
connect(thread,&ServerThread::socket_getmsg_thread,this->m_widget,&Widget::RecvMsg);
//當點擊發(fā)送的時候-> 產(chǎn)生一個SendData 信號 -> 調(diào)用線程中SendDataSlot函數(shù)用于發(fā)送sendData信號來使socket來發(fā)送消息
connect(this->m_widget,&Widget::sendData,thread,&ServerThread::sendDataSlot);
//當服務(wù)器給客戶端發(fā)送下消息后,會產(chǎn)生一個writeover信號-> 觸發(fā)線程發(fā)送writeover信號給 Tcpserver -> Tcpserver中widget更新消息
connect(thread,&ServerThread::writeover,[=](qintptr Desc,const QByteArray &msg){
m_widget->UpdateServerMsg(Desc,msg);
});
thread->start();
}
我們要實現(xiàn)線程類,線程的工作是需要每個線程里面都有一個不同的Tcpsocket類實例,但是我們要在不同的線程里面 工作不同的socket,那么我們可以在一個線程中 去使用套接字標識符(socketDesc)去區(qū)分不同的套接字,然后服務(wù)器也可以通過不同的套接字標識符去進行與不同的套接字進行通信,所以我們需要先去實現(xiàn)一個自定義的TcpSocket類
四.實現(xiàn)自定義的TcpSocket類
1.TcpSocket.h?? 先忽略掉信號與槽函數(shù),關(guān)注構(gòu)造函數(shù)與qintptr類型的 m_sockDesc
為什么是qintptr類型呢,我們查看官方文檔,使用qintpt類型作為參數(shù)更方便,
#pragma once
#ifndef SERVERSOCKET_H
#define SERVERSOCKET_H
#include <QObject>
#include<QTcpSocket>
class ServerSocket : public QTcpSocket
{
Q_OBJECT
public:
explicit ServerSocket(qintptr socketDesc,QObject *parent = nullptr);
signals:
void socket_getmsg(QString Desc, const QByteArray &msg);
void writeover(qintptr Desc,const QByteArray &msg);
public slots:
void sendData(qintptr Desc, const QByteArray &data);
private:
qintptr m_sockDesc;
};
#endif // SERVERSOCKET_H
五.實現(xiàn)自定義線程類
1.主要關(guān)注run函數(shù),其中run函數(shù)是繼承QThread中的虛函數(shù),需要我們進行重寫
//serverthread.h
#pragma once
#ifndef SERVERTHREAD_H
#define SERVERTHREAD_H
#include <QObject>
#include <QThread>
#include<serversocket.h>
class ServerThread : public QThread
{
Q_OBJECT
public:
//構(gòu)造函數(shù)初始化套接字標識符
explicit ServerThread(qintptr sockDesc,QObject *parent = nullptr);
void run() override;
~ServerThread();
signals:
void disconnectTCP(qintptr m_sockDesc);
void sendData(qintptr Desc, const QByteArray& msg);
void socket_getmsg_thread(QString Desc,const QByteArray &msg);
void writeover(qintptr Desc,const QByteArray &msg);
public slots:
void sendDataSlot(qintptr Desc, const QByteArray& msg);
private:
qintptr m_socketDesc;
ServerSocket *m_socket;
};
#endif // SERVERTHREAD_H
2.實現(xiàn)某個客戶端斷開連接時通過信號與槽讓主界面改變
1)我們在run函數(shù)中,其實就是對某個對應(yīng)的用來通信套接字運行一個線程,所以我們在run中,先對m_socket進行初始化,將自身的m_socketDesc 作為參數(shù)傳給m_socket的有參構(gòu)造。并且使用TcpSocket的setSocketDescriptor方法對m_socket進行綁定標識符,這樣我們每個線程內(nèi)工作的套接字都是不同的
m_socket = new ServerSocket(this->m_socketDesc);
//綁定套接字標識符綁定給自定義套接字對象
if (!m_socket->setSocketDescriptor(this->m_socketDesc)) {
return ;
}
2)在線程中,當該線程中的套接字斷開時,底層會發(fā)射出disconnected信號,我們線程可以此信號與一個用來發(fā)射信號的槽函數(shù)綁定起來,實現(xiàn)當套接字發(fā)送disconnect信號的時候,線程發(fā)射出一個disconnectTcp這樣一個自定義信號通知服務(wù)器套接字斷開,server在調(diào)用widget成員的方法實現(xiàn)在主界面中顯示斷開連接
//run()中:
//當套接字斷開時,發(fā)送底層的disconnected信號
connect(m_socket, &ServerSocket::disconnected, this, [=]{
//此信號可以出發(fā)server的槽函數(shù)然后再調(diào)用widget中combobox清除該socketDesc
emit disconnectTCP(this->m_socketDesc);
//讓該線程中的套接字斷開連接
m_socket->disconnectFromHost();//斷開連接
//線程退出
this->quit();
//incommingConnection中
//線程中發(fā)出斷開tcp連接,觸發(fā)widget中顯示斷開
connect(thread, &ServerThread::disconnectTCP, this,[=]{
m_widget->showDisconnection(sockDesc);
});
//widget.cpp中
//用以顯示連接斷開
void Widget::showDisconnection(qintptr sockDesc)
{
ui->textBrowser_ServerMess->append(QString::number(sockDesc)+"斷開了連接");
//通過信號傳遞的標識符,將其刪除
int index = ui->comboBox_CilentID->findData(sockDesc);
ui->comboBox_CilentID->removeItem(index);
}
3.實現(xiàn)有新的客戶端連接時主界面更新
當有新的客戶端連接的時候,會自動調(diào)用server中的incommingConnect函數(shù),直接在此函數(shù)中調(diào)用widget->showNewconnection函數(shù)
//incomingConnection函數(shù)中:
//窗口中顯示有新的連接
m_widget->showNewConnection(sockDesc);
//widget.cpp
void Widget::showNewConnection(qintptr sockDesc)
{
ui->textBrowser_ServerMess->append("有新的連接!,新接入"+QString::number(sockDesc));
ui->comboBox_CilentID->addItem(QString("%1").arg(sockDesc), sockDesc);
}
?通過這兩個連接就可以直接實現(xiàn)有新的客戶端連接時主界面更新。
六.服務(wù)器收到多客戶端消息進行顯示的流程實現(xiàn)
//serversocket.cpp
ServerSocket::ServerSocket(qintptr socketDesc,QObject *parent) : QTcpSocket(parent)
{
this->m_sockDesc = socketDesc;
connect(this,&ServerSocket::readyRead,this,[=]{
QString name = QString::number(m_sockDesc);
QByteArray msg = readAll();
emit socket_getmsg(name,msg);
});
}
//serverthread::run()中
//套接字發(fā)出有消息的信號,然后觸發(fā)線程中發(fā)出有消息的信號
connect(m_socket, &ServerSocket::socket_getmsg, this,[=](QString Desc,const QByteArray &msg){
emit socket_getmsg_thread(Desc,msg);
});
//server.cpp
//當socket 底層有readyread信號的時候 -> 發(fā)送socket_getmsg信號 -> 發(fā)送socket_getmsg_thread
//將socket_getmsg_thread 與 widget中 RecvMsg 綁定,RecvMsg 用于處理將收到的消息進行顯示
connect(thread,&ServerThread::socket_getmsg_thread,this->m_widget,&Widget::RecvMsg);
//widget.cpp
//當客戶端發(fā)送消息,服務(wù)器收到后,顯示消息
void Widget::RecvMsg(QString Desc,const QByteArray &msg)
{
ui->textBrowser_ServerMess->append(Desc+":"+msg);
}
實現(xiàn)收到客戶端消息進行顯示
七.服務(wù)器發(fā)送消息給某個客戶端流程
void Widget::on_pushButton_Trans_clicked()
{
if(serverisworking){
//如果連接個數(shù)大于0,發(fā)送發(fā)送消息的信號
if(ui->comboBox_CilentID->count() >0)
{
//發(fā)射 發(fā)送信號
emit sendData( ui->comboBox_CilentID->currentText().toInt(), ui->textEdit_SendMess->toPlainText().toUtf8());
qDebug()<<"發(fā)送了sendData信號"<<endl;
ui->textEdit_SendMess->clear();
}
}
else {
QMessageBox::critical(this,"錯誤","請檢查連接");
return;
}
}
//Tcpserver.cpp incomingConnection中
//當點擊發(fā)送的時候-> 產(chǎn)生一個SendData 信號 -> 調(diào)用線程中SendDataSlot函數(shù)用于發(fā)送sendData信號來使socket來發(fā)送消息
connect(this->m_widget,&Widget::sendData,thread,&ServerThread::sendDataSlot);
void ServerThread::sendDataSlot(qintptr Desc, const QByteArray &msg)
{
emit sendData(Desc, msg);
}
//run()中
connect(this,&ServerThread::sendData,m_socket,&ServerSocket::sendData);
void ServerSocket::sendData(qintptr Desc, const QByteArray &msg)
{
if (Desc == m_sockDesc && !msg.isEmpty()) {
this->write(msg);
//發(fā)送完畢,發(fā)出信號,通知主頁面更新聊天框
emit writeover(Desc,msg);
}
}
八.服務(wù)器發(fā)送信息后,要在主頁面信息消息更新顯示的流程
void ServerSocket::sendData(qintptr Desc, const QByteArray &msg)
{
if (Desc == m_sockDesc && !msg.isEmpty()) {
this->write(msg);
//發(fā)送完畢,發(fā)出信號,通知主頁面更新聊天框
emit writeover(Desc,msg);
}
}
//serverthread.cpp
//socket 發(fā)送 writeorver 通知線程發(fā)送writeover 用來提醒server中的widget更新消息
connect(m_socket,&ServerSocket::writeover,this,[=](qintptr Desc, const QByteArray& msg){
emit writeover(Desc,msg);
});
//server.cpp
//當服務(wù)器給客戶端發(fā)送下消息后,會產(chǎn)生一個writeover信號-> 觸發(fā)線程發(fā)送writeover信號給 Tcpserver -> Tcpserver中widget更新消息
connect(thread,&ServerThread::writeover,[=](qintptr Desc,const QByteArray &msg){
m_widget->UpdateServerMsg(Desc,msg);
});
//widget.cpp
//當服務(wù)器發(fā)送消息后,通知主窗口更新信號
void Widget::UpdateServerMsg(qintptr Desc, const QByteArray &msg)
{
ui->textBrowser_ServerMess->append("服務(wù)器:"+msg+" to "+QString::number(Desc));
}
注意:
注冊自定義信號參數(shù),否則信號槽機制使用時會出現(xiàn)保存
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
qRegisterMetaType<qintptr>("qintptr");
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
效果演示:
文章來源:http://www.zghlxwxcb.cn/news/detail-854530.html
源碼下載地址:
yuanzhaoyi/My_project at master (github.com)https://github.com/yuanzhaoyi/My_project/tree/master文章來源地址http://www.zghlxwxcb.cn/news/detail-854530.html
到了這里,關(guān)于Qt實現(xiàn)簡易的多線程TCP服務(wù)器(附源碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!