在目前的版本中,将网络数据传输方式改进成为网络数据报文(结构体)的形式传输,但比较不合理的一点是,传输数据的过程中总是要先传输 包头,再传输包体。因此,本节将多次收发报文改进为一次收发报文。
再回顾一下上一节的关键内容:
1、定义了四个网络数据包结构体
//DataPackge
struct Login
{
char userName[32];
char Password[32];
};
struct Logout
{
char userName[32];
};
struct LoginResult
{
int result;
};
struct LogoutResult
{
int result;
};
2、定义了一个包头结构体 和 命令枚举
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGOUT_RESULT,
CMD_ERROR
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
通过定义包头和包体结构体,那么每次传输数据均需要两次据收发传输工作。
接下来进行对数据包的改进:
1、首先将名命令枚举扩充完整 消息头结构不变
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGOUT_RESULT,
CMD_ERROR
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
2、改变数据包DataPackage结构体,使它继承于消息头。这么做的优点是,不需要在每次定义数据包之前,还要定义一个消息头,也避免了产生不必要的错误。
struct Login: public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char Password[32];
};
struct Logout:public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGINOUT;
}
char userName[32];
};
struct LoginResult :public DataHeader
{
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct LogoutResult :public DataHeader
{
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
3、修改服务端收发数据的逻辑
while (true)
{
DataHeader header = {};
//5 首先接收数据包头
int nlen = recv(_clientSock, (char*)&header, sizeof(DataHeader), 0); //经过改进 包头信息已经继承到了包体中 按照内存对齐 首先读取sizeof(DataHeader)的字节序列
if (nlen <= 0)
{
//客户端退出
cout << "客户端已退出,任务结束" << endl;
break;
}
switch (header.cmd)
{
case CMD_LOGIN:
{
Login _login;
recv(_clientSock, (char*)&_login + sizeof(DataHeader), sizeof(Login) - sizeof(DataHeader),0); //这里要注意 程序已经读取了Dataheader了,因此这里应该从_login + sizeof(DataHeader) 开始读取,读取的长度也需要剪掉包头的长度 实现传输的数据内存对齐
cout << "收到命令:CMD_LOGIN" << " 数据长度 = " << header.dataLength<<" UserName = "<< _login.userName<<" Password = "<<_login.Password << endl;
//忽略了判断用户名密码是否正确的过程
LoginResult _loginres;
send(_clientSock, (char*)&_loginres, sizeof(LoginResult), 0);
}break;
case CMD_LOGINOUT:
{
Logout _logout;
recv(_clientSock, (char*)&_logout + sizeof(DataHeader), sizeof(Logout) - sizeof(DataHeader), 0);
cout << "收到命令:CMD_LOGOUT" << " 数据长度 = " << header.dataLength << " UserName = " << _logout.userName<< endl;
LogoutResult _logoutres;
send(_clientSock, (char*)&_logoutres, sizeof(LogoutResult), 0);
}break;
default:
header.cmd = CMD_ERROR;
header.dataLength = 0;
send(_clientSock, (char*)&header, sizeof(DataHeader), 0);
break;
}
}
改进客户端收发数据逻辑:
while (true)
{
// 3 输入请求命令
char cmdBuf[128] = {};
cout << "输入命令: ";
cin >> cmdBuf;
// 4 处理请求
if (strcmp(cmdBuf, "exit") == 0)
{
break;
}
else if (0 == strcmp(cmdBuf,"login"))
{
Login _login;
strcpy(_login.userName, "Evila");
strcpy(_login.Password, "Evila_Password");
// 5 向服务器发送请求命令
send(_sock, (const char*)&_login, sizeof(Login), 0);
//6. 接受服务器信息 recv
LoginResult _lgRes;
recv(_sock, (char*)&_lgRes, sizeof(LoginResult), 0);
cout<<"LoginResult: " << _lgRes.result << endl;
}
else if (0 == strcmp(cmdBuf, "logout"))
{
Logout _logout;
strcpy(_logout.userName, "Evila");
// 5 向服务器发送请求命令
send(_sock, (const char*)&_logout, sizeof(Logout), 0);
//6. 接受服务器信息 recv
LogoutResult _lgRes;
//返回数据
recv(_sock, (char*)&_lgRes, sizeof(LogoutResult), 0);
cout << "LogoutResult: " << _lgRes.result << endl;
}
else
{
cout << "不支持的命令,请重新输入。" << endl;
}
}