2. C++ Socket 网络数据传输的几种方式

C++ Socket在进行网络数据的传送时,数据一般是char类型的字符数组,除此之外还有一些方法可以传送我们自己定义的数据类型

  1. 自定义结构体
  2. Json序列化
  3. 定义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
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对象

和结构体类似,在传输自定义的对象数据时,转换指针的类型即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值