• qt中Qtcpserver服务端_qt websocket


    0.前言

    本文主要讲解 Qt TCP 相关接口的基本应用,一些实践相关的后面会单独写。

    TCP 协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。

    TCP 通过三次握手来建立可靠的连接。

     

    TCP 四次挥手断开连接。TCP连接是双向的,在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。

     本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

    1.准备工作

    首先,要使用 Qt 的网络模块需要在 pro 中加上 network(如果是 VS IDE 就在模块选择里勾选上 network):

    QT += network

    引入相关类的头文件:

    1. #include
    2. #include
    3. #include

    另外, Qt 在 windows 下使用的 select 模型,在 linux 下新版本的改为了 poll 模型(具体版本待查)。

    Qt TCP 的操作流程:

     

    2.认识QTcpSocket的接口

    QTcpSocket 是 QAbstractSocket 的子类,用于建立 TCP 连接并传输数据流。

    对于 QTcpServer 服务端,可通过 nextPendingConnection() 接口获取到建立了 TCP 连接的 QTcpSocket 对象。

    对于客户端,创建好 QTcpSocket 对象后,调用 connectToHost() 连接到服务端:

    1. void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)
    2. void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode = ReadWrite)

    连接成功和连接断开会触发 connected() 和 disconnected() 信号:

    1. void QAbstractSocket::connected()
    2. void QAbstractSocket::disconnected()

    连接成功之后,可以调用 QIODevice 继承来的 read,write 等接口:

    1. qint64 QIODevice::read(char *data, qint64 maxSize)
    2. QByteArray QIODevice::read(qint64 maxSize)
    3. QByteArray QIODevice::readAll()
    4. qint64 QIODevice::write(const char *data, qint64 maxSize)
    5. qint64 QIODevice::write(const char *data)
    6. qint64 QIODevice::write(const QByteArray &byteArray)

    当有新的数据到来,会触发 readyRead() 信号,此时在槽函数中进行读取即可:

    void QIODevice::readyRead()

    操作完之后,调用相关接口关闭 TCP 连接:

    1. void QAbstractSocket::disconnectFromHost()
    2. void QAbstractSocket::close()
    3. void QAbstractSocket::abort()

    其中, abort 调用了 close, close 调用了 disconnectFromHost。 abort 立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。close 关闭套接字的 IO,以及套接字的连接。

    3.认识QTcpServer的接口

    QTcpServer 类提供基于 TCP 的服务器。

    首先,调用 listen() 监听指定的地址和端口:

    bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)

    当有新的 TCP 连接,会触发 newConnection() 信号,此时可以调用 nextPendingConnection() 以将挂起的连接接受为已连接的 QTcpSocket,通过该对象可以与客户端通信。

    QTcpSocket *QTcpServer::nextPendingConnection()

    注意,返回的 QTcpSocket 对象不能在另一个线程使用,如果需要在别的线程管理这个 socket 连接,需要重写 Server 的 incomingConnection() ,将 sokcet 描述符传递给别的线程并创建 QTcpSocket:

    void QTcpServer::incomingConnection(qintptr socketDescriptor)

    最后,调用 close() 停止监听:

    void QTcpServer::close()

    4.Qt Tcp的简单示例

    完整代码链接(分为SimpleTcpServer和SimpleTcpClient两个子项目):

    运行效果:

     

    服务端主要实现代码:

    1. #ifndef WIDGET_H
    2. #define WIDGET_H
    3. #include
    4. #include
    5. #include
    6. QT_BEGIN_NAMESPACE
    7. namespace Ui { class Widget; }
    8. QT_END_NAMESPACE
    9. //simple Tcp 服务端
    10. class Widget : public QWidget
    11. {
    12. Q_OBJECT
    13. public:
    14. Widget(QWidget *parent = nullptr);
    15. ~Widget();
    16. private:
    17. //初始化server操作
    18. void initServer();
    19. //close server
    20. void closeServer();
    21. //更新当前状态
    22. void updateState();
    23. private:
    24. Ui::Widget *ui;
    25. //server用于监听端口,获取新的tcp连接的描述符
    26. QTcpServer *server;
    27. //存储已连接的socket对象
    28. QList clientList;
    29. };
    30. #endif // WIDGET_H
    1. #include "widget.h"
    2. #include "ui_widget.h"
    3. #include <QHostAddress>
    4. Widget::Widget(QWidget *parent)
    5. : QWidget(parent)
    6. , ui(new Ui::Widget)
    7. {
    8. ui->setupUi(this);
    9. setWindowTitle("Server");
    10. initServer();
    11. }
    12. Widget::~Widget()
    13. {
    14. //关闭server
    15. closeServer();
    16. delete ui;
    17. }
    18. void Widget::initServer()
    19. {
    20. //创建Server对象
    21. server = new QTcpServer(this);
    22. //点击监听按钮,开始监听
    23. connect(ui->btnListen,&QPushButton::clicked,[this]{
    24. //判断当前是否已开启,是则close,否则listen
    25. if(server->isListening()){
    26. //server->close();
    27. closeServer();
    28. //关闭server后恢复界面状态
    29. ui->btnListen->setText("Listen");
    30. ui->editAddress->setEnabled(true);
    31. ui->editPort->setEnabled(true);
    32. }else{
    33. //从界面上读取ip和端口
    34. //可以使用 QHostAddress::Any 监听所有地址的对应端口
    35. const QString address_text=ui->editAddress->text();
    36. const QHostAddress address=(address_text=="Any")
    37. ?QHostAddress::Any
    38. :QHostAddress(address_text);
    39. const unsigned short port=ui->editPort->text().toUShort();
    40. //开始监听,并判断是否成功
    41. if(server->listen(address,port)){
    42. //连接成功就修改界面按钮提示,以及地址栏不可编辑
    43. ui->btnListen->setText("Close");
    44. ui->editAddress->setEnabled(false);
    45. ui->editPort->setEnabled(false);
    46. }
    47. }
    48. updateState();
    49. });
    50. //监听到新的客户端连接请求
    51. connect(server,&QTcpServer::newConnection,this,[this]{
    52. //如果有新的连接就取出
    53. while(server->hasPendingConnections())
    54. {
    55. //nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象
    56. //套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。
    57. //最好在完成处理后显式删除该对象,以避免浪费内存。
    58. //返回的QTcpSocket对象不能从另一个线程使用,如有需要可重写incomingConnection().
    59. QTcpSocket *socket=server->nextPendingConnection();
    60. clientList.append(socket);
    61. ui->textRecv->append(QString("[%1:%2] Soket Connected")
    62. .arg(socket->peerAddress().toString())
    63. .arg(socket->peerPort()));
    64. //关联相关操作的信号槽
    65. //收到数据,触发readyRead
    66. connect(socket,&QTcpSocket::readyRead,[this,socket]{
    67. //没有可读的数据就返回
    68. if(socket->bytesAvailable()<=0)
    69. return;
    70. //注意收发两端文本要使用对应的编解码
    71. const QString recv_text=QString::fromUtf8(socket->readAll());
    72. ui->textRecv->append(QString("[%1:%2]")
    73. .arg(socket->peerAddress().toString())
    74. .arg(socket->peerPort()));
    75. ui->textRecv->append(recv_text);
    76. });
    77. //error信号在5.15换了名字
    78. #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    79. //错误信息
    80. connect(socket, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
    81. [this,socket](QAbstractSocket::SocketError){
    82. ui->textRecv->append(QString("[%1:%2] Soket Error:%3")
    83. .arg(socket->peerAddress().toString())
    84. .arg(socket->peerPort())
    85. .arg(socket->errorString()));
    86. });
    87. #else
    88. //错误信息
    89. connect(socket,&QAbstractSocket::errorOccurred,[this,socket](QAbstractSocket::SocketError){
    90. ui->textRecv->append(QString("[%1:%2] Soket Error:%3")
    91. .arg(socket->peerAddress().toString())
    92. .arg(socket->peerPort())
    93. .arg(socket->errorString()));
    94. });
    95. #endif
    96. //连接断开,销毁socket对象,这是为了开关server时socket正确释放
    97. connect(socket,&QTcpSocket::disconnected,[this,socket]{
    98. socket->deleteLater();
    99. clientList.removeOne(socket);
    100. ui->textRecv->append(QString("[%1:%2] Soket Disonnected")
    101. .arg(socket->peerAddress().toString())
    102. .arg(socket->peerPort()));
    103. updateState();
    104. });
    105. }
    106. updateState();
    107. });
    108. //server向client发送内容
    109. connect(ui->btnSend,&QPushButton::clicked,[this]{
    110. //判断是否开启了server
    111. if(!server->isListening())
    112. return;
    113. //将发送区文本发送给客户端
    114. const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
    115. //数据为空就返回
    116. if(send_data.isEmpty())
    117. return;
    118. for(QTcpSocket *socket:clientList)
    119. {
    120. socket->write(send_data);
    121. //socket->waitForBytesWritten();
    122. }
    123. });
    124. //server的错误信息
    125. //如果发生错误,则serverError()返回错误的类型,
    126. //并且可以调用errorString()以获取对所发生事件的易于理解的描述
    127. connect(server,&QTcpServer::acceptError,[this](QAbstractSocket::SocketError){
    128. ui->textRecv->append("Server Error:"+server->errorString());
    129. });
    130. }
    131. void Widget::closeServer()
    132. {
    133. //停止服务
    134. server->close();
    135. for(QTcpSocket * socket:clientList)
    136. {
    137. //断开与客户端的连接
    138. socket->disconnectFromHost();
    139. if(socket->state()!=QAbstractSocket::UnconnectedState){
    140. socket->abort();
    141. }
    142. }
    143. }
    144. void Widget::updateState()
    145. {
    146. //将当前server地址和端口、客户端连接数写在标题栏
    147. if(server->isListening()){
    148. setWindowTitle(QString("Server[%1:%2] connections:%3")
    149. .arg(server->serverAddress().toString())
    150. .arg(server->serverPort())
    151. .arg(clientList.count()));
    152. }else{
    153. setWindowTitle("Server");
    154. }
    155. }

    客户端主要实现代码:

    1. #ifndef WIDGET_H
    2. #define WIDGET_H
    3. #include
    4. #include
    5. QT_BEGIN_NAMESPACE
    6. namespace Ui { class Widget; }
    7. QT_END_NAMESPACE
    8. //simple Tcp 客户端
    9. class Widget : public QWidget
    10. {
    11. Q_OBJECT
    12. public:
    13. Widget(QWidget *parent = nullptr);
    14. ~Widget();
    15. private:
    16. //初始化client操作
    17. void initClient();
    18. //更新当前状态
    19. void updateState();
    20. private:
    21. Ui::Widget *ui;
    22. //socket对象
    23. QTcpSocket *client;
    24. };
    25. #endif // WIDGET_H
    1. #include "widget.h"
    2. #include "ui_widget.h"
    3. #include <QHostAddress>
    4. Widget::Widget(QWidget *parent)
    5. : QWidget(parent)
    6. , ui(new Ui::Widget)
    7. {
    8. ui->setupUi(this);
    9. setWindowTitle("Client");
    10. initClient();
    11. }
    12. Widget::~Widget()
    13. {
    14. //析构关闭连接
    15. //client->disconnectFromHost();
    16. //if(client->state()!=QAbstractSocket::UnconnectedState){
    17. // client->waitForDisconnected();
    18. //}
    19. //关闭套接字的I/O设备,并调用disconnectFromHost()关闭套接字的连接。
    20. //client->close();
    21. //中止当前连接并重置套接字。与disconnectFromHost()不同,
    22. //此函数立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。
    23. client->abort();
    24. delete ui;
    25. }
    26. void Widget::initClient()
    27. {
    28. //创建client对象
    29. client = new QTcpSocket(this);
    30. //点击连接,根据ui设置的服务器地址进行连接
    31. connect(ui->btnConnect,&QPushButton::clicked,[this]{
    32. //判断当前是否已连接,连接了就断开
    33. if(client->state()==QAbstractSocket::ConnectedState){
    34. //如果使用disconnectFromHost()不会重置套接字,isValid还是会为true
    35. client->abort();
    36. }else if(client->state()==QAbstractSocket::UnconnectedState){
    37. //从界面上读取ip和端口
    38. const QHostAddress address=QHostAddress(ui->editAddress->text());
    39. const unsigned short port=ui->editPort->text().toUShort();
    40. //连接服务器
    41. client->connectToHost(address,port);
    42. }else{
    43. ui->textRecv->append("It is not ConnectedState or UnconnectedState");
    44. }
    45. });
    46. //连接状态
    47. connect(client,&QTcpSocket::connected,[this]{
    48. //已连接就设置为不可编辑
    49. ui->btnConnect->setText("Disconnect");
    50. ui->editAddress->setEnabled(false);
    51. ui->editPort->setEnabled(false);
    52. updateState();
    53. });
    54. connect(client,&QTcpSocket::disconnected,[this]{
    55. //断开连接还原状态
    56. ui->btnConnect->setText("Connect");
    57. ui->editAddress->setEnabled(true);
    58. ui->editPort->setEnabled(true);
    59. updateState();
    60. });
    61. //发送数据
    62. connect(ui->btnSend,&QPushButton::clicked,[this]{
    63. //判断是可操作,isValid表示准备好读写
    64. if(!client->isValid())
    65. return;
    66. //将发送区文本发送给客户端
    67. const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
    68. //数据为空就返回
    69. if(send_data.isEmpty())
    70. return;
    71. client->write(send_data);
    72. //client->waitForBytesWritten();
    73. });
    74. //收到数据,触发readyRead
    75. connect(client,&QTcpSocket::readyRead,[this]{
    76. //没有可读的数据就返回
    77. if(client->bytesAvailable()<=0)
    78. return;
    79. //注意收发两端文本要使用对应的编解码
    80. const QString recv_text=QString::fromUtf8(client->readAll());
    81. ui->textRecv->append(QString("[%1:%2]")
    82. .arg(client->peerAddress().toString())
    83. .arg(client->peerPort()));
    84. ui->textRecv->append(recv_text);
    85. });
    86. //error信号在5.15换了名字
    87. #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    88. //错误信息
    89. connect(client, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
    90. [this](QAbstractSocket::SocketError){
    91. ui->textRecv->append("Socket Error:"+client->errorString());
    92. });
    93. #else
    94. //错误信息
    95. connect(client,&QAbstractSocket::errorOccurred,[this](QAbstractSocket::SocketError){
    96. ui->textRecv->append("Socket Error:"+client->errorString());
    97. });
    98. #endif
    99. }
    100. void Widget::updateState()
    101. {
    102. //将当前client地址和端口写在标题栏
    103. if(client->state()==QAbstractSocket::ConnectedState){
    104. setWindowTitle(QString("Client[%1:%2]")
    105. .arg(client->localAddress().toString())
    106. .arg(client->localPort()));
    107. }else{
    108. setWindowTitle("Client");
    109. }
    110. }

    本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

  • 相关阅读:
    PyQt5快速开发与实战 9.4 Matplotlib在PyQt中的应用
    多表查询_子查询
    iOS 中,Atomic 修饰 NSString、 NSArray,也会线程不安全
    【Unity3D】资源管理
    Android之RecyclerView仿ViewPage滑动
    荷兰国旗问题与快速排序算法
    debian无法使用reboot 等系统命令解决
    $.ajax异步请求总结
    神奇的 CSS,让文字智能适配背景颜色
    win11 Windows hello录入指纹失败解决方法
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/128121828