qt中Qtcpserver服务端 您所在的位置:网站首页 如何开启websocket qt中Qtcpserver服务端

qt中Qtcpserver服务端

2022-12-29 17:39| 来源: 网络整理| 查看: 265

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

引入相关类的头文件:

#include #include #include

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

Qt TCP 的操作流程:

 

2.认识QTcpSocket的接口

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

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

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

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

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

void QAbstractSocket::connected() void QAbstractSocket::disconnected()

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

qint64 QIODevice::read(char *data, qint64 maxSize) QByteArray QIODevice::read(qint64 maxSize) QByteArray QIODevice::readAll() qint64 QIODevice::write(const char *data, qint64 maxSize) qint64 QIODevice::write(const char *data) qint64 QIODevice::write(const QByteArray &byteArray)

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

void QIODevice::readyRead()

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

void QAbstractSocket::disconnectFromHost() void QAbstractSocket::close() 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两个子项目):

运行效果:

 

服务端主要实现代码:

#ifndef WIDGET_H #define WIDGET_H #include #include #include QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE //simple Tcp 服务端 class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private: //初始化server操作 void initServer(); //close server void closeServer(); //更新当前状态 void updateState(); private: Ui::Widget *ui; //server用于监听端口,获取新的tcp连接的描述符 QTcpServer *server; //存储已连接的socket对象 QList clientList; }; #endif // WIDGET_H #include "widget.h" #include "ui_widget.h" #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); setWindowTitle("Server"); initServer(); } Widget::~Widget() { //关闭server closeServer(); delete ui; } void Widget::initServer() { //创建Server对象 server = new QTcpServer(this); //点击监听按钮,开始监听 connect(ui->btnListen,&QPushButton::clicked,[this]{ //判断当前是否已开启,是则close,否则listen if(server->isListening()){ //server->close(); closeServer(); //关闭server后恢复界面状态 ui->btnListen->setText("Listen"); ui->editAddress->setEnabled(true); ui->editPort->setEnabled(true); }else{ //从界面上读取ip和端口 //可以使用 QHostAddress::Any 监听所有地址的对应端口 const QString address_text=ui->editAddress->text(); const QHostAddress address=(address_text=="Any") ?QHostAddress::Any :QHostAddress(address_text); const unsigned short port=ui->editPort->text().toUShort(); //开始监听,并判断是否成功 if(server->listen(address,port)){ //连接成功就修改界面按钮提示,以及地址栏不可编辑 ui->btnListen->setText("Close"); ui->editAddress->setEnabled(false); ui->editPort->setEnabled(false); } } updateState(); }); //监听到新的客户端连接请求 connect(server,&QTcpServer::newConnection,this,[this]{ //如果有新的连接就取出 while(server->hasPendingConnections()) { //nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象 //套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。 //最好在完成处理后显式删除该对象,以避免浪费内存。 //返回的QTcpSocket对象不能从另一个线程使用,如有需要可重写incomingConnection(). QTcpSocket *socket=server->nextPendingConnection(); clientList.append(socket); ui->textRecv->append(QString("[%1:%2] Soket Connected") .arg(socket->peerAddress().toString()) .arg(socket->peerPort())); //关联相关操作的信号槽 //收到数据,触发readyRead connect(socket,&QTcpSocket::readyRead,[this,socket]{ //没有可读的数据就返回 if(socket->bytesAvailable()readAll()); ui->textRecv->append(QString("[%1:%2]") .arg(socket->peerAddress().toString()) .arg(socket->peerPort())); ui->textRecv->append(recv_text); }); //error信号在5.15换了名字 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) //错误信息 connect(socket, static_cast(&QAbstractSocket::error), [this,socket](QAbstractSocket::SocketError){ ui->textRecv->append(QString("[%1:%2] Soket Error:%3") .arg(socket->peerAddress().toString()) .arg(socket->peerPort()) .arg(socket->errorString())); }); #else //错误信息 connect(socket,&QAbstractSocket::errorOccurred,[this,socket](QAbstractSocket::SocketError){ ui->textRecv->append(QString("[%1:%2] Soket Error:%3") .arg(socket->peerAddress().toString()) .arg(socket->peerPort()) .arg(socket->errorString())); }); #endif //连接断开,销毁socket对象,这是为了开关server时socket正确释放 connect(socket,&QTcpSocket::disconnected,[this,socket]{ socket->deleteLater(); clientList.removeOne(socket); ui->textRecv->append(QString("[%1:%2] Soket Disonnected") .arg(socket->peerAddress().toString()) .arg(socket->peerPort())); updateState(); }); } updateState(); }); //server向client发送内容 connect(ui->btnSend,&QPushButton::clicked,[this]{ //判断是否开启了server if(!server->isListening()) return; //将发送区文本发送给客户端 const QByteArray send_data=ui->textSend->toPlainText().toUtf8(); //数据为空就返回 if(send_data.isEmpty()) return; for(QTcpSocket *socket:clientList) { socket->write(send_data); //socket->waitForBytesWritten(); } }); //server的错误信息 //如果发生错误,则serverError()返回错误的类型, //并且可以调用errorString()以获取对所发生事件的易于理解的描述 connect(server,&QTcpServer::acceptError,[this](QAbstractSocket::SocketError){ ui->textRecv->append("Server Error:"+server->errorString()); }); } void Widget::closeServer() { //停止服务 server->close(); for(QTcpSocket * socket:clientList) { //断开与客户端的连接 socket->disconnectFromHost(); if(socket->state()!=QAbstractSocket::UnconnectedState){ socket->abort(); } } } void Widget::updateState() { //将当前server地址和端口、客户端连接数写在标题栏 if(server->isListening()){ setWindowTitle(QString("Server[%1:%2] connections:%3") .arg(server->serverAddress().toString()) .arg(server->serverPort()) .arg(clientList.count())); }else{ setWindowTitle("Server"); } }

