3. QTcpServer基础:从入门到实战
大家好,我是老张。今天咱们来聊聊QTcpServer这个类。说实话,我在做网络编程的头两年,对这个类的理解也就停留在「能监听端口」这个层面。直到有一次做一个高并发的服务器项目,才真正把它吃透。嗯,今天就把这些经验分享给你。
3.1 QTcpServer是什么?
QTcpServer,说白了就是一个「接客」的类。它不负责具体的数据收发,只负责监听端口、接受连接。一旦有客户端连上来,它就创建一个QTcpSocket交给你处理。
我个人习惯把它比作「前台接待员」——它只管登记来访者,具体谈什么业务,那是QTcpSocket的事。
核心要点:QTcpServer只负责监听和接受连接,不负责数据传输。数据传输是QTcpSocket的活。
3.2 QTcpServer的常用方法
我整理了一下,实际项目中90%的场景,你只需要掌握下面这几个方法:
| 方法名 | 作用 | 我的使用建议 |
|---|---|---|
| listen() | 开始监听指定地址和端口 | 最常用的方法,记得检查返回值 |
| close() | 停止监听,释放端口 | 程序退出前一定要调用 |
| isListening() | 检查是否正在监听 | 调试时很有用 |
| nextPendingConnection() | 取出下一个等待的连接 | 在newConnection信号触发后调用 |
| serverPort() | 获取当前监听的端口号 | 自动分配端口时特别有用 |
| serverAddress() | 获取当前监听的地址 | 多网卡环境下确认绑定地址 |
我的小技巧:listen()的第二个参数可以传QHostAddress::Any,这样会自动监听所有网卡。但如果你只想监听本地回环,记得用QHostAddress::LocalHost。
3.3 核心方法详解
3.3.1 listen() —— 一切从这里开始
这个方法我用了不下几百次。它的签名很简单:
bool listen(const QHostAddress &address = QHostAddress::Any,
quint16 port = 0);
注意看,port默认是0。这意味着如果你不传端口,系统会自动分配一个可用端口。我在项目中遇到过这种情况:有时候你不想硬编码端口号,让系统分配反而更灵活。这时候可以用serverPort()来获取实际分配的端口。
我曾经踩过的坑:有一次我忘了检查listen()的返回值,结果程序跑起来半天没反应。后来才发现端口被占用了。所以记住:一定要检查返回值!
3.3.2 close() —— 优雅地收场
很多人觉得close()就是关掉服务器,没什么好说的。其实不然。你想想看,如果服务器正在处理连接,你直接close()会怎样?
嗯,QTcpServer的close()只会停止接受新连接,已经建立的连接不会受影响。这一点设计得很合理。我建议在程序退出时,先close()停止监听,再逐个关闭已有的socket。
3.3.3 nextPendingConnection() —— 拿到你的客户
这个方法必须在newConnection信号触发后调用。它返回一个已经建立好的QTcpSocket指针。注意,这个socket默认是打开状态,可以直接读写。
// 典型用法
void MyServer::onNewConnection()
{
QTcpSocket *clientSocket = m_server->nextPendingConnection();
// 现在你可以用clientSocket收发数据了
connect(clientSocket, &QTcpSocket::readyRead,
this, &MyServer::onReadyRead);
}
3.4 QTcpServer的信号与槽
信号与槽是Qt的灵魂,QTcpServer也不例外。它只有两个核心信号,但足够用了:
| 信号 | 触发时机 | 我的使用频率 |
|---|---|---|
| newConnection(QTcpSocket*) | 有新客户端连接时 | ★★★★★ 每次必用 |
| acceptError(QAbstractSocket::SocketError) | 接受连接出错时 | ★★★☆☆ 调试时用 |
3.4.1 newConnection 信号
这是最核心的信号。每当有客户端连接成功,它就会触发。你需要在槽函数里调用nextPendingConnection()来获取这个连接。
我个人习惯在槽函数里做三件事:
- 取出socket
- 连接它的readyRead信号
- 连接它的disconnected信号(用于清理资源)
void MyServer::onNewConnection()
{
QTcpSocket *socket = m_server->nextPendingConnection();
// 连接数据到达信号
connect(socket, &QTcpSocket::readyRead, this, [=]() {
QByteArray data = socket->readAll();
qDebug() << "收到数据:" << data;
});
// 连接断开信号
connect(socket, &QTcpSocket::disconnected, this, [=]() {
qDebug() << "客户端断开连接";
socket->deleteLater();
});
qDebug() << "新客户端连接:" << socket->peerAddress().toString();
}
我的经验:记得用deleteLater()来释放socket,不要直接delete。因为直接delete可能会导致正在处理的事件出问题。
3.4.2 acceptError 信号
这个信号我平时用得不多,但一旦出问题,它就是救命稻草。比如当系统资源不足时,acceptError就会触发。我曾经在一个嵌入式设备上遇到过这个问题——连接数太多导致无法接受新连接,就是靠这个信号定位到的。
3.5 完整示例:一个简单的回显服务器
光说不练假把式。下面是一个完整的回显服务器,客户端发什么,它就回什么:
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
class EchoServer : public QObject
{
Q_OBJECT
public:
EchoServer(quint16 port, QObject *parent = nullptr)
: QObject(parent)
{
m_server = new QTcpServer(this);
// 开始监听
if (!m_server->listen(QHostAddress::Any, port)) {
qCritical() << "监听失败:" << m_server->errorString();
return;
}
qDebug() << "服务器启动,监听端口:" << m_server->serverPort();
// 连接新连接信号
connect(m_server, &QTcpServer::newConnection,
this, &EchoServer::onNewConnection);
}
private slots:
void onNewConnection()
{
while (m_server->hasPendingConnections()) {
QTcpSocket *socket = m_server->nextPendingConnection();
qDebug() << "新客户端:" << socket->peerAddress().toString();
// 收到数据就原样返回
connect(socket, &QTcpSocket::readyRead, this, [socket]() {
QByteArray data = socket->readAll();
socket->write(data); // 回显
});
// 断开连接时清理
connect(socket, &QTcpSocket::disconnected,
socket, &QTcpSocket::deleteLater);
}
}
private:
QTcpServer *m_server;
};
3.6 知识体系总览
下面这张图,是我画的一个QTcpServer知识体系图。你看一眼就能明白整个脉络:
总结一下:QTcpServer的核心流程就是「监听→接受→处理→清理」这四个步骤。你只要掌握了listen()、nextPendingConnection()和newConnection信号,就能写出一个能用的服务器。
好了,QTcpServer的基础就讲到这里。记住,多写多练才是王道。下次遇到网络编程的需求,别怕,先搭个QTcpServer框架再说。