C++ Qt开发:QTcpSocket网络通信组件

打印 上一主题 下一主题

主题 860|帖子 860|积分 2580

Qt 是一个跨平台C++图形界面开发库,使用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QTcpSocket组件实现基于TCP的网络通信功能。
QTcpSocket和QTcpServer是Qt中用于实现基于TCP(Transmission Control Protocol)通信的两个关键类。TCP是一种面向毗连的协议,它提供可靠的、双向的、面向字节省的通信。这两个类允许Qt应用程序在网络上建立客户端和服务器之间的毗连。
以下是QTcpSocket类的一些常用函数:
函数描述QTcpSocket()构造函数,创建一个新的QTcpSocket对象。~QTcpSocket()析构函数,释放QTcpSocket对象及其资源。void connectToHost(const QString &hostName, quint16 port)尝试与指定主机名和端口建立毗连。void disconnectFromHost()断开与主机的毗连。QAbstractSocket::SocketState state() const返回套接字的当前状态。QHostAddress peerAddress() const返回与套接字毗连的长途主机的地址。quint16 peerPort() const返回与套接字毗连的长途主机的端口。QAbstractSocket::SocketError error() const返回套接字的当前错误代码。qint64 write(const char *data, qint64 maxSize)将数据写入套接字,返回实际写入的字节数。qint64 read(char *data, qint64 maxSize)从套接字读取数据,返回实际读取的字节数。void readyRead()当套接字有可供读取的新数据时发出信号。void bytesWritten(qint64 bytes)当套接字已经写入指定字节数的数据时发出信号。void error(QAbstractSocket::SocketError socketError)当套接字发生错误时发出信号。以下是QTcpServer类的一些常用函数及其简要解释:
函数描述QTcpServer()构造函数,创建一个新的QTcpServer对象。~QTcpServer()析构函数,释放QTcpServer对象及其资源。bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)开始监听指定的地址和端口。void close()制止监听并关闭服务器。bool isListening() const返回服务器是否正在监听毗连。QList pendingConnections()返回等待处理的挂起毗连的列表。virtual void incomingConnection(qintptr socketDescriptor)当有新毗连时调用,可以在子类中实现以处理新毗连。void maxPendingConnections() const返回允许的最大挂起毗连数。void setMaxPendingConnections(int numConnections)设置允许的最大挂起毗连数。QNetworkProxy proxy() const返回服务器的署理设置。void setProxy(const QNetworkProxy &networkProxy)设置服务器的署理设置。QAbstractSocket::SocketError serverError() const返回服务器的当前错误代码。QString errorString() const返回服务器的错误消息字符串。void pauseAccepting()暂停接受新毗连,但保持现有毗连。void resumeAccepting()恢复接受新毗连。void close()关闭服务器。如上这些只是常用函数的简要描述,具体的函数说明和用法可以参考Qt官方文档或相关文档。
1.1 通信的流程

1.1.1 服务端流程

在使用TCP通信时同样需要导入Qt+=network模块,并在头文件中引入QTcpServer和QTcpSocket两个模块,当有了模块的支持,接着就是侦听套接字,此处可通过调用server.listen来实现侦听,此函数原型如下;
  1. bool QTcpServer::listen(
  2.     const QHostAddress &address = QHostAddress::Any,
  3.     quint16 port = 0
  4. );
复制代码
这个函数用于开始在指定的地址和端口上监听毗连。它的参数包罗:

  • address:一个QHostAddress对象,指定要监听的主机地址。默认为QHostAddress::Any,表示监听全部可用的网络接口。
  • port:一个quint16类型的端口号,指定要监听的端口。如果设置为0,体系将选择一个可用的未使用端口。
函数返回一个bool值,表示是否成功开始监听。如果成功返回true,否则返回false,并且可以通过调用errorString()获取错误消息。
紧随套接字侦听其后,通过使用一个waitForNewConnection等待新的毗连到达。它的原型如下:
  1. bool QTcpServer::waitForNewConnection(
  2.     int msec = 0,
  3.     bool *timedOut = nullptr
  4. );
复制代码
该函数在服务器接受新毗连之前会不停阻塞。参数包罗:

  • msec:等待毗连的超时时间(以毫秒为单位)。如果设置为0(默认值),则表示无限期等待,直到有新毗连到达。
  • timedOut:一个可选的布尔指针,用于指示等待是否超时。如果转达了此参数,并且等待时间达到了指定的超时时间,*timedOut将被设置为true,否则为false。如果不关心超时,可以将此参数设置为nullptr。
