Qt 网络
和多线程类似,Qt 为了⽀持跨平台,对网络编程的 API 也进行了重新封装。
在进行网络编程之前,需要在项目中的 .pro 文件中添加 network 模块。添加之后要手动编译⼀下项目,使 Qt Creator 能够加载对应模块的头文件。
1. UDP Socket
(1)核心 API 概览
主要的类有两个:QUdpSocket 和 QNetworkDatagram
QUdpSocket 表示⼀个 UDP 的 socket 文件。
QNetworkDatagram 表示⼀个 UDP 数据报
(2)回显服务器
1、创建界面,包含⼀个 QListWidget 用来显示信息 2、 创建 QUdpSocket 成员,修改 widget.h:
代码语言:javascript复制 class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QUdpSocket *socket;
};
修改 widget.cpp,完成 socket 后续的初始化。⼀般来说, 要先连接信号槽, 再绑定端⼝。如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了.
代码语言:javascript复制 Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题
this->setWindowTitle("服务器");
// 2. 实例化 socket
socket = new QUdpSocket(this);
// 3. 连接信号槽, 处理收到的请求
connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
// 4. 绑定端⼝
bool ret = socket->bind(QHostAddress::Any, 9090);
if (!ret)
{
QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
return;
}
}
3、实现 processRequest , 完成处理请求的过程
读取请求并解析
根据请求计算响应
把响应写回到客户端
代码语言:javascript复制 void Widget::processRequest()
{
// 1. 读取请求
const QNetworkDatagram &requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data();
// 2. 根据请求计算响应
const QString &response = process(request);
// 3. 把响应写回到客⼾端
QNetworkDatagram responseDatagram(response.toUtf8(),
requestDatagram.senderAddress(), requestDatagram.senderPort());
socket->writeDatagram(responseDatagram);
// 显⽰打印⽇志
QString log = "[" requestDatagram.senderAddress().toString() ":"
QString::number(requestDatagram.senderPort()) "] req: " request ", resp: " response;
ui->listWidget->addItem(log);
}
4、实现 process 函数
由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容.
代码语言:javascript复制 QString Widget::process(const QString& request)
{
return request;
}
(3)回显客户端
1、创建界面,包含⼀个 QLineEdit , QPushButton , QListWidget;
- 先使用⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的 sizePolicy 为 Expanding • 再使用垂直布局把 QListWidget 和上面的水平布局放好. • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调).
2、在 widget.cpp 中, 先创建两个全局常量, 表⽰服务器的 IP 和 端⼝
代码语言:javascript复制 // 提前定义好服务器的 IP 和 端⼝
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
3、创建 QUdpSocket 成员
修改 widget.h, 定义成员
代码语言:javascript复制 class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 socket 成员
QUdpSocket *socket;
};
修改 widget.cpp, 初始化 socket
代码语言:javascript复制 Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝名字
this->setWindowTitle("客⼾端");
// 2. 实例化 socket
socket = new QUdpSocket(this);
}
4、给发送按钮 slot 函数, 实现发送请求
代码语言:javascript复制 void Widget::on_pushButton_clicked()
{
// 1. 获取到输⼊框的内容
const QString &text = ui->lineEdit->text();
// 2. 构造请求数据
QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP),
SERVER_PORT);
// 3. 发送请求
socket->writeDatagram(requestDatagram);
// 4. 消息添加到列表框中
ui->listWidget->addItem("客户端说: " text);
// 5. 清空输⼊框
ui->lineEdit->setText("");
}
5、再次修改 Widget 的构造函数, 通过信号槽, 来处理服务器的响应.
代码语言:javascript复制 connect(socket, &QUdpSocket::readyRead, this, [=]()
{
const QNetworkDatagram responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
ui->listWidget->addItem(QString("服务器说: ") response);
});
2. TCP Socket
(1)核心 API 概览
核⼼类是两个: QTcpServer 和 QTcpSocket
QTcpServer 用于监听端口, 和获取客户端连接。
QTcpSocket 用户客户端和服务器之间的数据交互。
QByteArray 用于表示⼀个字节数组. 可以很⽅便的和 QString 进行相互转换.
例如:
- 使用 QString 的构造函数即可把 QByteArray 转成 QString.
- 使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray.
(2)回显服务器
1、创建界⾯. 包含⼀个 QListWidget , ⽤于显⽰收到的数据.
2、创建 QTcpServer 并初始化
修改 widget.h, 添加 QTcpServer 指针成员
代码语言:javascript复制 class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 QTcpServer
QTcpServer *tcpServer;
};
修改 widget.cpp, 实例化 QTcpServer 并进⾏后续初始化操作.
设置窗⼝标题
实例化 TCP server. (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
通过信号槽, 处理客⼾端建⽴的新连接.
监听端口
代码语言:javascript复制 Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题
this->setWindowTitle("服务器");
// 2. 实例化 TCP server
tcpServer = new QTcpServer(this);
// 3. 通过信号槽, 处理客⼾端建⽴的新连接.
connect(tcpServer, &QTcpServer::newConnection, this,
&Widget::processConnection);
// 4. 监听端⼝
bool ret = tcpServer->listen(QHostAddress::Any, 9090);
if (!ret)
{
QMessageBox::critical(nullptr, "服务器启动失败!", tcpServer - > errorString());
exit(1);
}
}
3、继续修改 widget.cpp, 实现处理连接的具体⽅法 processConnection
获取到新的连接对应的 socket.
通过信号槽, 处理收到请求的情况
通过信号槽, 处理断开连接的情况
代码语言:javascript复制 void Widget::processConnection()
{
// 1. 获取到新的连接对应的 socket.
QTcpSocket *clientSocket = tcpServer->nextPendingConnection();
QString log = QString("[") clientSocket->peerAddress().toString() ":" QString::number(clientSocket->peerPort()) "] 客⼾端上线!";
ui->listWidget->addItem(log);
// 2. 通过信号槽, 处理收到请求的情况
connect(clientSocket, &QTcpSocket::readyRead, this, [=]()
{
// a) 读取请求
QString request = clientSocket->readAll();
// b) 根据请求处理响应
const QString& response = process(request);
// c) 把响应写回客⼾端
clientSocket->write(response.toUtf8());
QString log = QString("[") clientSocket->peerAddress().toString()
":" QString::number(clientSocket->peerPort()) "] req: "
request ", resp: " response;
ui->listWidget->addItem(log);
});
// 3. 通过信号槽, 处理断开连接的情况
connect(clientSocket, &QTcpSocket::disconnected, this, [=]()
{
QString log = QString("[") clientSocket->peerAddress().toString()
":" QString::number(clientSocket->peerPort()) "] 客户端下线!";
ui->listWidget->addItem(log);
// 删除 clientSocket
clientSocket->deleteLater();
});
}
4、实现 process 方法, 实现根据请求处理响应
由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容
代码语言:javascript复制 QString Widget::process(const QString &request)
{
return request;
}
(3)回显客户端
1、创建界⾯;包含⼀个 QLineEdit , QPushButton , QListWidget
- 先使用⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的 sizePolicy 为 Expanding
- 再使用垂直布局把 QListWidget 和上面的水平布局放好.
- 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个⼈喜好微调).
2、创建 QTcpSocket 并实例化
修改 widget.h, 创建成员.
代码语言:javascript复制 class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 新增 QTcpSocket
QTcpSocket *socket;
};
修改 widget.cpp, 对 QTcpSocket 进行实例化
设置窗⼝标题
实例化 socket 对象 (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
和服务器建⽴连接.
等待并确认连接是否出错.
代码语言:javascript复制 Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题.
this->setWindowTitle("客⼾端");
// 2. 实例化 socket 对象.
socket = new QTcpSocket(this);
// 3. 和服务器建⽴连接.
socket->connectToHost("127.0.0.1", 9090);
// 4. 等待并确认连接是否出错.
if (!socket->waitForConnected())
{
QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
exit(1);
}
}
3、修改 widget.cpp, 给按钮增加点击的 slot 函数, 实现发送请求给服务器.
代码语言:javascript复制 void Widget::on_pushButton_clicked()
{
// 获取输⼊框的内容
const QString &text = ui->lineEdit->text();
// 清空输⼊框内容
ui->lineEdit->setText("");
// 把消息显⽰到界⾯上
ui->listWidget->addItem(QString("客⼾端说: ") text);
// 发送消息给服务器
socket->write(text.toUtf8());
}
4、修改 widget.cpp 中的 Widget 构造函数, 通过信号槽, 处理收到的服务器的响应.
代码语言:javascript复制 // 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]()
{
QString response = socket->readAll();
qDebug() << response;
ui->listWidget->addItem(QString("服务器说: ") response);
});