Qt网络编程01-QTcpSocket和QTcpServer的基本使用

  • 1 前言
  • 试验效果

原文链接:https://blog.csdn.net/gongjianbo1992/article/details/107743780



1 前言

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

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

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

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

TCP 知识参考:https://blog.csdn.net/sinat_36629696/article/details/80740678

TCP 知识参考:https://www.jianshu.com/p/ca64764e4a26

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

QT += network

引入相关类的头文件:

#include <QTcpServer>
#include <QTcpSocket>
#include <QHostAddress>

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

Qt TCP 的操作流程:

图片参考:https://blog.csdn.net/qq_32298647/article/details/74834254

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,以及套接字的连接。

文档:https://doc.qt.io/qt-5/qtcpserver.html

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()

文档:https://doc.qt.io/qt-5/qabstractsocket.html

文档:https://doc.qt.io/qt-5/qtcpsocket.html

4.Qt Tcp的简单示例
服务端主要实现代码:

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE//simple Tcp 服务端
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private://初始化server操作void initServer();//close servervoid closeServer();//更新当前状态void updateState();private:Ui::Widget *ui;//server用于监听端口,获取新的tcp连接的描述符QTcpServer *server;//存储已连接的socket对象QList<QTcpSocket*> clientList;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"#include <QHostAddress>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);setWindowTitle("Server");initServer();
}Widget::~Widget()
{//关闭servercloseServer();delete ui;
}void Widget::initServer()
{//创建Server对象server = new QTcpServer(this);//点击监听按钮,开始监听connect(ui->btnListen,&QPushButton::clicked,[this]{//判断当前是否已开启,是则close,否则listenif(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()));//关联相关操作的信号槽//收到数据,触发readyReadconnect(socket,&QTcpSocket::readyRead,[this,socket]{//没有可读的数据就返回if(socket->bytesAvailable()<=0)return;//注意收发两端文本要使用对应的编解码const QString recv_text=QString::fromUtf8(socket->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<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&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]{//判断是否开启了serverif(!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 <QWidget>
#include <QTcpSocket>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE//simple Tcp 客户端
class Widget : public QWidget
{Q_OBJECTpublic: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 <QHostAddress>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还是会为trueclient->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();});//收到数据,触发readyReadconnect(client,&QTcpSocket::readyRead,[this]{//没有可读的数据就返回if(client->bytesAvailable()<=0)return;//注意收发两端文本要使用对应的编解码const QString recv_text=QString::fromUtf8(client->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<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&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");}
}

试验效果