函数返回一个布尔值,表示是否成功等待新毗连。如果在超时时间内有新毗连到达,返回true,否则返回false。如果等待超时,可以通过查抄timedOut参数来确定。如果函数返回false,可以通过调用errorString()获取错误消息。
套接字的接收会使用nextPendingConnection()函数来实现,nextPendingConnection 是 QTcpServer 类的成员函数,用于获取下一个已接受的毗连的套接字(QTcpSocket)。它的原型如下:
  1. QTcpSocket *QTcpServer::nextPendingConnection();
复制代码
函数返回一个指向新毗连套接字的指针。如果没有已接受的毗连,则返回 nullptr。
使用这个函数,你可以在服务器接受毗连之后获取相应的套接字,以便举行数据传输和通信。一般来说,在收到 newConnection 信号后,你可以调用这个函数来获取新毗连的套接字。
当有了套接字以后,就可以通过QTcpServer指针判断对应的套接字状态,一般套接字的状态被界说在QAbstractSocket类内。以下是QAbstractSocket类中界说的一些状态及其对应的标记:
状态标记描述UnconnectedState未毗连状态,套接字没有毗连到长途主机。HostLookupState正在查找主机地址状态,套接字正在解析主机名。ConnectingState毗连中状态,套接字正在尝试与长途主机建立毗连。ConnectedState已毗连状态,套接字已经成功毗连到长途主机。BoundState已绑定状态,套接字已经与地址和端口绑定。ClosingState关闭中状态,套接字正在关闭毗连。ListeningState监听中状态,用于QTcpServer,表示服务器正在监听毗连。这些状态反映了套接字在不同阶段的毗连和通信状态。在实际使用中,可以通过调用state()函数获取当前套接字的状态,并根据需要处理相应的状态。比方,可以使用信号和槽机制来捕捉状态变化,以便在毗连建立或断开时实行相应的操纵。
当套接字被毗连后则可以通过socket->write()方法向上线客户端发送一个字符串,此处我们以发送lyshark为例,发送时需要向write()中传入两个参数。其原型如下:
  1. qint64 QTcpSocket::write(const char *data, qint64 maxSize);
复制代码
该函数接受两个参数:

  • data:指向要写入套接字的数据的指针。
  • maxSize:要写入的数据的最大字节数。
函数返回实际写入的字节数,如果发生错误,则返回 -1。在写入数据之后,可以使用 bytesWritten 信号来获取写入的字节数。此外,你也可以使用 waitForBytesWritten 函数来阻塞等待直到全部数据都被写入。
至此服务端代码可总结为如下案例;
  1. #include <QCoreApplication>
  2. #include <QTcpServer>
  3. #include <QTcpSocket>
  4. #include <iostream>
  5. int main(int argc, char *argv[])
  6. {
  7.     QCoreApplication a(argc, argv);
  8.     QTcpServer server;
  9.     server.listen(QHostAddress::Any,9000);
  10.     server.waitForNewConnection(100000);
  11.     QTcpSocket *socket;
  12.     socket = server.nextPendingConnection();
  13.     if(socket->state() && QAbstractSocket::ConnectedState)
  14.     {
  15.         QByteArray bytes = QString("lyshark").toUtf8();
  16.         socket->write(bytes.data(),bytes.length());
  17.     }
  18.     socket->close();
  19.     server.close();
  20.     return a.exec();
  21. }
复制代码
1.1.2 客户端流程

客户端的流程与服务端根本保持一致,唯一的区别在于将server.listen更换为socket.connectToHost毗连到对应的主机,QTcpSocket 的 connectToHost 函数的原型如下:
  1. void QTcpSocket::connectToHost(
  2. const QString &hostName,
  3. quint16 port,
  4. OpenMode openMode = ReadWrite
  5. );
复制代码

  • hostName:长途主机的主机名或IP地址。
  • port:要毗连的端口号。
  • openMode:套接字的打开模式,默认为 ReadWrite。
函数用于初始化与指定长途主机和端口的毗连。在实际使用中,你可以通过调用这个函数来发起与目标主机的毗连尝试。
读取数据时可以使用readAll函数来实现,socket.readAll() 是 QTcpSocket 类的成员函数,用于读取全部可用的数据并返回一个 QByteArray 对象。其函数函数原型如下:
  1. QByteArray QTcpSocket::readAll();
