第一部分解释服务端的实现。
(服务端结构)
下面一个用于实现TCP服务器的代码,包括消息服务器(TcpMsgServer)和文件中转服务器(TcpFileServer)。
首先,TcpServer是TcpMsgServer和TcpFileServer的基类,它负责创建QTcpServer对象并监听端口。通过StartListen()函数可以启动监听,传入指定的端口号进行监听。CloseListen()函数用于关闭监听。
TcpMsgServer是消息服务器,继承自TcpServer类。它通过重写SltNewConnection()函数来处理新客户端连接的逻辑。当有新的客户端连接到服务器时,会创建一个ClientSocket对象来管理该客户端连接。在SltConnected()函数中,对连接进行验证后,将客户端对象添加到容器m_clients中,并建立与该客户端的信号与槽连接。在SltDisConnected()函数中,处理客户端下线的情况,从容器中移除对应的客户端对象,并断开相关的信号与槽连接。SltMsgToClient()函数用于消息转发控制,根据收到的消息类型、目标客户端ID和消息内容,找到对应的客户端对象,并调用其SltSendMessage()函数将消息发送给客户端。
TcpFileServer是文件中转服务器,同样继承自TcpServer类。它也重写了SltNewConnection()函数来处理新的客户端连接。在SltConnected()函数中,将连接上的客户端对象添加到容器m_clients中。SltDisConnected()函数处理客户端断连的情况,从容器中移除对应的客户端对象,并断开相关的信号与槽连接。SltClientDownloadFile()函数处理客户端请求下载文件的情况,根据收到的消息中的来源ID和文件名,在容器m_clients中找到对应的客户端对象,调用其StartTransferFile()函数开始文件传输过程。
在代码中,TcpMsgServer和TcpFileServer都采用了容器来管理连接的客户端对象,以便进行消息转发和文件传输等操作。
cpp
#include "tcpserver.h"
#include "clientsocket.h"
#include "myapp.h"
#include "databasemagr.h"
#include <QHostAddress>
/
/// 服务器类,是TcpMsgServer和TcpFileServer的基类
TcpServer::TcpServer(QObject *parent) :
QObject(parent)
{
m_tcpServer = new QTcpServer(this);
connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(SltNewConnection()));
}
TcpServer::~TcpServer()
{
if (m_tcpServer->isListening()) m_tcpServer->close();
}
///启动监听
bool TcpServer::StartListen(int port)
{
if (m_tcpServer->isListening()) m_tcpServer->close();
bool bOk = m_tcpServer->listen(QHostAddress::Any, port);
return bOk;
}
///关闭监听
void TcpServer::CloseListen()
{
m_tcpServer->close();
}
/
/// 消息服务器
TcpMsgServer::TcpMsgServer(QObject *parent) :
TcpServer(parent)
{
}
TcpMsgServer::~TcpMsgServer()
{
qDebug() << "tcp server close";
foreach (ClientSocket *client, m_clients) {
m_clients.removeOne(client);
client->Close();
}
}
/// 新客户端连接处理
void TcpMsgServer::SltNewConnection()
{
ClientSocket *client = new ClientSocket(this, m_tcpServer->nextPendingConnection());
connect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
connect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
}
///通过验证后,才可以加入容器进行管理
void TcpMsgServer::SltConnected()
{
ClientSocket *client = (ClientSocket *)this->sender();
if (NULL == client) return;
connect(client, SIGNAL(signalMsgToClient(quint8,int,QJsonValue)),
this, SLOT(SltMsgToClient(quint8,int,QJsonValue)));
connect(client, SIGNAL(signalDownloadFile(QJsonValue)), this, SIGNAL(signalDownloadFile(QJsonValue)));
m_clients.push_back(client);
qDebug() << "TcpMsgServer::SltConnected. last m_nId=" + QString::number(m_clients[m_clients.size()-1]->GetUserId());
}
///有客户端下线
void TcpMsgServer::SltDisConnected()
{
//找到断连的socket
ClientSocket *client = (ClientSocket *)this->sender();
if (NULL == client) return;
//移除对应socket
for (int i = 0; i < m_clients.size(); i++) {
if (client == m_clients.at(i))
{
m_clients.remove(i);
return;
}
}
disconnect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
disconnect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
disconnect(client, SIGNAL(signalMsgToClient(quint8,int,QJsonValue)),
this, SLOT(SltMsgToClient(quint8,int,QJsonValue)));
disconnect(client, SIGNAL(signalDownloadFile(QJsonValue)), this, SIGNAL(signalDownloadFile(QJsonValue)));
}
///消息转发控制
void TcpMsgServer::SltMsgToClient(const quint8 &type, const int &id, const QJsonValue &json)
{
// 查找要发送过去的id
for (int i = 0; i < m_clients.size(); i++) {
if (id == m_clients.at(i)->GetUserId())
{
qDebug()<<"TcpMsgServer::SltMsgToClient. send to:"+QString::number(id);
m_clients.at(i)->SltSendMessage(type, json);
return;
}
}
}
///传送文件到指定ID的客户端
void TcpMsgServer::SltTransFileToClient(const int &userId, const QJsonValue &json)
{
// 查找要发送过去的id
for (int i = 0; i < m_clients.size(); i++) {
if (userId == m_clients.at(i)->GetUserId())
{
m_clients.at(i)->SltSendMessage(SendFile, json);
return;
}
}
}
//
/// 文件中转服务器,客户端先把待转发的文件保存在服务器
/// 服务器接受完成后,通知其他客户端来下载
TcpFileServer::TcpFileServer(QObject *parent) :
TcpServer(parent)
{
}
TcpFileServer::~TcpFileServer()
{
qDebug() << "tcp server close";
foreach (ClientFileSocket *client, m_clients) {
m_clients.removeOne(client);
client->Close();
}
}
///客户端与文件服务器新建连接
void TcpFileServer::SltNewConnection()
{
//新建槽函数与socket
ClientFileSocket *client = new ClientFileSocket(this, m_tcpServer->nextPendingConnection());
connect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
connect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
}
/// socket管理
void TcpFileServer::SltConnected()
{
//连接时将Client放入vector m_clients
ClientFileSocket *client = (ClientFileSocket *)this->sender();
if (NULL == client) return;
m_clients.push_back(client);
}
/// 客户端断连
void TcpFileServer::SltDisConnected()
{
ClientFileSocket *client = (ClientFileSocket *)this->sender();
if (NULL == client) return;
for (int i = 0; i < m_clients.size(); i++) {
if (client == m_clients.at(i))
{
m_clients.remove(i);
return;
}
}
disconnect(client, SIGNAL(signalConnected()), this, SLOT(SltConnected()));
disconnect(client, SIGNAL(signalDisConnected()), this, SLOT(SltDisConnected()));
}
/// 客户端请求下载文件
void TcpFileServer::SltClientDownloadFile(const QJsonValue &json)
{
// 根据ID寻找连接的socket
if (json.isObject()) {
QJsonObject jsonObj = json.toObject();
qint32 nId = jsonObj.value("from").toInt();//
qint32 nWid = jsonObj.value("id").toInt();;//
QString fileName = jsonObj.value("msg").toString();
qDebug() << "get file" << jsonObj << m_clients.size();
for (int i = 0; i < m_clients.size(); i++) {
if (m_clients.at(i)->CheckUserId(nId, nWid))
{
m_clients.at(i)->StartTransferFile(fileName);
return;
}
}
}
}
当服务端端通过accpt收到一个请求后,创建一个ClientSocket,处理客户端消息。
下面是一个Qt中的客户端socket管理类,用于与服务端进行通信。其中包含两个类,一个是ClientSocket,用于处理普通消息,另一个是ClientFileSocket,用于处理文件传输。
在ClientSocket中,包含了一些信号和槽函数,用于处理连接、数据接收、关闭等操作。同时还有一些私有函数,用于解析不同类型的消息,并且把解析后的数据发送到前台界面进行展示。
在ClientFileSocket中,主要有两个功能:文件接收和文件发送。对于文件接收,分别记录了已经接收到的数据大小、文件名大小、要接收的文件等信息;对于文件发送,记录了文件大小、已经发送的数据大小、剩余数据大小、要发送的文件等信息。同时还有一些私有函数,用于初始化socket、处理接收到的数据、更新发送进度等操作。
总的来说,这个类是一个很重要的网络通信模块,可以实现与服务端的双向交互,包括文字、图片、文件等。
cpp
#ifndef CLIENTSOCKET_H
#define CLIENTSOCKET_H
#include <QObject>
#include <QTcpSocket>
#include <QFile>
#include <QApplication>
/// 服务端socket管理类
class ClientSocket : public QObject
{
Q_OBJECT
public:
explicit ClientSocket(QObject *parent = 0, QTcpSocket *tcpSocket = NULL);
~ClientSocket();
int GetUserId() const;
void Close();
signals:
void signalConnected();
void signalDisConnected();
void signalDownloadFile(const QJsonValue &json);
void signalMsgToClient(const quint8 &type, const int &id, const QJsonValue &dataVal);
public slots:
private:
QTcpSocket *m_tcpSocket;
int m_nId;
public slots:
// 消息回发
void SltSendMessage(const quint8 &type, const QJsonValue &json);
private slots:
void SltConnected();
void SltDisconnected();
void SltReadyRead();
private:
// 消息解析和抓转发处理
void ParseLogin(const QJsonValue &dataVal);
void ParseUserOnline(const QJsonValue &dataVal);
void ParseLogout(const QJsonValue &dataVal);
void ParseUpdateUserHead(const QJsonValue &dataVal);
void ParseReister(const QJsonValue &dataVal);
void ParseAddFriend(const QJsonValue &dataVal);
void ParseAddGroup(const QJsonValue &dataVal);
void ParseCreateGroup(const QJsonValue &dataVal);
void ParseGetMyFriend(const QJsonValue &dataVal);
void ParseGetMyGroups(const QJsonValue &dataVal);
void ParseRefreshFriend(const QJsonValue &dataVal);
void ParseRefreshGroups(const QJsonValue &dataVal);
void ParseFriendMessages(const QByteArray &reply);
void ParseGroupMessages(const QByteArray &reply);
};
cpp
ClientSocket::ClientSocket(QObject *parent, QTcpSocket *tcpSocket) :
QObject(parent)
{
qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
m_nId = -1;
if (tcpSocket == NULL) m_tcpSocket = new QTcpSocket(this);
m_tcpSocket = tcpSocket;
connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(SltReadyRead()));//处理客户端信息
connect(m_tcpSocket, SIGNAL(connected()), this, SLOT(SltConnected()));//处理登录成功信号
connect(m_tcpSocket, SIGNAL(disconnected()), this, SLOT(SltDisconnected()));//处理登出信号
}
处理客户端消息,根据消息类型进行不同的处理:
cpp
void ClientSocket::SltReadyRead()
{
// 读取socket数据
QByteArray reply = m_tcpSocket->readAll();
QJsonParseError jsonError;
// 转化为 JSON 文档
QJsonDocument doucment = QJsonDocument::fromJson(reply, &jsonError);
// 解析未发生错误
if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {
// JSON 文档为对象
if (doucment.isObject()) {
// 转化为对象
QJsonObject jsonObj = doucment.object();
int nType = jsonObj.value("type").toInt();
QJsonValue dataVal = jsonObj.value("data");
switch (nType) {
case Register:
{
ParseReister(dataVal);
}
break;
case Login:
{
ParseLogin(dataVal);
}
break;
case UserOnLine:
{
ParseUserOnline(dataVal);
}
break;
case Logout:
{
ParseLogout(dataVal);
Q_EMIT signalDisConnected();
m_tcpSocket->abort();
}
break;
case UpdateHeadPic:
{
ParseUpdateUserHead(dataVal);
}
break;
case AddFriend:
{
ParseAddFriend(dataVal);
}
break;
case AddGroup:
{
ParseAddGroup(dataVal);
}
break;
case CreateGroup:
{
ParseCreateGroup(dataVal);
}
break;
case GetMyFriends:
{
ParseGetMyFriend(dataVal);
}
break;
case GetMyGroups:
{
ParseGetMyGroups(dataVal);
}
break;
case RefreshFriends:
{
ParseRefreshFriend(dataVal);
}
break;
case RefreshGroups:
{
ParseRefreshGroups(dataVal);
}
break;
case SendMsg:
case SendFile:
case SendPicture:
{
ParseFriendMessages(reply);
}
break;
case SendGroupMsg:
{
ParseGroupMessages(reply);
}
break;
case SendFace:
{
ParseGroupMessages(reply);
}
break;
case SendFileOk:
{
}
break;
case GetFile:
{
Q_EMIT signalDownloadFile(dataVal);
}
break;
default:
break;
}
}
}
}
登录的处理:
cpp
void ClientSocket::ParseLogin(const QJsonValue &dataVal)
{
// data 的 value 也是JSON对象
if (dataVal.isObject()) {
QJsonObject dataObj = dataVal.toObject();
QString strName = dataObj.value("name").toString();
QString strPwd = dataObj.value("passwd").toString();
QJsonObject jsonObj = DataBaseMagr::Instance()->CheckUserLogin(strName, strPwd);
m_nId = jsonObj.value("id").toInt();
qDebug() << "login" << jsonObj;
//验证成功才向server发送信号说明可以将socket加入容器管理
if (m_nId > 0) Q_EMIT signalConnected();
// 发送查询结果至客户端
SltSendMessage(Login, jsonObj);;
}
}
余略.....