C++ Socket网络编程1.2版本 发送结构化的网络消息数据和网络数据报文
目前的版本中,客户端和服务端的网络通信 实现了简单的逻辑:(1)客户端发送请求(字符串)到服务端 (2)服务端处理请求(字符串) (3)服务端返回处理结果(字符串)
本节没有改进客户端和服务端的业务逻辑,而是改进字符串的消息传递,构建结构化的网络消息,使网络传输功能更复杂。
**
使用结构体定义结构化的网络消息(1.2版本)
**
在客户端和服务端程序代码中定义结构体
//一定要保证服务端和客户端(操作系统)中 数据结构字节顺序和大小保证一致 内存对齐
struct DataPackage
{
int age;
char name[32];
};
在服务端中 更改 send语句
if (0 == strcmp(_recvBuf, "getInfo"))
{
DataPackage dp = {24,"Evila"};
send(_clientSock, (const char*)&dp, sizeof(DataPackage), 0);
}
在客户端中 更改recv语句
char recvBuf[256] = {};
int nlen = recv(_sock, recvBuf, sizeof(recvBuf), 0); //返回值是接收数据的长度
if (nlen > 0)
{
DataPackage* pDP = (DataPackage*)recvBuf;
cout << "接收到数据: 年龄=" << pDP->age << ",名字=" << pDP->name << endl;
}
可以看到,当客户端输入正确的请求时,服务端可以返回逻辑更清晰的结果。
但是当客户端输入的请求不正确时,这是服务端返回的结果是乱的。
网络数据报文
报文有两个部分,包头和包体,是网络消息的基本单元
包头:描述本次消息包的大小
包体:数据内容
传输数据时,客户端发送请求应先发送 包头 再发送包体; 服务器端接收请求时,先接收请求头,再接收请求体;同样的,服务器端返回时和客户端接收时。
在服务端和客户端中增加如下结构体:
缺点:分次发送包头和包体 下节进行优化
enum CMD //消息枚举
{
CMD_LOGIN,
CMD_LOGINOUT,
CMD_ERROR
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
struct Login
{
char userName[32];
char Password[32];
};
struct Logout
{
char userName[32];
};
struct LoginResult
{
int result;
};
struct LogoutResult
{
int result;
};
服务器端等待接收和返回更新代码:
while (true)
{
DataHeader header = {};
//5 首先接收数据包头
int nlen = recv(_clientSock, (char*)&header, sizeof(header), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象
if (nlen <= 0)
{
//客户端退出
cout << "客户端已退出,任务结束" << endl;
break;
}
cout << "收到命令:" << header.cmd << "数据长度" <<header.dataLength<< endl;
switch (header.cmd)
{
case CMD_LOGIN:
{
Login _login = {};
recv(_clientSock, (char*)&_login, sizeof(Login),0);
//忽略判断用户名密码是否正确的过程
LoginResult _loginres = { 1 };
send(_clientSock, (char*)&header, sizeof(DataHeader), 0);
send(_clientSock, (char*)&_loginres, sizeof(LoginResult), 0);
cout << "登陆用户: " << _login.userName << endl;
}break;
case CMD_LOGINOUT:
{
Logout _logout = {};
recv(_clientSock, (char*)&_logout, sizeof(Logout), 0);
LogoutResult _logoutres = { 1 };
send(_clientSock, (char*)&header, sizeof(DataHeader), 0);
send(_clientSock, (char*)&_logoutres, sizeof(LogoutResult), 0);
cout << "登出用户: " << _logout.userName << endl;
}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 = {"Evila","Evila_Password"};
DataHeader _header = {};
_header.dataLength = sizeof(Login);
_header.cmd = CMD_LOGIN;
// 5 向服务器发送请求命令 先发送包头
send(_sock,(const char*)&_header, sizeof(DataHeader), 0);
//再发送包体
send(_sock, (const char*)&_login, sizeof(Login), 0);
//6. 接受服务器信息 recv
//先接收 返回数据的包头
DataHeader returnHeader = {};
LoginResult _lgRes = {};
recv(_sock, (char*)&returnHeader, sizeof(DataHeader), 0);
//再接收 返回数据的包体
recv(_sock, (char*)&_lgRes, sizeof(LoginResult), 0);
cout<<"LoginResult: " << _lgRes.result << endl;
}
else if (0 == strcmp(cmdBuf, "logout"))
{
Logout _logout = {"Evila"};
DataHeader _header = {};
_header.dataLength = sizeof(Logout);
_header.cmd = CMD_LOGINOUT;
// 5 向服务器发送请求命令 先发送包头
send(_sock, (const char*)&_header, sizeof(DataHeader), 0);
//再发送包体
send(_sock, (const char*)&_logout, sizeof(Logout), 0);
//6. 接受服务器信息 recv
//先接收 返回数据的包头
DataHeader returnHeader = {};
LogoutResult _lgRes = {};
recv(_sock, (char*)&returnHeader, sizeof(DataHeader), 0);
//再接收 返回数据的包体
recv(_sock, (char*)&_lgRes, sizeof(LogoutResult), 0);
cout << "LogoutResult: " << _lgRes.result << endl;
}
else
{
cout << "不支持的命令,请重新输入。" << endl;
}
//放到循环中 重复接受服务器的返回信息
//6. 接受服务器信息 recv
//char recvBuf[256] = {};
//int nlen = recv(_sock, recvBuf, sizeof(recvBuf), 0); //返回值是接收数据的长度
//if (nlen > 0)
//{
// DataPackage* pDP = (DataPackage*)recvBuf;
// cout << "接收到数据: 年龄=" << pDP->age << ",名字=" << pDP->name << endl;
//}
//else
//{
// cout << "ERROR: 数据传输失败..." << endl;
//}
}