本文未经授权,禁止转载
目前存在的问题
到这里,我们已经完成了以下功能:登陆注册、界面跳转、查找用户以及显示在线用户。
然而,服务器对客户端请求的处理,以及客户端对于服务器响应的处理。我们都是在各自接收消息的函数中,利用switch,以不同的消息类型为case,直接在该函数内进行处理的。
这样就会导致每添加一个功能,接收消息的函数就会多一些代码,这对代码的可读性来说是非常致命的。我们来看一下,到目前为止,客户端与服务器接收消息的函数有多长。
客户端接收消息的函数
// 接收消息
void Client::recvMsg()
{
// 打印socket中的数据长度
qDebug()<<"socket中接收到的数据长度:"<<m_tcpSocket.bytesAvailable();
// 读出消息结构体的总长度,这部分内容会从socket中读出去
uint uiPDULen = 0; // 传出参数
// 读取的大小为 消息结构体总长度的数据类型大小
m_tcpSocket.read((char*)&uiPDULen,sizeof(uint));
// 计算实际消息长度--柔性数组长度(消息结构体总长度-结构体大小)
uint uiMsgLen = uiPDULen-sizeof(PDU);
// 初始化一个PUD结构体,用于存储
PDU* pdu = initPDU(uiMsgLen);
// 读出socket中剩余的数据,消息结构体的总长度已经被读出去了,剩下的不是一个完整的PDU结构体。
// 因此需要对pdu这个指针进行偏移,偏移的长度就是已经读出的数据长度(消息结构体总长度的数据类型大小)
// 而剩余需要读出的内容为,消息结构体总长度 减去 消息结构体总长度的数据类型大小
m_tcpSocket.read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
// // 测试--打印输出消息结构体的内容
// qDebug()<<"recvMsg 结构体总长度:"<<pdu->uiPDULen;
// qDebug()<<"recvMsg 消息类型:"<<pdu->uiMsgType;
// qDebug()<<"recvMsg 消息长度:"<<pdu->uiMsgLen;
qDebug()<<"recvMsg 参数1:"<<pdu->caData;
qDebug()<<"recvMsg 参数2:"<<pdu->caData+32;
qDebug()<<"recvMsg 接收到的消息:"<<pdu->caMsg;
// 根据消息类型对消息进行处理
switch (pdu->uiMsgType)
{
// 注册响应
case ENUM_MSG_TYPE_REGIST_RESPOND:
{
// 将消息取出
bool ret;
memcpy(&ret,pdu->caData,sizeof(bool));
// 根据返回的响应进行处理
if(ret)
{
QMessageBox::information(this,"注册","注册成功");
}
else
{
QMessageBox::information(this,"注册","注册失败:用户名或密码非法");
}
break;
}
// 登录响应
case ENUM_MSG_TYPE_LOGIN_RESPOND:
{
// 将消息取出
bool ret;
char caName[32] = {'\0'};
memcpy(&ret,pdu->caData,sizeof(bool));
memcpy(caName,pdu->caData+32,32);
// 根据返回的响应进行处理
if(ret)
{
m_LoginName = caName;
qDebug()<<"recvMsg LOGIN_REQUEST m_userName"<<m_LoginName;
QMessageBox::information(this,"登录","登录成功");
// 登录成功后,跳转到首页
Index::getInstance().show();
// 隐藏登录界面
hide();
}
else
{
QMessageBox::information(this,"登录","登录失败:用户名或密码非法");
}
break;
}
// 查找用户响应
case ENUM_MSG_TYPE_FIND_USER_RESPOND:
{
// 将消息取出
int ret;
memcpy(&ret,pdu->caData,sizeof(int));
// 根据返回的响应进行处理
if(ret == -1)
{
QMessageBox::information(&Index::getInstance(),"查找用户","该用户不存在");
return;
}
else if(ret == 0)
{
QMessageBox::information(&Index::getInstance(),"查找用户","该用户不在线");
return;
}
else if(ret == 1)
{
QMessageBox::information(&Index::getInstance(),"查找用户","该用户在线");
return;
}
else
{
QMessageBox::critical(&Index::getInstance(),"查找用户","查找错误");
return;
}
break;
}
// 在线用户响应
case ENUM_MSG_TYPE_ONLINE_USER_RESPOND:
{
// 获取在线用户的个数
uint listSize = pdu->uiMsgLen/32;
qDebug()<<"listSize "<<listSize;
// 创建变量存储在线用户的用户名
char userName[32];
QStringList nameList;
// 将caMsg中的用户名挨个取出,并放到nameList中
for(uint i = 0; i <listSize; i++)
{
// 挨个取出用户名
memcpy(userName,pdu->caMsg+i*32,32);
// 测试
// qDebug()<<"ONLINE_USER_RESPOND "<<QString(userName);
// 跳过自己
if(QString(userName) == m_LoginName)
{
continue;
}
// 将取到的用户名存放到 nameList中
nameList.append(QString(userName));
}
// 调用展示在线用户的函数
Index::getInstance().getFriend()->m_onlineUser->showOnlineUser(nameList);
break;
}
default:
break;
}
// 释放pdu
free(pdu);
pdu=NULL;
}
服务器接收消息的函数
// 接收消息的槽函数
void MyTcpSocket::recvMsg()
{
// 打印socket中的数据长度
qDebug()<<"socket中接收到的数据长度:"<<this->bytesAvailable();
// 读出消息结构体的总长度,这部分内容会从socket中读出去
uint uiPDULen = 0; // 传出参数
// 读取的大小为 消息结构体总长度的数据类型大小
this->read((char*)&uiPDULen,sizeof(uint));
// 计算实际消息长度--柔性数组长度(消息结构体总长度-结构体大小)
uint uiMsgLen = uiPDULen-sizeof(PDU);
// 初始化一个PUD结构体,用于存储
PDU* pdu = initPDU(uiMsgLen);
// 读出socket中剩余的数据,消息结构体的总长度已经被读出去了,剩下的不是一个完整的PDU结构体。
// 因此需要对pdu这个指针进行偏移,偏移的长度就是已经读出的数据长度(消息结构体总长度的数据类型大小)
// 而剩余需要读出的内容为,消息结构体总长度 减去 消息结构体总长度的数据类型大小
this->read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
// 测试--打印输出消息结构体的内容
// qDebug()<<"recvMsg 结构体总长度:"<<pdu->uiPDULen;
// qDebug()<<"recvMsg 消息类型:"<<pdu->uiMsgType;
// qDebug()<<"recvMsg 消息长度:"<<pdu->uiMsgLen;
// qDebug()<<"recvMsg 参数1:"<<pdu->caData;
// qDebug()<<"recvMsg 参数2:"<<pdu->caData+32;
// qDebug()<<"recvMsg 接收到的消息:"<<pdu->caMsg;
// 根据消息类型对消息进行处理
switch (pdu->uiMsgType)
{
// 注册请求
case ENUM_MSG_TYPE_REGIST_REQUEST:
{
// 将消息取出
char caName[32] = {'\0'};
char caPwd[32] = {'\0'};
memcpy(caName,pdu->caData,32);
memcpy(caPwd,pdu->caData+32,32);
// 测试
qDebug()<<"recvMsg REGIST caName: "<<caName;
qDebug()<<"recvMsg REGIST caPwd: "<<caPwd;
// 处理消息
bool ret = OperateDB::getInstance().handleRegist(caName,caPwd);
// 向客户端发送响应
// 初始化响应注册的PDU对象
PDU* registPdu = initPDU(0);
// 消息类型为注册响应
registPdu->uiMsgType = ENUM_MSG_TYPE_REGIST_RESPOND;
// 将消息存储到消息结构体
memcpy(registPdu->caData,&ret,sizeof(bool));
// 利用socket 向客户端发送 注册的响应
write((char*)registPdu,registPdu->uiPDULen);
// 释放 registPdu
free(registPdu);
registPdu=NULL;
break;
}
// 登录请求
case ENUM_MSG_TYPE_LOGIN_REQUEST:
{
// 将消息取出
char caName[32] = {'\0'};
char caPwd[32] = {'\0'};
memcpy(caName,pdu->caData,32);
memcpy(caPwd,pdu->caData+32,32);
// 测试
qDebug()<<"recvMsg LOGIN caName: "<<caName;
qDebug()<<"recvMsg LOGIN caPwd: "<<caPwd;
// 处理消息
bool ret = OperateDB::getInstance().handleLogin(caName,caPwd);
if(ret)
{
m_LoginName = caName;
}
// 向客户端发送响应
// 初始化响应登录的PDU对象
PDU* loginPdu = initPDU(0);
// 消息类型为注册响应
loginPdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_RESPOND;
// 将消息存储到消息结构体
memcpy(loginPdu->caData,&ret,sizeof(bool));
memcpy(loginPdu->caData+32,caName,32);
// 利用socket 向客户端发送 登录的响应
write((char*)loginPdu,loginPdu->uiPDULen);
// 释放 loginPdu
free(loginPdu);
loginPdu=NULL;
break;
}
// 查找用户请求
case ENUM_MSG_TYPE_FIND_USER_REQUEST:
{
// 将消息取出
char caName[32] = {'\0'};
memcpy(caName,pdu->caData,32);
// 测试
qDebug()<<"recvMsg FIND_USER caName: "<<caName;
// 处理消息
int ret = OperateDB::getInstance().handleFindUser(caName);
// 向客户端发送响应
// 初始化响应查找用户的PDU对象
PDU* findUserPdu = initPDU(0);
// 消息类型为查找用户响应
findUserPdu->uiMsgType = ENUM_MSG_TYPE_FIND_USER_RESPOND;
// 将消息存储到消息结构体
memcpy(findUserPdu->caData,&ret,sizeof(int));
// 利用socket 向客户端发送 查找用户的响应
write((char*)findUserPdu,findUserPdu->uiPDULen);
// 释放 findUserPdu
free(findUserPdu);
findUserPdu=NULL;
break;
}
// 在线用户请求
case ENUM_MSG_TYPE_ONLINE_USER_REQUEST:
{
// 处理消息
QStringList nameList = OperateDB::getInstance().handleOnlineUser();
// 获取列表大小
int listSize = nameList.size();
uint uiMsgLen = listSize*32;
// 向客户端发送响应
// 初始化响应在线用户的PDU对象
PDU* onlineUserPdu = initPDU(uiMsgLen);
// 消息类型为在线用户响应
onlineUserPdu->uiMsgType = ENUM_MSG_TYPE_ONLINE_USER_RESPOND;
// 将用户名 挨个放到 caMsg中
for(int i = 0; i<listSize; i++)
{
// 测试
// qDebug()<<"ONLINE_USER_REQUEST " << nameList.at(i);
// 将每一个用户名都存储到 caMsg中
memcpy(onlineUserPdu->caMsg+i*32,nameList.at(i).toStdString().c_str(),32);
}
// 利用socket 向客户端发送 在线用户的响应
write((char*)onlineUserPdu,onlineUserPdu->uiPDULen);
// 释放 onlineUserPdu
free(onlineUserPdu);
onlineUserPdu=NULL;
break;
}
default:
break;
}
// 释放pdu
free(pdu);
pdu=NULL;
}
可以看到,客户端与服务器中,接收消息的函数,都达到了150行上下。这对代码的可读性,以及后面的维护成本来说是致命的问题。因此,再次细分代码是非常有必要的。
首先,我们来分析一下,为什么会出现这样的问题呢?(以服务器为例)
1、在一开始,我们实现接收消息的函数recvMsg(),只是为了测试服务器与客户端之间的通信能力。目的是为了接收客户端发来的消息,并输出到终端进行显示。
2、在完成具体的功能之后,在接收消息的函数中,在接收到pdu之后,我们利用switch,根据不同的消息类型为case,在每个case中对消息进行了处理,并向客户端发回了响应。
而问题的本质就在这里。(违背了单一职责原则)
1、recvMsg()函数本身的目的只是为了接收客户端发来的消息。
2、后面的recvMsg()函数,不仅仅完成了接收消息的功能,还完成了处理消息的功能,以及向客户端发送响应消息的功能。(一个函数实现了三个功能)
这样就违背了单一职责原则:即要求一个函数只做一件事,以保证函数逻辑的清晰以及高内聚。
如何解决这个问题?
1、将原本的recvMsg()函数按照不同的功能,划分为3个函数
(1)readPDU():读出客户端发来pdu内容的函数,readPDU(),将读出的pdu进行返回。
(2)handleReq():根据读出的内容,来处理不同请求的函数,handleReq()。因此要将readPDU()读出的内容传入该函数,通过处理得到要发回客户端的响应的pdu,并进行返回。
(3)sendPDU():根据处理结果,将响应pdu,发送到客户端的函数,sendPDU()。
2、设计一个请求处理器类(ReqHandler),将不同的请求,封装成不同的函数,并在handleReq()中,根据不同的消息类型,调用不同的函数。
在处理消息的函数内,根据不同的消息类型,要实现不同的功能,其实就是不同的函数。因此,需要将这些处理不同消息的函数,封装成一个类(请求处理器类:ReqHandler),在不同的case下,调用不同的函数即可。
一、服务器封装
1.1 拆分函数
(1)readPDU():读出客户端发来pdu内容的函数,readPDU(),将读出的pdu进行返回。
(2)handleReq():根据读出的内容,来处理不同请求的函数,handleReq()。因此要将readPDU()读出的内容传入该函数,通过处理得到要发回客户端的响应的pdu,并进行返回。
(3)sendPDU():根据处理结果,将响应pdu,发送到客户端的函数,sendPDU()。
1.1.1 读取pdu
查看recvMsg()函数,其实大致可以分为两部分,前半部分是用来将socket中的数据读取到pdu中,后半部分是根据pdu来对不同消息进行处理。
因此,我们可以将读取pdu的部分重新封装成一个函数,readPDU(),并将读取到的pdu返回。因此需要在recvMsg()函数中,调用readPDU()函数,并接收返回值。
PDU *MyTcpSocket::readPDU()
{
// 打印socket中的数据长度
qDebug()<<"socket中接收到的数据长度:"<<this->bytesAvailable();
// 读出消息结构体的总长度,这部分内容会从socket中读出去
uint uiPDULen = 0; // 传出参数
// 读取的大小为 消息结构体总长度的数据类型大小
this->read((char*)&uiPDULen,sizeof(uint));
// 计算实际消息长度--柔性数组长度(消息结构体总长度-结构体大小)
uint uiMsgLen = uiPDULen-sizeof(PDU);
// 初始化一个PUD结构体,用于存储
PDU* pdu = initPDU(uiMsgLen);
// 读出socket中剩余的数据,消息结构体的总长度已经被读出去了,剩下的不是一个完整的PDU结构体。
// 因此需要对pdu这个指针进行偏移,偏移的长度就是已经读出的数据长度(消息结构体总长度的数据类型大小)
// 而剩余需要读出的内容为,消息结构体总长度 减去 消息结构体总长度的数据类型大小
this->read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
返回读取到的pdu
return pdu;
}
1.1.2 处理客户端请求
我们将recvMsg()函数中的前面的一部分封装为了read PDU函数。通过readPDU()函数,我们得到了客户端发来的数据。
而剩下的内容就是在不同消息类型中,对消息进行处理,得到处理结果,并将结果发回到客户端。
因此,我们需要将处理消息封装为一个处理请求的函数handleReq(),并将得到到结果存储到响应pdu中,并将响应pdu返回。
PDU *MyTcpSocket::handleReq(PDU *pdu)
{
// 根据消息类型对消息进行处理
switch (pdu->uiMsgType)
{
// 注册请求
case ENUM_MSG_TYPE_REGIST_REQUEST:
{
// 将消息取出
char caName[32] = {'\0'};
char caPwd[32] = {'\0'};
memcpy(caName,pdu->caData,32);
memcpy(caPwd,pdu->caData+32,32);
// 处理消息
bool ret = OperateDB::getInstance().handleRegist(caName,caPwd);
// 向客户端发送响应
// 初始化响应注册的PDU对象
PDU* registPdu = initPDU(0);
// 消息类型为注册响应
registPdu->uiMsgType = ENUM_MSG_TYPE_REGIST_RESPOND;
// 将消息存储到消息结构体
memcpy(registPdu->caData,&ret,sizeof(bool));
return registPdu;
break;
}
// 登录请求
case ENUM_MSG_TYPE_LOGIN_REQUEST:
{
// 将消息取出
char caName[32] = {'\0'};
char caPwd[32] = {'\0'};
memcpy(caName,pdu->caData,32);
memcpy(caPwd,pdu->caData+32,32);
// 处理消息
bool ret = OperateDB::getInstance().handleLogin(caName,caPwd);
if(ret)
{
m_LoginName = caName;
}
// 向客户端发送响应
// 初始化响应登录的PDU对象
PDU* loginPdu = initPDU(0);
// 消息类型为注册响应
loginPdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_RESPOND;
// 将消息存储到消息结构体
memcpy(loginPdu->caData,&ret,sizeof(bool));
memcpy(loginPdu->caData+32,caName,32);
return loginPdu
break;
}
// 查找用户请求
case ENUM_MSG_TYPE_FIND_USER_REQUEST:
{
// 将消息取出
char caName[32] = {'\0'};
memcpy(caName,pdu->caData,32);
// 处理消息
int ret = OperateDB::getInstance().handleFindUser(caName);
// 向客户端发送响应
// 初始化响应查找用户的PDU对象
PDU* findUserPdu = initPDU(0);
// 消息类型为查找用户响应
findUserPdu->uiMsgType = ENUM_MSG_TYPE_FIND_USER_RESPOND;
// 将消息存储到消息结构体
memcpy(findUserPdu->caData,&ret,sizeof(int));
return findUserPdu;
break;
}
// 在线用户请求
case ENUM_MSG_TYPE_ONLINE_USER_REQUEST:
{
// 处理消息
QStringList nameList = OperateDB::getInstance().handleOnlineUser();
// 获取列表大小
int listSize = nameList.size();
uint uiMsgLen = listSize*32;
// 向客户端发送响应
// 初始化响应在线用户的PDU对象
PDU* onlineUserPdu = initPDU(uiMsgLen);
// 消息类型为在线用户响应
onlineUserPdu->uiMsgType = ENUM_MSG_TYPE_ONLINE_USER_RESPOND;
// 将用户名 挨个放到 caMsg中
for(int i = 0; i<listSize; i++)
{
// 将每一个用户名都存储到 caMsg中
memcpy(onlineUserPdu->caMsg+i*32,nameList.at(i).toStdString().c_str(),32);
}
return onlineUserPdu;
break;
}
default:
break;
}
return NULL;
}
1.1.3 向客户端发送pdu
在处理请求的函数中,我们得到了需要发回客户端的响应pdu。
因此,需要实现一个发送PDU的函数(sendPDU),将pdu发回客户端。
void MyTcpSocket::sendPDU(PDU *resPdu)
{
// 利用socket 向客户端发送 注册的响应
write((char*)resPdu,resPdu->uiPDULen);
// 发送完消息,需要释放 resPdu
if(!resPdu)
{
free(resPdu);
resPdu=NULL;
}
}
在recvMsg中,依次调用三个函数
// 接收消息的槽函数
void MyTcpSocket::recvMsg()
{
// 读取PDU
PDU* pdu = readPDU();
// 处理请求
PDU* resPdu = handleReq(pdu);
// 响应为空,无需发送,函数返回
if(!resPdu) return;
// 发送响应
sendPDU(resPdu);
if(!pdu)
{
// 释放pdu
free(pdu);
pdu=NULL;
}
}
1.2 请求处理器类
在处理请求的函数内(handleReq()),根据不同的消息类型,要实现不同的功能,其实就是不同的函数。因此,需要将这些处理不同消息的函数,封装成一个类(请求处理器类:ReqHandler),在不同的case下,调用不同的函数即可。
在请求处理器类:ReqHandler中,定义一个成员变量,用来存储读取到的pdu,这样就不用在调用函数时,进行传参了。只需要将读取到的pdu,赋值给该成员变量即可。
reqhanlder.h
#ifndef REQHANDLER_H
#define REQHANDLER_H
#include "protocol.h"
#include <QObject>
class ReqHandler : public QObject
{
Q_OBJECT
public:
explicit ReqHandler(QObject *parent = nullptr);
// 处理注册请求
PDU* regist();
// 处理登录请求
PDU* login(QString& loginName);
// 处理查找用户请求
PDU* findUser();
// 处理在线用户请求
PDU* onlineUser();
// 客户端发来的pdu
PDU* m_pdu;
signals:
};
#endif // REQHANDLER_H
reqhanlder.cpp
#include "operatedb.h"
#include "reqhandler.h"
#include <QDebug>
ReqHandler::ReqHandler(QObject *parent) : QObject(parent)
{
}
// 处理注册请求
PDU *ReqHandler::regist()
{
// 将消息取出
char caName[32] = {'\0'};
char caPwd[32] = {'\0'};
memcpy(caName,m_pdu->caData,32);
memcpy(caPwd,m_pdu->caData+32,32);
// 测试
qDebug()<<"ReqHandler regist caName: "<<caName;
qDebug()<<"ReqHandler regist caPwd: "<<caPwd;
// 处理消息
bool ret = OperateDB::getInstance().handleRegist(caName,caPwd);
// 向客户端发送响应
// 初始化响应注册的PDU对象
PDU* registPdu = initPDU(0);
// 消息类型为注册响应
registPdu->uiMsgType = ENUM_MSG_TYPE_REGIST_RESPOND;
// 将消息存储到消息结构体
memcpy(registPdu->caData,&ret,sizeof(bool));
return registPdu;
}
// 处理登录请求
PDU *ReqHandler::login(QString& loginName)
{
// 将消息取出
char caName[32] = {'\0'};
char caPwd[32] = {'\0'};
memcpy(caName,m_pdu->caData,32);
memcpy(caPwd,m_pdu->caData+32,32);
// 测试
qDebug()<<"ReqHandler login caName: "<<caName;
qDebug()<<"ReqHandler login caPwd: "<<caPwd;
// 处理消息
bool ret = OperateDB::getInstance().handleLogin(caName,caPwd);
if(ret)
{
// 登录成功,记录登陆成功的用户名
loginName = caName;
}
// 向客户端发送响应
// 初始化响应登录的PDU对象
PDU* loginPdu = initPDU(0);
// 消息类型为注册响应
loginPdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_RESPOND;
// 将消息存储到消息结构体
memcpy(loginPdu->caData,&ret,sizeof(bool));
memcpy(loginPdu->caData+32,caName,32);
return loginPdu;
}
// 处理查找用户请求
PDU *ReqHandler::findUser(PDU *pdu)
{
// 将消息取出
char caName[32] = {'\0'};
memcpy(caName,pdu->caData,32);
// 测试
qDebug()<<"ReqHandler findUser caName: "<<caName;
// 处理消息
int ret = OperateDB::getInstance().handleFindUser(caName);
// 向客户端发送响应
// 初始化响应查找用户的PDU对象
PDU* findUserPdu = initPDU(0);
// 消息类型为查找用户响应
findUserPdu->uiMsgType = ENUM_MSG_TYPE_FIND_USER_RESPOND;
// 将消息存储到消息结构体
memcpy(findUserPdu->caData,&ret,sizeof(int));
return findUserPdu;
}
// 处理在线用户请求
PDU *ReqHandler::onlineUser()
{
// 处理消息
QStringList nameList = OperateDB::getInstance().handleOnlineUser();
// 获取列表大小
int listSize = nameList.size();
uint uiMsgLen = listSize*32;
// 向客户端发送响应
// 初始化响应在线用户的PDU对象
PDU* onlineUserPdu = initPDU(uiMsgLen);
// 消息类型为在线用户响应
onlineUserPdu->uiMsgType = ENUM_MSG_TYPE_ONLINE_USER_RESPOND;
// 将用户名 挨个放到 caMsg中
for(int i = 0; i<listSize; i++)
{
// 测试
// qDebug()<<"ReqHandler onlineUser " << nameList.at(i);
// 将每一个用户名都存储到 caMsg中
memcpy(onlineUserPdu->caMsg+i*32,nameList.at(i).toStdString().c_str(),32);
}
return onlineUserPdu;
}
因此,还需要在mytcpsocket类中,添加一个 请求处理器类的成员变量,在mytcpsocket的构造函数中初始化这个成员变量
MyTcpSocket::MyTcpSocket()
{
// 新建一个消息处理器成员
m_rh = new ReqHandler();
// 将socket中,接收到信息触发的信号,与取出信息的信号槽函数进行关联
connect(this,&MyTcpSocket::readyRead,this,&MyTcpSocket::recvMsg);
// 将socket中,接收客户端关闭的信号,与处理下线的槽函数进行关联
connect(this,&MyTcpSocket::disconnected,this,&MyTcpSocket::clientOffline);
}
在handleReq()函数中,就可以改进为
PDU *MyTcpSocket::handleReq(PDU *pdu)
{
// 将读取到的pdu,赋值给消息处理器中的成员变量
m_rh->m_pdu = pdu;
// 根据消息类型对消息进行处理
switch (pdu->uiMsgType)
{
// 注册请求
case ENUM_MSG_TYPE_REGIST_REQUEST:
{
return m_rh->regist();
}
// 登录请求
case ENUM_MSG_TYPE_LOGIN_REQUEST:
{
return m_rh->login(m_LoginName);
}
// 查找用户请求
case ENUM_MSG_TYPE_FIND_USER_REQUEST:
{
return m_rh->findUser();
}
// 在线用户请求
case ENUM_MSG_TYPE_ONLINE_USER_REQUEST:
{
return m_rh->onlineUser();
}
default:
break;
}
return NULL;
}
这样我们就完成了服务器函数的封装。
二、客户端封装
客户端的封装,基本与服务器一致。
1、在客户端接收消息的函数中,我们也是先将服务器发回的消息进行读取,然后再根据不同的消息类型进行处理,显示不同的结果。因此我们可以将这部分封装为两个函数。
(1)将服务器发来的数据读取到pdu。(readPDU)
(2)根据不同的消息类型,对服务器的响应,进行不同的处理。(handleRes)
2、根据处理不同的响应消息,需要不同的函数,创建一个响应处理器类:ResHandle。将处理不同消息类型的功能在该类中封装成不同的函数。在handleRes()函数中,调用即可。
3、在发送消息时,总是需要调用write,写入不同的pdu。因此,我们可以将向服务器发送消息的功能封装为一个函数(sendPDU()),在发送消息时,只需要将封装好的pdu传入即可。
具体的封装过程和思路,和服务器的基本一致。这里就不多进行叙述。
代码会放到最后
总结
今天我们完成的主要内容就是,由于功能的增加,一些函数开始变得冗余且繁琐。为了增加代码的可读性,以及后面在增加其他功能或修改现有功能时的高效维护,对一些函数进行了再次封装。
最主要的问题:(违背了单一职责原则)
一个函数,却实现了多个函数的功能,随着功能的增加,就会导致这个函数内的代码变得冗余。所以要按照不同的功能进行细致的划分,封装成其他函数,甚至是其他的类。
而我们能彻底避免这种问题么?
显然想完全避免,是不太可能的,我们只能尽量减少这种情况的发生
(1)在实现函数功能之前,就想清除该功能相关的流程
(2)而在一开始,就将所有的函数或者类完成划分,是不可取的。你就实现了一个登录功能,就分了10几个甚至20几个文件和类,显然这种做法是不合理的,是浪费自身效率的
(3)因此,我们只能在编写代码的过程中,不断的完善我们的代码。在发现某一函数或类违背了某些原则时,就要想清楚。该不该这样做?目的是什么?问题怎么避免?而不是将错就错。
一些思考
其实不单单是函数封装,需要进行思考。对于命名,变量名(局部变量、成员变量、形参)、函数名、类名、控件名,都需要在深思熟虑之后才进行命名的。而不是随随便便的a、b、c。我相信,随便给变量起名字的,都不用很长时间。一周以后,自己都不知道自己写了一坨什么玩意。
只有这样,才能避免写出史山代码。在我看来,一个合格的程序员就是计算机行业的艺术家,而一份标准的代码就是一件艺术品。一个项目完成,你交付的东西不说是一件艺术品,起码是一件说的过去的工艺品。而不是一坨答辩。这样在后面接手项目的人,在看到你写的代码时,就会由衷的赞叹一句,上一个写代码的人确实是个人。而不是反问一句,上一个写代码的是个人么?
这里向大家推荐一本书(不是带货)
《高质量C++/C编程指南》由林锐 博士完成于2001年7月24日。该书系统阐述了一个优秀的程序员应该怎样编写出合格且规范的代码。我相信,一本好书是经得起时间的考验的。
该书的链接在这里
通过百度网盘分享的文件:高质量编程指南.pdf
链接:https://pan.baidu.com/s/12n7PwU3Kn8zLQzNlNQ8nrQ?pwd=1223
提取码:1223
--来自百度网盘超级会员V4的分享
完整代码
依旧在码云上有提交,请自行寻找历史提交