C++ Socket在进行网络数据的传送时,数据一般是char类型的字符数组,除此之外还有一些方法可以传送我们自己定义的数据类型
- 自定义结构体
- Json序列化
- 定义Class对象
1. 结构体
定义一个结构体,例如:
struct DataPack
{
int age;
string name;
};
在发送数据的时候对数据进行处理,将DataPack类型的指针强制转换成char类型指针,具体如下:
DataPack da = { 20, "anthony" };
send(cSock, (char *)&da, sizeof(da), 0);
接收数据的时候,定义一个相同类型的结构体对这个字符串进行解析:
DataPack tmp;
int nlen = recv(sock, (char *)&tmp, 128, 0);
if (nlen > 0)
{
cout << "接受到数据:" << tmp.age << " " << tmp.name << endl;
}
或者也可以这样:
int nlen = recv(sock, recvBuf, 128, 0); // recvBuf为接收数据的缓冲区
if (nlen > 0)
{
DataPack* da = (DataPack*)recvBuf; // 将char类型指针转换成DataPack类型指针
cout << "接受到数据:" << tmp->age << " " << tmp->name << endl;
}
1.1 网络数据报文
为了减少数据传送过程中出现的错误以及增强稳定性,我们可以使用网络数据报文的方式:
与网络协议的报文格式类似,报文有两个部分:包头和包体,将其作为网络消息的基本单元
包头:描述本次发送的数据包的大小和作用
包头:包的数据部分
下面我们定义一个登录和退出登录的消息结构:
// 包头中关于消息或命令的作用(登录,退出)
enum CMD
{
CMD_LOGIN,
CMD_LogOut,
CMD_ERROR
};
// 包头
struct DataHeader
{
int lenth;
int cmd;
};
// 登录数据
struct Login
{
char UserName[32];
char passWord[32];
};
// 登录的结果
struct LoginSucceed
{
int result;
};
// 退出登录
struct LogOut
{
char UserName[32];
};
// 退出登录的结果
struct LogOutSucceed
{
int result;
};
每次发送数据时,先发送包头,在发送数据部分
服务端实现:
// 接受客户端的连接后
cSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
if (INVALID_SOCKET == cSock)
{
cout << "错误,接受到无效客户端SOCKET..." << endl;
}
cout << "新客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;
//5 send 向客户端发送数据
while (true)
{
DataHeader header = {};
int nlen = recv(cSock, (char*)&header, sizeof(header), 0);
if (nlen <= 0)
{
cout << "客户端退出..." << endl;
break;
}
cout << "接收到命令:" << header.cmd << " 数据长度:" << header.lenth << endl;
//处理请求
switch (header.cmd)
{
case CMD_LOGIN:
{
Login login;
int nlen = recv(cSock, (char*)&login, sizeof(login), 0);
LoginSucceed ret = { 1 };
send(cSock, (char*)&header, sizeof(header), 0);
send(cSock, (char*)&ret, sizeof(ret), 0);
}
break;
case CMD_LogOut:
{
LogOut logout;
recv(cSock, (char*)&logout, sizeof(logout), 0);
LogOutSucceed ret = { 1 };
send(cSock, (char*)&header, sizeof(header), 0);
send(cSock, (char*)&ret, sizeof(ret), 0);
}
break;
default:
header.cmd = CMD_ERROR;
header.lenth = 0;
send(cSock, (char*)&header, sizeof(header), 0);
break;
}
}
客户端实现:
if (SOCKET_ERROR == connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in)))
{
cout << "建立连接失败..." << endl;
}
else {
cout << "建立连接成功..." << endl;
}
while (true)
{
string comBuf;
cin >> comBuf;
if (comBuf == "exit")
{
break;
}
else if (comBuf == "login") {
Login login = { "anthony", "chen" };
DataHeader dh = {sizeof(login), CMD_LOGIN};
//向服务器发送请求命令
send(sock, (char*)&dh, sizeof(dh), 0);
send(sock, (char*)&login, sizeof(login), 0);
//接收服务器返回数据
DataHeader retHeader;
LoginSucceed loginRet;
recv(sock, (char*)&retHeader, sizeof(retHeader), 0);
recv(sock, (char*)&loginRet, sizeof(loginRet), 0);
cout << "LoginSucceed: " << loginRet.result << endl;
}
else if (comBuf == "logout") {
LogOut logout = { "anthony" };
DataHeader dh = { sizeof(logout), CMD_LogOut};
//向服务器发送请求命令
send(sock, (char*)&dh, sizeof(dh), 0);
send(sock, (char*)&logout, sizeof(logout), 0);
//接收服务器返回数据
DataHeader retHeader;
LogOutSucceed logoutRet;
recv(sock, (char*)&retHeader, sizeof(retHeader), 0);
recv(sock, (char*)&logoutRet, sizeof(logoutRet), 0);
cout << "LogOutSucceed: " << logoutRet.result << endl;
}
else {
cout << "命令无效,请重新输入:" << endl;
}
}
1.2 多次收发报文改为一次收发
为了减少出错,我们可以将多个结构体进行封装,使用聚合或继承的方式,如下:
// 这里使用继承的方式,除此之外也可以直接在一个结构体定义一个另外的结构体
// 例如:struct Login{DataHeader header; char UserName[32]; char passWord[32];}
enum CMD
{
CMD_LOGIN,
CMD_LogOut,
CMD_LOGIN_SU,
CMD_LOGOUT_SU,
CMD_ERROR
};
struct DataHeader
{
int lenth;
int cmd;
};
// Data Package
struct Login : public DataHeader
{
Login()
{
lenth = sizeof(Login);
cmd = CMD_LOGIN;
}
char UserName[32];
char passWord[32];
};
struct LoginSucceed : public DataHeader
{
LoginSucceed()
{
lenth = sizeof(LoginSucceed);
cmd = CMD_LOGIN_SU;
}
int result;
};
struct LogOut : public DataHeader
{
LogOut()
{
lenth = sizeof(LogOut);
cmd = CMD_LogOut;
}
char UserName[32];
};
struct LogOutSucceed : public DataHeader
{
LogOutSucceed()
{
lenth = sizeof(LogOutSucceed);
cmd = CMD_LOGOUT_SU;
}
int result;
};
服务端实现:
while (true)
{
/*DataHeader header = {};
int nlen = recv(cSock, (char*)&header, sizeof(header), 0);*/
Login login;
int nlen = recv(cSock, (char*)&login, sizeof(login), 0);
if (nlen <= 0)
{
cout << "客户端退出..." << endl;
break;
}
//处理请求
switch (login.cmd)
{
case CMD_LOGIN:
{
/*Login login;
recv(cSock, (char*)&login, sizeof(login), 0);*/
cout << "接收到命令:CMD_LOGIN" << " 数据长度:" << login.lenth << " UserName: " << login.UserName << " PassWord: " << login.passWord << endl;
LoginSucceed ret;
send(cSock, (char*)&ret, sizeof(ret), 0);
}
break;
case CMD_LogOut:
{
/*LogOut logout;
recv(cSock, (char*)&logout, sizeof(logout), 0);*/
cout << "接收到命令:CMD_LOGIN" << " 数据长度:" << login.lenth << " UserName: " << login.UserName << endl;
LogOutSucceed ret;
send(cSock, (char*)&ret, sizeof(ret), 0);
}
break;
default:
login.cmd = CMD_ERROR;
login.lenth = 0;
send(cSock, (char*)&login, sizeof(login), 0);
break;
}
}
客户端:
while (true)
{
string comBuf;
cin >> comBuf;
if (comBuf == "exit")
{
break;
}
else if (comBuf == "login") {
Login login;
strcpy(login.UserName, "anthony");
strcpy(login.passWord, "chen");
//向服务器发送请求命令
send(sock, (char*)&login, sizeof(login), 0);
//接收服务器返回数据
LoginSucceed loginRet;
recv(sock, (char*)&loginRet, sizeof(loginRet), 0);
cout << "LoginSucceed: " << loginRet.result << endl;
}
else if (comBuf == "logout") {
LogOut logout;
strcpy(logout.UserName, "anthony");
//向服务器发送请求命令
send(sock, (char*)&logout, sizeof(logout), 0);
//接收服务器返回数据
LogOutSucceed logoutRet;
recv(sock, (char*)&logoutRet, sizeof(logoutRet), 0);
cout << "LogOutSucceed: " << logoutRet.result << endl;
}
else {
cout << "命令无效,请重新输入:" << endl;
}
}
2. 使用Json(后续在尝试)
JSON 的全称为:JavaScript Object Notation,是一种轻量级的数据传输格式。C++处理JSON格式数据时,需要使用到第三方库,比较出名的是jsoncpp,下载地址为:json-cpp下载
3. 定义Class对象
和结构体类似,在传输自定义的对象数据时,转换指针的类型即可。