serverskt 是一个基于 Qt 网络模块 的服务器核心组件,提供 TCP/UDP 双协议支持,主要功能包括:
-
多客户端连接管理(TCP 按 Socket 管理,UDP 按地址管理)。
-
数据收发(支持文本和二进制格式,带调试输出)。
-
连接状态监控(自动清理超时连接)。
-
字节统计(实时记录收发数据量)。
#ifndef __SERVERSKT_H__
#define __SERVERSKT_H__
#include <QHash>
#include <QTcpServer>
#include <QUdpSocket>
#include <QDateTime>
#include <QTimer>
class ServerSkt : public QObject
{
typedef QHash<QString, void*> OBJMAP;
Q_OBJECT
public:
ServerSkt(QObject *parent=0);
virtual ~ServerSkt();
virtual QString name() const { return "General"; };
bool start(const QHostAddress& ip, quint16 port);
void kill(const QString& key);
void stop();
void send(const QString& key, const QString& data);
const QHostAddress& addr() const { return m_ip; };
quint16 port() const { return m_port; };
signals:
void connOpen(const QString& key);
void connClose(const QString& key);
void message(const QString& msg);
void dumpbin(const QString& title, const char* data, quint32 len);
void countRecv(qint32 bytes);
void countSend(qint32 bytes);
protected:
void dump(const char* buf, qint32 len, bool isSend, const QString& key);
void show(const QString& msg);
void setError(const QString& err);
void recordRecv(qint32 bytes);
void recordSend(qint32 bytes);
void getKeys(QStringList& res);
void setCookie(const QString& k, void* v);
void unsetCookie(const QString& k);
void* getCookie(const QString& k);
virtual bool open() =0;
virtual bool close(void* cookie) =0;
virtual void send(void* cookie, const QByteArray& bin) =0;
virtual void close() =0;
private:
bool m_started;
QHostAddress m_ip;
quint16 m_port;
OBJMAP m_conns;
QString m_error;
};
class ServerSktTcp : public ServerSkt
{
typedef struct _Conn
{
QTcpSocket* client;
QString key;
} Conn;
Q_OBJECT
public:
ServerSktTcp(QObject *parent=0);
virtual ~ServerSktTcp();
virtual QString name() const { return "TCP"; };
protected:
virtual bool open();
virtual bool close(void* cookie);
virtual void send(void* cookie, const QByteArray& bin);
virtual void close();
private slots:
void newConnection();
void newData();
void error();
void close(QObject* obj);
private:
QTcpServer m_server;
};
class ServerSktUdp : public ServerSkt
{
typedef struct _Conn
{
QHostAddress addr;
quint16 port;
QDateTime stamp;
QString key;
} Conn;
Q_OBJECT
public:
ServerSktUdp(QObject *parent=0);
virtual ~ServerSktUdp();
virtual QString name() const { return "UDP"; };
protected:
virtual bool open();
virtual bool close(void* cookie);
virtual void send(void* cookie, const QByteArray& bin);
virtual void close();
private slots:
void newData();
void error();
void check();
private:
QUdpSocket m_server;
QTimer m_timer;
};
#endif // __SERVERSKT_H__
#include <QTcpSocket>
#include <QStringList>
#include "toolkit.h"
#include "serverskt.h"
#define PROP_CONN "CONN"
#define MAXBUFFER 1024*1024
ServerSkt::ServerSkt(QObject *parent)
: QObject(parent),m_port(0)
{
m_started = false;
}
ServerSkt::~ServerSkt()
{
}
bool ServerSkt::start(const QHostAddress& ip, quint16 port)
{
m_ip = ip;
m_port = port;
m_conns.clear();
m_error.clear();
m_started = open();
QString msg("start %1 server %2!");
if (!m_started)
{
msg = msg.arg(name(),"failed");
if (!m_error.isEmpty())
{ msg += " error:[";
msg += m_error;
msg += "].";
}
}
else
{
msg = msg.arg(name(),"successfully");
}
show(msg);
return m_started;
}
void ServerSkt::stop()
{
if (!m_started)
return;
OBJMAP::const_iterator i;
for (i = m_conns.constBegin(); i != m_conns.constEnd(); ++i)
{
QString k = i.key();
void* v = i.value();
if (close(v))
emit connClose(k);
}
m_conns.clear();
close();
show(QString("stop %1 server!").arg(name()));
m_started = false;
}
void ServerSkt::setError(const QString& err)
{
m_error = err;
}
void ServerSkt::recordRecv(qint32 bytes)
{
emit countRecv(bytes);
}
void ServerSkt::recordSend(qint32 bytes)
{
emit countSend(bytes);
}
void ServerSkt::getKeys(QStringList& res)
{
res = m_conns.keys();
}
void ServerSkt::setCookie(const QString& k, void* v)
{
void* o = m_conns.value(k);
if (o)
{
if (close(o))
emit connClose(k);
}
m_conns.insert(k, v);
emit connOpen(k);
}
void ServerSkt::unsetCookie(const QString& k)
{
m_conns.remove(k);
emit connClose(k);
}
void* ServerSkt::getCookie(const QString& k)
{
return m_conns.value(k);
}
void ServerSkt::kill(const QString& key)
{
void* v = m_conns.value(key);
if (v)
{
if (close(v))
unsetCookie(key);
}
else
{
unsetCookie(key);
}
}
void ServerSkt::send(const QString& key, const QString& data)
{
void* v = m_conns.value(key);
if (v)
{
QString err;
QByteArray bin;
if (!TK::ascii2bin(data, bin, err))
show("bad data format to send: "+err);
else
send(v, bin);
}
}
void ServerSkt::dump(const char* buf, qint32 len, bool isSend, const QString& key)
{
emit dumpbin(QString("DAT %1 %2").arg(isSend?"<---":"--->",key), buf, (quint32)len);
}
void ServerSkt::show(const QString& msg)
{
emit message(msg);
}
ServerSktTcp::ServerSktTcp(QObject *parent)
:ServerSkt(parent)
{
}
ServerSktTcp::~ServerSktTcp()
{
m_server.disconnect(this);
stop();
}
bool ServerSktTcp::open()
{
if (m_server.listen(addr(), port()))
{
connect(&m_server, SIGNAL(newConnection()), this, SLOT(newConnection()));
return true;
}
else
{
setError(QString("%1, %2").arg(m_server.serverError()).arg(m_server.errorString()));
}
return false;
}
bool ServerSktTcp::close(void* cookie)
{
Conn* conn = (Conn*)cookie;
if (conn->client)
conn->client->disconnect(this);
delete conn->client;
delete conn;
return true;
}
void ServerSktTcp::close(QObject* obj)
{
Conn* conn = (Conn*)obj->property(PROP_CONN).value<void*>();
if (!conn) return;
unsetCookie(conn->key);
delete conn;
}
void ServerSktTcp::error()
{
QTcpSocket* s = qobject_cast<QTcpSocket*>(sender());
show(QString("TCP socket error %1, %2").arg(s->error()).arg(s->errorString()));
s->deleteLater();
}
void ServerSktTcp::close()
{
m_server.close();
m_server.disconnect(this);
}
void ServerSktTcp::newConnection()
{
QTcpServer* server = qobject_cast<QTcpServer*>(sender());
if (!server) return;
QTcpSocket* client = server->nextPendingConnection();
while (client)
{
Conn* conn = new Conn;
if (!conn)
{
client->deleteLater();
}
else
{
client->setProperty(PROP_CONN, qVariantFromValue((void*)conn));
conn->client = client;
conn->key = TK::ipstr(client->peerAddress(),client->peerPort(), true);
connect(client, SIGNAL(readyRead()), this, SLOT(newData()));
connect(client, SIGNAL(destroyed(QObject*)), this, SLOT(close(QObject*)));
connect(client, SIGNAL(disconnected()), client, SLOT(deleteLater()));
connect(client, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));
setCookie(conn->key, conn);
}
client = server->nextPendingConnection();
}
}
void ServerSktTcp::newData()
{
QTcpSocket* client = qobject_cast<QTcpSocket*>(sender());
if (!client) return;
Conn* conn = (Conn*)client->property(PROP_CONN).value<void*>();
if (!conn) return;
qint64 bufLen = client->bytesAvailable();
char* buf = TK::createBuffer(bufLen, MAXBUFFER);
if (!buf) return;
qint64 readLen = 0;
qint64 ioLen = client->read(buf, bufLen);
while (ioLen > 0)
{
readLen += ioLen;
ioLen = client->read(buf+readLen, bufLen-readLen);
}
if (ioLen >= 0)
{
recordRecv(readLen);
dump(buf, readLen, false, conn->key);
}
TK::releaseBuffer(buf);
}
void ServerSktTcp::send(void* cookie, const QByteArray& bin)
{
Conn* conn = (Conn*)cookie;
const char * src = bin.constData();
qint64 srcLen = bin.length();
qint64 writeLen = 0;
qint64 ioLen = conn->client->write(src, srcLen);
while (ioLen > 0)
{
writeLen += ioLen;
ioLen = conn->client->write(src+writeLen, srcLen-writeLen);
}
if (writeLen != srcLen)
{
show(QString("failed to send data to %1:%2 [%3]")
.arg(addr().toString()).arg(port()).arg(writeLen));
return;
}
recordSend(writeLen);
dump(src, srcLen, true, conn->key);
}
ServerSktUdp::ServerSktUdp(QObject *parent)
:ServerSkt(parent)
{
}
ServerSktUdp::~ServerSktUdp()
{
m_server.disconnect(this);
stop();
}
void ServerSktUdp::error()
{
QUdpSocket* s = qobject_cast<QUdpSocket*>(sender());
show(QString("UDP socket error %1, %2").arg(s->error()).arg(s->errorString()));
}
bool ServerSktUdp::open()
{
if (m_server.bind(addr(), port(), QUdpSocket::ShareAddress))
{
connect(&m_server, SIGNAL(readyRead()), this, SLOT(newData()));
connect(&m_server, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));
connect(&m_timer, SIGNAL(timeout()), this, SLOT(check()));
m_timer.start(2000);
return true;
}
else
{
setError(QString("%1, %2").arg(m_server.error()).arg(m_server.errorString()));
}
return false;
}
bool ServerSktUdp::close(void* cookie)
{
delete (Conn*)cookie;
return true;
}
void ServerSktUdp::close()
{
m_timer.disconnect(this);
m_timer.stop();
m_server.close();
m_server.disconnect(this);
}
void ServerSktUdp::newData()
{
QUdpSocket* s = qobject_cast<QUdpSocket*>(sender());
if (!s) return;
qint64 bufLen = s->pendingDatagramSize();
char* buf = TK::createBuffer(bufLen, MAXBUFFER);
if (!buf) return;
QHostAddress addr; quint16 port(0);
qint64 readLen = 0;
qint64 ioLen = s->readDatagram(buf, bufLen, &addr, &port);
//while (ioLen > 0)
//{
readLen += ioLen;
// ioLen = s->readDatagram(buf+readLen, bufLen-readLen, &addr, &port);
//}
if (ioLen >= 0)
{
Conn* conn = (Conn*)getCookie(TK::ipstr(addr, port, false));
if (!conn)
{
conn = new Conn;
if (conn)
{
conn->key = TK::ipstr(addr, port, false);
conn->addr = addr;
conn->port = port;
setCookie(conn->key, conn);
}
}
if (conn)
{
recordRecv(readLen);
conn->stamp = QDateTime::currentDateTime();
dump(buf, readLen, false, conn->key);
}
}
TK::releaseBuffer(buf);
}
void ServerSktUdp::send(void* cookie, const QByteArray& bin)
{
Conn* conn = (Conn*)cookie;
const char* src = bin.constData();
qint64 srcLen = bin.length();
qint64 writeLen = 0;
qint64 ioLen = m_server.writeDatagram(src, srcLen, conn->addr, conn->port);
while (ioLen > 0)
{
writeLen += ioLen;
ioLen = (writeLen >= srcLen) ? 0 :
m_server.writeDatagram(src+writeLen, srcLen-writeLen, conn->addr, conn->port);
}
if (writeLen != srcLen)
{
show(QString("failed to send data to %1:%2 [%3]")
.arg(conn->addr.toString()).arg(conn->port).arg(writeLen));
return;
}
recordSend(writeLen);
dump(src, srcLen, true, conn->key);
}
void ServerSktUdp::check()
{
QStringList list;
getKeys(list);
while (!list.isEmpty())
{
QString k = list.takeFirst();
void* c = getCookie(k);
if (c && (((Conn*)c)->stamp.addSecs(120) < QDateTime::currentDateTime()))
{
close(c);
unsetCookie(k);
}
}
}
核心类与分工
| 类名 | 职责 |
|---|---|
ServerSkt | 抽象基类,定义通用接口(如 start()/stop())和连接管理逻辑。 |
ServerSktTcp | 实现 TCP 服务器逻辑,基于 QTcpServer 和 QTcpSocket。 |
ServerSktUdp | 实现 UDP 服务器逻辑,基于 QUdpSocket,支持会话超时清理。 |
核心功能
| 功能模块 | 实现细节 |
|---|---|
| TCP/UDP 服务器 | 支持多客户端连接,管理连接生命周期(建立、通信、关闭)。 |
| 数据收发 | 统一接口处理二进制/文本数据,支持调试输出(十六进制 + ASCII 格式)。 |
| 连接管理 | 使用 QHash 存储活跃连接,TCP 按 Socket 管理,UDP 按客户端地址管理。 |
| 统计与监控 | 实时统计收发字节数,定时清理无效连接(UDP)。 |
| 错误处理 | 通过信号传递错误信息(如 error(QAbstractSocket::SocketError))。 |
关键方法实现
(1) 服务器启动与停止
// 启动服务器(基类 ServerSkt)
bool ServerSkt::start(const QHostAddress& ip, quint16 port) {
m_ip = ip;
m_port = port;
m_started = open(); // 调用子类实现的 open()
emit message(m_started ? "启动成功" : "启动失败: " + m_error);
return m_started;
}
// 停止服务器(基类 ServerSkt)
void ServerSkt::stop() {
if (!m_started) return;
close(); // 调用子类实现的 close()
m_conns.clear(); // 清理所有连接
emit message("服务器已停止");
}
-
TCP 实现 (
ServerSktTcp):bool ServerSktTcp::open() { return m_server.listen(m_ip, m_port); // 监听端口 } void ServerSktTcp::close() { m_server.close(); // 停止监听 } -
UDP 实现 (
ServerSktUdp):bool ServerSktUdp::open() { return m_server.bind(m_ip, m_port); // 绑定端口 } void ServerSktUdp::close() { m_server.close(); // 解绑端口 }
(2) 连接管理
-
TCP 新连接处理:
void ServerSktTcp::newConnection() { QTcpSocket* client = m_server.nextPendingConnection(); QString key = TK::ipstr(client->peerAddress(), client->peerPort(), true); Conn* conn = new Conn{client, key}; setCookie(key, conn); // 存储连接 emit connOpen(key); // 通知新连接 } -
UDP 连接标识:
void ServerSktUdp::newData() { QHostAddress addr; quint16 port; m_server.readDatagram(data, &addr, &port); QString key = TK::ipstr(addr, port, false); if (!m_conns.contains(key)) { Conn* conn = new Conn{addr, port, QDateTime::currentDateTime(), key}; setCookie(key, conn); // 按地址管理会话 } }
(3) 数据收发
-
发送数据(TCP/UDP 统一接口):
void ServerSktTcp::send(void* cookie, const QByteArray& bin) { Conn* conn = (Conn*)cookie; conn->client->write(bin); // TCP 发送 emit countSend(bin.size()); } void ServerSktUdp::send(void* cookie, const QByteArray& bin) { Conn* conn = (Conn*)cookie; m_server.writeDatagram(bin, conn->addr, conn->port); // UDP 发送 emit countSend(bin.size()); } -
接收数据:
void ServerSktTcp::newData() { QTcpSocket* client = (QTcpSocket*)sender(); QByteArray data = client->readAll(); emit dumpbin("RECV", data.constData(), data.size()); // 调试输出 }
(4) 超时清理(UDP)
void ServerSktUdp::check() {
QStringList keys = m_conns.keys();
for (const QString& key : keys) {
Conn* conn = (Conn*)m_conns.value(key);
if (conn->stamp.addSecs(120) < QDateTime::currentDateTime()) {
close(conn); // 关闭超时连接
unsetCookie(key); // 移除记录
}
}
}
(5) TCP 服务器流程
// 启动监听
bool ServerSktTcp::open() {
return m_server.listen(m_ip, m_port);
}
// 处理新连接
void ServerSktTcp::newConnection() {
QTcpSocket* client = m_server.nextPendingConnection();
QString key = TK::ipstr(client->peerAddress(), client->peerPort(), true);
Conn* conn = new Conn{client, key};
setCookie(key, conn); // 存储连接
}
// 数据接收
void ServerSktTcp::newData() {
QTcpSocket* client = (QTcpSocket*)sender();
QByteArray data = client->readAll();
emit dumpbin("RECV", data.constData(), data.size());
}
(6) UDP 服务器流程
// 绑定端口
bool ServerSktUdp::open() {
return m_server.bind(m_ip, m_port);
}
// 接收数据并管理会话
void ServerSktUdp::newData() {
QHostAddress addr; quint16 port;
QByteArray data;
data.resize(m_server.pendingDatagramSize());
m_server.readDatagram(data.data(), data.size(), &addr, &port);
QString key = TK::ipstr(addr, port, false);
if (!m_conns.contains(key)) {
Conn* conn = new Conn{addr, port, QDateTime::currentDateTime(), key};
setCookie(key, conn); // 新会话
}
}
// 定时清理超时会话
void ServerSktUdp::check() {
for (auto it = m_conns.begin(); it != m_conns.end(); ) {
Conn* conn = (Conn*)it.value();
if (conn->stamp.addSecs(120) < QDateTime::currentDateTime()) {
delete conn;
it = m_conns.erase(it); // 移除超时连接
} else {
++it;
}
}
}
信号与槽机制
| 信号 | 触发场景 | 典型槽函数用途 |
|---|---|---|
connOpen(key) | 新连接建立(TCP)或首次收到数据(UDP) | 更新 UI 连接列表 |
connClose(key) | 连接关闭 | 清理连接资源 |
message(msg) | 状态变更/错误发生 | 显示日志信息 |
dumpbin(title, data, len) | 数据收发时 | 调试窗口输出十六进制数据 |
countRecv(bytes) | 收到数据 | 更新接收字节统计 |
实际应用示例
启动 TCP 服务器并发送数据
ServerSktTcp tcpServer; tcpServer.start(QHostAddress::Any, 8080); // 监听 8080 端口 // 向指定客户端发送数据 QString clientKey = "192.168.1.2:54321"; tcpServer.send(clientKey, "Hello, Client!");
UDP 服务器处理超时
ServerSktUdp udpServer; udpServer.start(QHostAddress::Any, 9090); // 每 2 秒自动触发 check() 清理超时连接
关键设计亮点
-
抽象基类:通过纯虚函数 (
open/close/send) 实现多态,统一 TCP/UDP 接口。 -
连接标识:TCP 使用
IP:Port,UDP 使用IP,确保唯一性。 -
资源管理:
-
TCP 连接通过
QTcpSocket自动释放(deleteLater)。 -
UDP 会话通过定时器清理,避免内存泄漏。
-
-
调试支持:
dumpbin信号提供完整的数据流监控能力。
潜在问题与改进
-
TCP 粘包:当前实现未处理粘包,需在应用层添加协议头(如长度前缀)。
-
线程安全:若在多线程中调用
send(),需加锁保护m_conns。 -
性能优化:UDP 的
check()可改用更高效的数据结构(如时间轮)。
总结
-
适用场景:网络调试工具、IoT 设备通信、轻量级服务端。
-
扩展性:可轻松集成 SSL 加密或自定义协议(如 WebSocket)。
1245

被折叠的 条评论
为什么被折叠?



