qt中Qtcpserver服务端_qt websocket

qt中Qtcpserver服务端_qt websocket0 前言 本文主要讲解 Qt TCP 相关接口的基本应用 一些实践相关的后面会单独写 TCP 协议是一种面向连接的 可靠的 基于字节流的传输层通信协议 TCP 通过检验和 序列号 确认应答 重发控制 连接管理以及窗口控制等机制实现可靠性传输 TCP 通过三次握手来建立可靠的连接 TCP 四次挥手断开连接 TCP 连接是双向的 在四次挥手中 前两次挥手用于断开一个方向的连接

0.前言

本文主要讲解 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 
#include
#include

另外, 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的简单示例

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

https://github.com/gongjianbo/HelloQtNetwork

运行效果:

服务端主要实现代码:

#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()<=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(&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()<=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(&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");
}
}
编程小号
上一篇 2025-03-20 17:06
下一篇 2025-02-16 16:46

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/146425.html