要使用 Qt 网络编程,需要在项目中的 .pro
文件中添加 network
模块
核心API
核心类有两个:QTcpServer
和 QTcpSocket
QTcpServer
用于监听端口,和获取客户端连接
名称 | 类型 | 说明 | 对标原生API |
---|---|---|---|
listen(const QHostAddress &address, quint16 port) | 方法 | 绑定指定的 IP地址和端口号,并开始监听 | bind 和 listen |
nextPendingConnection() | 方法 | 从系统中获取一个已经建立好的tcp连接 返回一个 QTcpSocket ,表示这个客户端的连接 通过这个 socket 对象完成和客户端的通信 | accept |
newConnection | 信号 | 有新的客户端建立连接好之后触发 | 无,类似 IO多路复用 中的通知机制 |
QTcpSocket
用于客户端和服务端之间的数据交互
名称 | 类型 | 说明 | 对标原生API |
---|---|---|---|
readAll() | 方法 | 读取当前接收缓冲区中的所有数据 返回 QByteArray 对象 | read |
write(const QByteArray &byteArray) | 方法 | 把数据写入 socket 中 | write |
deleteLater | 方法 | 暂时把 socket 对象标记为无效。Qt 会在下次事件循环中析构释放该对象 | 类似“半自动的垃圾回收机制” |
readyRead | 信号 | 有数据到达并准备就绪时触发 | 类似 IO多路复用的通知机制 |
disconnected | 信号 | 连接断开时触发 | 类似 IO多路复用的通知机制 |
QByteArray
表示一个字节数组,Qt 提供了 QByteArray 和 QString 的转换接口
使用 QString 的赋值重载,即可把 QByteArray 转换成 QString
使用 QString 的toUtf8()函数
把 QString 转换成 QByteArray
代码示例
通过模拟 客户服务器的TCP通信
熟悉上述方法使用
客户端通过输入栏获取数据,点击按钮发送给服务端
服务端接受客户端数据,并返回相同数据,验证通信无误
服务器
- 编写 UI文件,创建界面,包含一个
QListWidget
,用于显示收到的数据
2. 编写 widget.h
,添加QTcpServer
指针成员,槽函数和业务处理函数
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
//槽函数
public slots:
void handleConnect();//处理新连接
private:
QString handleRequest(const QString &request);//处理请求
private:
Ui::Widget *ui;
QTcpServer *listensock;
};
- 编写
widget.cpp
,完成QTcpServer
实例化和相关初始化。
具体逻辑如下:
- 设置窗口标题
- 实例化
QTcpServer
,创建套接字- 连接信号槽,处理客户端建立的新连接
- 绑定端口号,监听端口
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//1. 设置窗口标题
this->setWindowTitle("服务端");
//2. 创建套接字
listensock = new QTcpServer(this);
//3. 连接信号槽
connect(listensock, &QTcpServer::newConnection, this, &Widget::handleConnect);
//4. 绑定端口号,监听套接字
bool ret = listensock->listen(QHostAddress::Any, 9090);
if(!ret)
{
QMessageBox::critical(this, "绑定端口号失败", listensock->errorString());
exit(1);
}
}
- 编写
widget.cpp
的新连接处理槽函数handleConnect()
逻辑如下:
- 获取新连接
- 连接信号槽,处理客户端数据
- 连接信号槽,处理客户端断开连接
//处理新连接
void Widget::handleConnect()
{
//1. 获取连接
QTcpSocket *clientSocket = listensock->nextPendingConnection();
//2. 记录客户端信息
//peerAddress是对端地址的意思
QString log = "[" + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] 客户端上线!";
ui->listWidget->addItem(log);
//3. 连接读数据的信号槽
connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
//a) 获取TCP字节流内容
QString request = clientSocket->readAll();
//b) 业务处理
const QString &responce = handleRequest(request);
//c) 返回响应给客户端
clientSocket->write(responce.toUtf8());
//d) 记录数据
QString log = "[" + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] request:"
+ request + " responce:" + responce;
ui->listWidget->addItem(log);
});
//4. 连接断开后释放资源
connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
//a) 记录客户端下线信息
QString log = "[" + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] 客户端下线!";
ui->listWidget->addItem(log);
//b) 释放资源
clientSocket->deleteLater();
});
}
客户端断开连接,不能直接delete 释放
客户端QTcpSocket
,因为可能服务端还有数据传输,所以需要在数据完全传输结束后再释放连接,而Qt 提供的deleteLater()
就是在下一次事件循环时,检测该客户端连接没有数据传输,才释放连接
- 编写
widget.cpp
,实现业务处理函数,因为是简单的回显服务端,业务处理较为简单
//业务处理
QString Widget::handleRequest(const QString &request)
{
return request;
}
客户端
- 编写 UI文件,创建界面,包含一个
QLineEdit
,QPushButton
,QListWidget
,并通过布局管理器规范控件
- 编写
widget.h
,声明QTcpSocket
指针成员和按钮槽函数
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QTcpSocket *socket;
};
- 编写
widget.cpp
,实例化QTcpSocket
,完成相应初始化
具体逻辑如下:
- 设置窗口标题
- 实例化
QTcpSocket
,创建套接字- 连接信号槽,处理服务端返回的响应
- 连接服务端
- 等待连接,查看连接是否成功
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置窗口标题
this->setWindowTitle("客户端");
//1. 创建套接字
socket = new QTcpSocket(this);
//2. 连接客户端收到服务端消息的槽函数
connect(socket, &QTcpSocket::readyRead, this, [=](){
QString responce = socket->readAll();
QString log = "服务端响应:" + responce;
ui->listWidget->addItem(log);
});
//3. 连接服务端,该连接不是阻塞的
socket->connectToHost("127.0.0.1", 9090);
//4. 查看连接是否成功
bool ret = socket->waitForConnected();
if(!ret)
{
QMessageBox::critical(this, "连接服务器失败", socket->errorString());
exit(1);
}
}
- 编写
widget.cpp
,实现按钮的槽函数
具体逻辑如下:
- 获取输入栏数据
- 清空输入栏
- 记录数据
- 发送数据到服务端
void Widget::on_pushButton_clicked()
{
//1. 获取输入框内容
QString text = ui->lineEdit->text();
//2. 清空输入栏
ui->lineEdit->clear();
//3. 记录发送
QString log = "客户端发送:" + text;
ui->listWidget->addItem(log);
//4. 发送数据给服务端
socket->write(text.toUtf8());
}
运行结果如下:
结束语
感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。