复制代码
该函数返回一个包含从套接字中读取的全部数据的 QByteArray 对象。通常,你可以通过这个函数来获取已经到达的全部数据,然后对这些数据举行进一步的处理。其客户端功能如下所示;
  1. #include <QCoreApplication>
  2. #include <QTcpServer>
  3. #include <QTcpSocket>
  4. #include <iostream>
  5. int main(int argc, char *argv[])
  6. {
  7.     QCoreApplication a(argc, argv);
  8.     QTcpSocket socket;
  9.     socket.connectToHost(QHostAddress::LocalHost,9000);
  10.     if(socket.state() && QAbstractSocket::ConnectedState)
  11.     {
  12.         socket.waitForReadyRead(10000);
  13.         QByteArray ref = socket.readAll();
  14.         QString ref_string;
  15.         ref_string.prepend(ref);
  16.         std::cout << ref_string.toStdString() << std::endl;
  17.     }
  18.     socket.close();
  19.     return a.exec();
  20. }
复制代码
当读者点击侦听时则直接调用tcpServer->listen实现对本地IP的8888端口的侦听功能,制止侦听则是调用tcpServer->close函数实现,如下所示;
  1. MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
  2. {
  3.     ui->setupUi(this);
  4.     // 新建TCP套接字类
  5.     tcpServer=new QTcpServer(this);
  6.     // 连接信号初始化其他信号
  7.     connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));
  8. }
复制代码
对于读取数据可以通过canReadLine()函数判断行,并通过tcpClient->readLine()逐行读入数据,相对应的发送数据可通过调用tcpSocket->write函数实现,在发送之前需要将其转换为QByteArray类型的字符串格式,如下所示;
  1. // 初始化信号槽函数
  2. void MainWindow::onNewConnection()
  3. {
  4.     // 创建新套接字
  5.     tcpSocket = tcpServer->nextPendingConnection();
  6.     // 连接触发信号
  7.     connect(tcpSocket, SIGNAL(connected()),this, SLOT(onClientConnected()));
  8.     onClientConnected();
  9.     // 关闭触发信号
  10.     connect(tcpSocket, SIGNAL(disconnected()),this, SLOT(onClientDisconnected()));
  11.     // 状态改变触发信号
  12.     connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
  13.     onSocketStateChange(tcpSocket->state());
  14.     // 读入数据触发信号
  15.     connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
  16. }
复制代码
运行服务端程序,并点击侦听按钮此时将会在本地的8888端口上启用侦听,如下图所示;

1.2.2 客户端流程

对于客户端而言同样需要绑定四个信号并对应到特定的槽函数上,其初始化部门与服务端保持一致,唯一不同的是客户端使用connectToHost函数链接到服务端上,断开毗连时使用的是disconnectFromHost函数,如下所示;
  1. // 开始侦听
  2. void MainWindow::on_pushButton_2_clicked()
  3. {
  4.     // 此处指定绑定本机的8888端口
  5.     tcpServer->listen(QHostAddress::LocalHost,8888);
  6.     ui->plainTextEdit->appendPlainText("[+] 开始监听");
  7.     ui->plainTextEdit->appendPlainText(" 服务器地址:" + tcpServer->serverAddress().toString() +
  8.                                        " 服务器端口:"+QString::number(tcpServer->serverPort())
  9.                                        );
  10. }
  11. // 停止侦听
  12. void MainWindow::on_pushButton_3_clicked()
  13. {
  14.     if (tcpServer->isListening())
  15.     {
  16.         tcpServer->close();
  17.     }
  18. }
复制代码
此处的读取数据与服务端保持一致,发送数据时则是通过tcpClient->write(str)函数直接转达给客户端,代码如下所示;
  1. // 读取数据
  2. void MainWindow::onSocketReadyRead()
  3. {
  4.     while(tcpSocket->canReadLine())
  5.         ui->plainTextEdit->appendPlainText("[接收] | " + tcpSocket->readLine());
  6. }
  7. // 发送数据
  8. void MainWindow::on_pushButton_clicked()
  9. {
  10.     QString  msg=ui->lineEdit->text();
  11.     ui->plainTextEdit->appendPlainText("[发送] | " + msg);
  12.     QByteArray str=msg.toUtf8();
  13.     str.append('\n');
  14.     tcpSocket->write(str);
  15. }
复制代码
运行后,服务端启用侦听等待客户端毗连,客户端毗连后,两边则可以实现数据的收发功能,由于采用了信号机制,两者的收发并不会阻断可同时举行,如下图所示;


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

渣渣兔

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表