Qt编程:sokit工具中ServerSkt实现

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() 清理超时连接

关键设计亮点

  1. 抽象基类:通过纯虚函数 (open/close/send) 实现多态,统一 TCP/UDP 接口。

  2. 连接标识:TCP 使用 IP:Port,UDP 使用 IP,确保唯一性。

  3. 资源管理

    • TCP 连接通过 QTcpSocket 自动释放(deleteLater)。

    • UDP 会话通过定时器清理,避免内存泄漏。

  4. 调试支持dumpbin 信号提供完整的数据流监控能力。

 潜在问题与改进

  • TCP 粘包:当前实现未处理粘包,需在应用层添加协议头(如长度前缀)。

  • 线程安全:若在多线程中调用 send(),需加锁保护 m_conns

  • 性能优化:UDP 的 check() 可改用更高效的数据结构(如时间轮)。


总结

  • 适用场景:网络调试工具、IoT 设备通信、轻量级服务端。

  • 扩展性:可轻松集成 SSL 加密或自定义协议(如 WebSocket)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值