void MainWindow::initSever()
{//创建server对象server = new QTcpServer(this);//监听到新的客户端连接请求connect(server, &QTcpServer::newConnection, this, [this]{//如果有新的连接就取出while(server->hasPendingConnections()){//nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象//套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。//最好在完成处理后显式删除该对象,以避免浪费内存。//返回的QTcpSocket对象不能从另一个线程使用,如有需要可重写incomingConnection().qDebug()<<"有新的客户端接入";QTcpSocket *socket = server->nextPendingConnection();clientList.append(socket);//关联相关socket操作的信号槽//收到信号,出发readyReadconnect(socket, &QTcpSocket::readyRead, this, [=]{static quint16 i = 0;//没有可读的数据就返回if(socket->bytesAvailable()<=0){return ;}//注意收发两端文本要使用对应的编解码const QByteArray recv_test = socket->readAll();dataParse(recv_test);//qDebug()<<"recv msg:"<<recv_test;QString ret;QByteArray ba = recv_test.toHex(' ');qDebug()<<"ba: "<<ba;for (int i=0; i<recv_test.count(); ++i) {ret.append(tr("%1 ").arg((quint8)recv_test.at(i),2,16,QLatin1Char('0')).toUpper());}ui->textEdit_message->append(ba.toUpper()); // ret换成bai++;//qDebug()<<"i: "<<i;//超过5000条记录,历史记录自动清楚if(i > 5000){ui->textEdit_message->clear();i = 0;}});//连接断开,销毁socket对象,这是为了开关server时socket正确释放connect(socket, &QTcpSocket::disconnected, this, [=]{socket->deleteLater();clientList.removeOne(socket);ui->textEdit_message->append(QString("%1:%2 Socket Disconnected").arg(socket->peerAddress().toString()).arg(socket->peerPort()));});}});}void MainWindow::closeServer()
{//停止服务server->close();for(QTcpSocket *socket: clientList){//断开与客户端的连接socket->disconnectFromHost();if(socket->state() != QAbstractSocket::UnconnectedState){socket->abort();}}
}void MainWindow::on_pBtnListen_clicked()
{if(server->isListening()){closeServer();//关闭server后恢复界面状态ui->pBtnListen->setText("开始监听");ui->pBtnSendCmd->setEnabled(false);}//开始监听,判断是否成功else if (server->listen(QHostAddress::Any, 8000)) {ui->pBtnListen->setText("停止监听");ui->pBtnSendCmd->setEnabled(true);}
}void MainWindow::on_pBtnSendCmd_clicked()
{QString str1 = ui->lineEdit_SendCmd->text();QByteArray cmd = QByteArray::fromHex(str1.toLatin1().data()); //按照十六进制编码接入文本//qDebug()<<ba1;//server向client发送内容//判断是否开启了serverif(!server->isListening())return;//将发送区文本发送给客户端//数据为空就返回if(cmd.isEmpty())return;for(QTcpSocket *socket:clientList){socket->write(cmd);//socket->waitForBytesWritten();}
}

Qt网络编程01-QTcpSocket和QTcpServer的基本使用相关推荐

  1. Qt网络编程概述(一)

    分享主题 Qt网络编程概述(一) Qt网络编程之QTCPSocket和QTCPServer实例(二) Qt网络编程之QUdpSocket实例(三) Qt网络编程概述 QtNetWork模块提供了若干类 ...

  2. [Qt教程] 第31篇 网络(一)Qt网络编程简介

    [Qt教程] 第31篇 网络(一)Qt网络编程简介 楼主  发表于 2013-8-28 17:04:17 | 查看: 515| 回复: 0 Qt网络编程简介 版权声明 该文章原创于作者yafeilin ...

  3. qt android 网络编程实例,QT网络编程Tcp下C/S架构的即时通信实例

    先写一个客户端,实现简单的,能加入聊天,以及加入服务器的界面. #ifndef TCPCLIENT_H #define TCPCLIENT_H #include #include #include # ...

  4. Qt网络编程——TCP

    Qt网络编程--TCP 1. 概念 2. 服务器 3. 客户端 4. TCP服务器和客户端互传文件 5. 资源下载 1. 概念 TCP(Transmission Control Protocol, 传 ...

  5. Qt网络编程实战之HTTP服务器-安晓辉-专题视频课程

    Qt网络编程实战之HTTP服务器-30196人已学习 课程介绍         设计了一个实用的HTTP服务器,基于Qt的网络框架实现.先详细介绍Qt的网络.IO.线程..自定义事件.配置文件等类库的 ...

  6. 第十四章:Qt网络编程

    回顾: 第一章:Qt的概述 第二章:在Ubuntu编写第一个Qt程序 第三章:Qt的字符串和字符编码 第四章:Qt的信号和槽 第五章:Qt容器窗口(父窗口) 第六章:面向对象的Qt编程 第七章:Qt设 ...

  7. 【Qt入门第31篇】 网络(一)Qt网络编程简介

    导语 从这一节开始我们讲述Qt网络应用方面的编程知识.在开始这部分知识的学习之前,大家最好已经拥有了一定的网络知识和Qt的编程基础.在后面的教程中我们不会对一个常用的网络名词进行详细的解释,对于不太了 ...

  8. QT网络编程开发服务端

    下一篇: QT网络编程开发客户端 文章目录 基于Qt的网络编程服务端 QTcpServer 配置 listen() close() newConnection() SINGL readyRead() ...

  9. python运维开发之socket网络编程01

    python运维开发之socket网络编程01说明:本文来自来自北京老男孩linux运维实战培训中心-运维开发课程免费视频内容,本文内容为系列内容,更多分享信息见:http://oldboy.blog ...

  10. Qt网络编程之实例一GET方式

    看了两天的Qt网络编程,其实主要就是看了看QNetworkAccessManager.QNetworkRequest和QNetworkReply这三个类的主要内容.在之前,Qt网络编程主要是使用QHt ...

最新文章

  1. 网络推广团队介绍网站页面优化时需要注意什么细节?
  2. VTK:可视化之StreamLines
  3. 对象的多态(核心、困难、重点)
  4. 【线上分享】短视频出海 — 用户体验衡量关键指标与优化策略
  5. 【好用的ORM框架】
  6. linux内核 块驱动程序,linux – 为什么内核使用默认的块驱动程序而不是我的驱动程序代码?...
  7. Swift3.0 键盘高度监听获取
  8. 这个保守的 RNA motif是病毒侵染的关键
  9. 【搬运】各种知乎段子
  10. Matlab编程基础
  11. MPI/DP转以太网通过CHNet-S7200/300连接IFIX组态TCP通信
  12. 微信群机器人管理系统源码+支持同登陆多号
  13. MySql常用命令集
  14. AM5SE-IS防孤岛保护装置如何解决分布式光伏发电过程中的影响?
  15. TortoiseSVN 帮助教程(一)—— 建立版本库
  16. Centos7 快捷键调节屏幕亮度
  17. 哦, 这该死的txt回车符~
  18. 学习Cocos的第二天-序列帧动画
  19. How to Add a Dotted Underline Beneath HTML Text
  20. mysql utf8 表情符号_MySQLutf8mb4字符集:支持emoji表情符号

热门文章

  1. imagecropper php,cropper+php+ajax实现上传头像
  2. C#+ AE实现地图注记功能
  3. 设计配色的基本知识以及原理
  4. C语言链表详解(通俗易懂)
  5. NXP智能车竞赛笔记(室外电磁组)
  6. CentOS mysql配置主从复制
  7. rba有哪几個主要組成部分_RBA管理体系有哪些
  8. CardView的基本使用、DrawerLayout 滑动菜单、Fragment
  9. Qemu复现雄迈摄像头固件漏洞
  10. php 百望电子发票,百望云电子发票服务平台开票工具