客户端主要实现代码:

#ifndef WIDGET_H #define WIDGET_H #include #include QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE //simple Tcp 客户端 class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private: //初始化client操作 void initClient(); //更新当前状态 void updateState(); private: Ui::Widget *ui; //socket对象 QTcpSocket *client; }; #endif // WIDGET_H #include "widget.h" #include "ui_widget.h" #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); setWindowTitle("Client"); initClient(); } Widget::~Widget() { //析构关闭连接 //client->disconnectFromHost(); //if(client->state()!=QAbstractSocket::UnconnectedState){ // client->waitForDisconnected(); //} //关闭套接字的I/O设备,并调用disconnectFromHost()关闭套接字的连接。 //client->close(); //中止当前连接并重置套接字。与disconnectFromHost()不同, //此函数立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。 client->abort(); delete ui; } void Widget::initClient() { //创建client对象 client = new QTcpSocket(this); //点击连接,根据ui设置的服务器地址进行连接 connect(ui->btnConnect,&QPushButton::clicked,[this]{ //判断当前是否已连接,连接了就断开 if(client->state()==QAbstractSocket::ConnectedState){ //如果使用disconnectFromHost()不会重置套接字,isValid还是会为true client->abort(); }else if(client->state()==QAbstractSocket::UnconnectedState){ //从界面上读取ip和端口 const QHostAddress address=QHostAddress(ui->editAddress->text()); const unsigned short port=ui->editPort->text().toUShort(); //连接服务器 client->connectToHost(address,port); }else{ ui->textRecv->append("It is not ConnectedState or UnconnectedState"); } }); //连接状态 connect(client,&QTcpSocket::connected,[this]{ //已连接就设置为不可编辑 ui->btnConnect->setText("Disconnect"); ui->editAddress->setEnabled(false); ui->editPort->setEnabled(false); updateState(); }); connect(client,&QTcpSocket::disconnected,[this]{ //断开连接还原状态 ui->btnConnect->setText("Connect"); ui->editAddress->setEnabled(true); ui->editPort->setEnabled(true); updateState(); }); //发送数据 connect(ui->btnSend,&QPushButton::clicked,[this]{ //判断是可操作,isValid表示准备好读写 if(!client->isValid()) return; //将发送区文本发送给客户端 const QByteArray send_data=ui->textSend->toPlainText().toUtf8(); //数据为空就返回 if(send_data.isEmpty()) return; client->write(send_data); //client->waitForBytesWritten(); }); //收到数据,触发readyRead connect(client,&QTcpSocket::readyRead,[this]{ //没有可读的数据就返回 if(client->bytesAvailable()readAll()); ui->textRecv->append(QString("[%1:%2]") .arg(client->peerAddress().toString()) .arg(client->peerPort())); ui->textRecv->append(recv_text); }); //error信号在5.15换了名字 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) //错误信息 connect(client, static_cast(&QAbstractSocket::error), [this](QAbstractSocket::SocketError){ ui->textRecv->append("Socket Error:"+client->errorString()); }); #else //错误信息 connect(client,&QAbstractSocket::errorOccurred,[this](QAbstractSocket::SocketError){ ui->textRecv->append("Socket Error:"+client->errorString()); }); #endif } void Widget::updateState() { //将当前client地址和端口写在标题栏 if(client->state()==QAbstractSocket::ConnectedState){ setWindowTitle(QString("Client[%1:%2]") .arg(client->localAddress().toString()) .arg(client->localPort())); }else{ setWindowTitle("Client"); } }

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有