写在前面
本文是Qt5下使用socket通信的一个示例,通过该示例,你可以学到:
- Qt5下信号与槽的使用方式
- Qt中线程与线程之间的通信方式
- 拉姆达表达式的简单使用
- Qt5下Socket通信的流程
界面样式
客户端启动后的页面如上图所示,端口默认为6665
,IP为127.0.0.1
,连接服务器
按钮可点击,断开连接
和发送信息
按钮不可点击,左下角连接状态后面的图片为一把红色的锁。
服务器启动后的页面如上图所示,监听端口默认为6665
,启动监听
按钮可点击,左下角连接状态后面的图片为一把红色的锁。
当点击服务器的启动监听
按钮后,该按钮变为不可点击,此时点击客户端的连接服务器
按钮,如果连接成功,客户端的历史信息一栏将会出现“连接服务器,成功!”的信息,连接服务器
按钮变为不可点击,断开连接
和发送信息
按钮变为可点击。同时,服务器和客户端左下角连接状态后面的图片都会变为一把绿色的盾牌。如上图所示。
当客户端点击断开连接
按钮,三个按钮的状态将会回到客户端启动的时候,客户端的历史信息栏将会多出一条“与服务器断开连接”的信息,同时服务器和客户端左下角连接状态后面的图片都会变为一把红色的锁。如上图所示。
如上图所示,在正常连接的状态下,客户端在发送的信息
一栏中输入要发送的文字,然后点击发送信息
按钮,此时,客户端的历史信息和服务器的历史信息一栏中都会出现“客户端say:xxxxxx”内容。
同理,服务器在发送的信息
一栏中输入要发送的文字,然后点击发送信息
按钮,此时,服务器的历史信息和客户端的历史信息一栏中都会出现“服务器say:xxxxxx”内容。
客户端代码
mainwindow.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE
class MainWindow : public QMainWindow { Q_OBJECT
public: MainWindow(QWidget *parent = nullptr); ~MainWindow();
void initUi(); void connectRegistration();
signals: void startConnect(unsigned short, QString); void sendMsg(QString); void disconnect();
private: Ui::MainWindow *ui; QLabel * m_status; QThread *sub_thread; SendMsg *sendmsg; };
|
mainwindow.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void MainWindow::initUi() { setWindowTitle("客户端");
ui->port->setText("6665"); ui->ip->setText("127.0.0.1"); ui->disconnect->setEnabled(false); ui->sendMsg->setEnabled(false);
m_status = new QLabel; m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20, 20)); ui->statusbar->addWidget(new QLabel("连接状态:")); ui->statusbar->addWidget(m_status); }
|
首先构建一个初始化UI页面的函数initUi()
,把默认的端口号、IP、按钮的状态以及状态栏的图片设置好。
mainwindow.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
initUi();
sub_thread = new QThread; sendmsg = new SendMsg; sendmsg->moveToThread(sub_thread);
connectRegistration();
sub_thread->start(); }
|
然后我们在主函数中调用initUi()
,并且创建一个线程对象,用该线程去处理连接服务器、监听服务器、向服务器发消息等任务,而不是把所有的功能都放在主线程中去做。
sendmsg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class SendMsg : public QObject { Q_OBJECT public: explicit SendMsg(QObject *parent = nullptr);
void connectServer(unsigned short port, QString ip);
void sendMessage(QString msg);
void disconnect();
signals: void connectOk(); void disconnectOK(); void newMessage(QByteArray);
private: QTcpSocket * m_t; };
|
sendmsg.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| SendMsg::SendMsg(QObject *parent) : QObject(parent) {
}
void SendMsg::connectServer(unsigned short port, QString ip) { m_t = new QTcpSocket; m_t->connectToHost(QHostAddress(ip), port);
connect(m_t, &QTcpSocket::connected, this, [=]() { emit connectOk(); });
connect(m_t, &QTcpSocket::readyRead, this, [=](){ QByteArray data = m_t->readAll(); emit newMessage(data); });
connect(m_t, &QTcpSocket::disconnected, this, [=]() { m_t->close(); m_t->deleteLater(); emit disconnectOK(); }); }
void SendMsg::sendMessage(QString msg) { m_t->write(msg.toUtf8()); }
void SendMsg::disconnect() { m_t->close(); }
|
mainwindow.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| void MainWindow::connectRegistration() { connect(ui->connServer, &QPushButton::clicked, this, [=](){ QString ip = ui->ip->text(); unsigned short port = ui->port->text().toUShort();
emit startConnect(port, ip); ui->connServer->setEnabled(false); });
connect(this, &MainWindow::startConnect, sendmsg, &SendMsg::connectServer);
connect(sendmsg, &SendMsg::connectOk, this, [=](){ ui->record->append("连接服务器,成功!"); m_status->setPixmap(QPixmap(":/connect.png").scaled(20, 20)); ui->disconnect->setEnabled(true); ui->sendMsg->setEnabled(true); });
connect(ui->sendMsg, &QPushButton::clicked, this, [=](){ QString msg = ui->msg->toPlainText(); ui->record->append("客户端say:" + msg); emit sendMsg(msg); });
connect(this, &MainWindow::sendMsg, sendmsg, &SendMsg::sendMessage);
connect(sendmsg, &SendMsg::newMessage, this, [=](QByteArray data){ ui->record->append("服务器say:" + data); });
connect(ui->disconnect, &QPushButton::clicked, this, [=](){ emit disconnect(); ui->connServer->setEnabled(true); ui->disconnect->setEnabled(false); ui->sendMsg->setEnabled(false); });
connect(this, &MainWindow::disconnect, sendmsg, &SendMsg::disconnect);
connect(sendmsg, &SendMsg::disconnectOK, this, [=](){
m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20, 20)); ui->record->append("与服务器断开连接"); ui->connServer->setEnabled(true); ui->disconnect->setEnabled(false); ui->sendMsg->setEnabled(false); });
|
OK,代码稍微有点长,我用注释说明了每一步执行的任务以及子线程和主线程之间的通信情况(即信号/槽的对应情况)。connectRegistration()
函数就是一个信号与槽的汇总。
第一、二、三步。当我们点击客户端页面上的“连接服务器”按钮(对应mainwindow.cpp里connect(ui->connServer, &QPushButton::clicked, this, [=](){...}
,这里采用了拉姆达表达式的写法),主线程会发出一个startConnect
的信号(对应emit startConnect(port, ip);
),告诉子线程,需要连接服务器。而子线程中处理该信号的槽叫connectServer
(对应mainwindow.cpp里connect(this, &MainWindow::startConnect, sendmsg, &SendMsg::connectServer);
)。这就是主线程与子线程通信的方式。
第四步。子线程开始连接服务器(对应sendmsg.cpp里m_t->connectToHost(QHostAddress(ip), port);
)。
第五、六步。若子线程连接服务器成功了,要发出一个connectOk
的信号(对应sendmsg.cpp里emit connectOk();
)。处理该信号的函数在主线程中(对应mainwindow.cpp里connect(sendmsg, &SendMsg::connectOk, this, [=](){...}
),这里会将按钮的状态、历史信息栏以及连接状态的图标重置。
第七、八、九步。点击客户端页面上的“发送信息按钮”(对应mainwindow.cpp里connect(ui->sendMsg, &QPushButton::clicked, this, [=](){...}
),主线程会发送一个sendMsg
的信号,告诉子线程需要发送信息(对应emit sendMsg(msg);
),处理该信号的槽叫sendMessage
(对应connect(this, &MainWindow::sendMsg, sendmsg, &SendMsg::sendMessage);
)。
第十步。子线程向服务器发送信息(对应sendmsg.cpp里sendMessage(QString msg)
函数)。
第十一、十二步,子线程中需要监听服务器,以处理服务器中发送来的信息并通知主线程更新UI(对应sendmsg.cpp里connect(m_t, &QTcpSocket::readyRead, this, [=](){...}
)。
第十三步,子线程发送信号newMessage
后,主线程需要更新UI(对应mainwindow.cpp里ui->record->append("服务器say:" + data);
)
以上就是一个完整的通信的流程。
同时,接下来,还需要考虑当服务器关闭后,客户端这里需要断开连接的情况。
第十四步,子线程关闭并释放socket对象,通知主线程(对应sendmsg.cpp里emit disconnectOK();
)。
第十五步,主线程执行断开连接的信号(对应mainwindow.cpp里connect(sendmsg, &SendMsg::disconnectOK, this, [=](){...}
)。
第十六、十七、十八、十九步,客户端点击“断开连接”按钮,主动释放连接,那么主线程发射信号emit disconnect();
,子线程处理该信号(disconnect()
),关闭连接后再通知主线程。
服务器代码
mainwindow.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE
class MainWindow : public QMainWindow { Q_OBJECT
public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); void initUi(); void connectRegistion();
signals: void setListenSig(unsigned short); void sendMsgSig(QString);
private: Ui::MainWindow *ui;
QLabel * m_status; QThread * sub_thread; RecvMsg * recv_msg; };
|
mainwindow.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
initUi(); connectRegistion(); }
MainWindow::~MainWindow() { delete ui; }
void MainWindow::connectRegistion() { connect(ui->setListen, &QPushButton::clicked, this, [=](){ unsigned short port = ui->port->text().toUShort(); emit setListenSig(port); ui->setListen->setEnabled(false); });
connect(this, &MainWindow::setListenSig, recv_msg, &RecvMsg::setListenSlt);
connect(recv_msg, &RecvMsg::newConnectionSig, this, [=](){ m_status->setPixmap(QPixmap(":/connect.png").scaled(20, 20)); });
connect(recv_msg, &RecvMsg::recvMsgSig, this, [=](QByteArray data){ ui->record->append("客户端say:" + data); });
connect(ui->sendMsg, &QPushButton::clicked, this, [=](){ QString msg = ui->msg->toPlainText(); emit sendMsgSig(msg); ui->record->append("服务端say:" + msg); });
connect(this, &MainWindow::sendMsgSig, recv_msg, &RecvMsg::sendMsgSlt); connect(recv_msg, &RecvMsg::breakConnection, this, [=](){ m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20, 20)); }); }
void MainWindow::initUi() { sub_thread = new QThread; recv_msg = new RecvMsg;
ui->port->setText("6665");
setWindowTitle("服务器");
m_status = new QLabel; m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20, 20)); ui->statusbar->addWidget(new QLabel("连接状态:")); ui->statusbar->addWidget(m_status);
recv_msg->moveToThread(sub_thread);
sub_thread->start(); }
|
recvmsg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class RecvMsg : public QObject { Q_OBJECT public: explicit RecvMsg(QObject *parent = nullptr);
void setListenSlt(unsigned short port);
void sendMsgSlt(QString msg);
signals: void newConnectionSig(); void recvMsgSig(QByteArray); void breakConnection(); private: QTcpServer * m_s; QTcpSocket * m_t; };
|
recvmsg.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| RecvMsg::RecvMsg(QObject *parent) : QObject(parent) {
}
void RecvMsg::setListenSlt(unsigned short port) { m_s = new QTcpServer; m_s->listen(QHostAddress::Any, port);
connect(m_s, &QTcpServer::newConnection, this, [=](){ m_t = m_s->nextPendingConnection();
connect(m_t, &QTcpSocket::readyRead, this, [=](){ QByteArray data = m_t->readAll(); emit recvMsgSig(data); });
connect(m_t, &QTcpSocket::disconnected, this, [=](){ m_t->close(); m_t->deleteLater(); emit breakConnection(); });
emit newConnectionSig(); }); }
void RecvMsg::sendMsgSlt(QString msg) { m_t->write(msg.toUtf8()); }
|
执行的过程请参照分析客户端代码的时候来,此处不再赘述。客户端与服务器的完整代码可以点击链接 socket_demo 下载。使用 Qt Creator 将两个项目导入后即可查看。