window下socket实现cs模式-简易版-1.2数据结构化

  • 上一篇文章数据传输都只是传输一个字符串

  • 纯字符串网络消息

    1. 优点:处理建议命令方便快捷
    2. 缺点:传递大量数据是字符串解析消耗大
  • 打算改进一下,使用结构话的二进制流来传输网络数据

    1. 优点:简单、方便、解析快、消耗低
    2. 缺点:需要严格的网络字节序一致
  • 下面就模拟一个登陆登出

    1. client 输入 login 就把账号密码发送给server
  • server


#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <iostream>
#include <windows.h>
#include <WinSock2.h>

#pragma comment(lib,"ws2_32.lib") // 指定库文件

using namespace std;

enum CMD
{
	CMD_LOGIN,
	CMD_LOGOUT,
	CMD_ERROR
};

struct DataHeader	// 数据头
{
	// 一般的网络数据包用int型就可以描述
	short dataLength;
	short cmd;
};

// 用户名密码
struct Login
{
	char UserName[32];
	char PassWord[32];
};

struct LoginResult	// 登录结果
{
	int result;
};

// -- 登出
struct Logout
{
	char UserName[32];
};

struct LogoutResult // 登出结果
{
	int result;
};

int main()
{
	// 启动win socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);

	// 用socket api 建立一个简单的tcp服务端
	// 1 建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	// SOCK_STREAM 面向流的TCP协议
																// 2 bind绑定用于监听客户端连接的网络端口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(8888);	// host to net unsigned short
	_sin.sin_addr.S_un.S_addr = INADDR_ANY; //inet_addr("127.0.0.1");	// 如果是在内网就可以用本机的地址,这样可以防止外网访问

	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin))) // 绑定有可能不成功所有要判断
	{
		cout << "绑定端口失败" << endl;
		exit(1);
	}
	cout << "bind 成功...." << endl;

	// 3 listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "监听端口失败" << endl;
		exit(1);
	}
	cout << "listen 成功...." << endl;

	// 4 accept 等待接受客户端连接
	sockaddr_in _client_addr = {};
	int n_addr_len = sizeof(_client_addr);

	SOCKET _client_sock = INVALID_SOCKET;

	_client_sock = accept(_sock, (sockaddr*)&_client_addr, &n_addr_len);
	if (INVALID_SOCKET == _client_sock)
	{
		cout << "accept失败" << endl;
		exit(1);
	}
	cout << "accept 成功.... ip = " << inet_ntoa(_client_addr.sin_addr) << endl;

	while (true)
	{
		DataHeader header = {};
		// 5 接受客户端数据 接收数据也是先接收包头

		int ret = recv(_client_sock, (char*)&header, sizeof(DataHeader), 0);
		if (ret <= 0)
		{
			cout << "client exit...." << endl;
			break;
		}
		cout << "接收数据...." << header.cmd << ", 数据长度" << header.dataLength << endl;

		switch (header.cmd)
		{
			case CMD_LOGIN:
			{
				// 在接收数据
				Login login = {};
				recv(_client_sock, (char*)&login, sizeof(Login), 0);

				LoginResult ret = { CMD_LOGIN };
				send(_client_sock, (char*)&header, sizeof(DataHeader), 0);
				send(_client_sock, (char*)&ret, sizeof(LoginResult), 0);
				
				// 判断账户和密码  -- 忽略
				cout << "UserName : " << login.UserName << endl;
				cout << "PassWord : " << login.PassWord << endl;
			}
			break;

			case CMD_LOGOUT:
			{
				Logout logout = {};
				recv(_client_sock, (char*)&logout, sizeof(Logout), 0);

				LoginResult ret = { CMD_LOGOUT };
				send(_client_sock, (char*)&header, sizeof(DataHeader), 0);
				send(_client_sock, (char*)&ret, sizeof(ret), 0);
				
				cout << "UserName : " << logout.UserName << endl;
			}
			break;

			default:
				header.cmd = CMD_ERROR;
				header.dataLength = 0;
				send(_client_sock, (char*)&header, sizeof(DataHeader), 0);
			break;
		}

	}

	// 6 关闭套接字
	closesocket(_sock);
	// 清理win socket环境
	WSACleanup();
	getchar();
	return 0;
}

  • client 客户端也是如此

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
//#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <windows.h>
#include <WinSock2.h>
#include <string>
#include <stdio.h>

#pragma comment(lib,"ws2_32.lib") // 指定库文件

using namespace std;

enum CMD
{
	CMD_LOGIN,
	CMD_LOGOUT,
	CMD_ERROR
};

// 消息头
struct DataHeader
{
	// 一般的网络数据包用int型就可以描述
	short dataLength;	// 数据长度
	short cmd;
};

// -- 登录
struct Login
{
	char UserName[32];
	char PassWord[32];
};

struct LoginResult	// 登录结果
{
	int result;
};

// -- 登出
struct Logout
{
	char UserName[32];
};

struct LogoutResult	 // 登出结果
{
	int result;
};

int main()
{
	// 启动win socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);

	// 用socket api 建立一个简单的tcp客户端
	// 1 建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0);	// SOCK_STREAM 面向流的TCP协议

	if (INVALID_SOCKET == _sock)
	{
		cout << "socket error" << endl;
		return 0;
	}
	cout << "socket access" << endl;

	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(8888);	// host to net unsigned short
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 客户端要指定有效的服务器地址

														// 2 连接 connect
	if (INVALID_SOCKET == connect(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		cout << "connect error" << endl;
		return 0;
	}
	cout << "connect access" << endl;

	while (true)
	{
		// 3 输入请求命令
		char cmdBuf[128] = {};
		scanf_s("%s", cmdBuf, 128);

		// 4 处理请求命令
		if (0 == strcmp(cmdBuf, "exit"))
		{
			break;
		}
		else if (0 == strcmp(cmdBuf, "login"))
		{
			Login login = {"her","hermm"};
			DataHeader dh = {sizeof(login),CMD_LOGIN};

			// 5 向服务器发送请求命令 先发送消息头和长度
			send(_sock, (const char*)&dh, sizeof(dh), 0);
			send(_sock, (const char*)&login, sizeof(login), 0);

			// 接收服务器消息
			DataHeader retHeader = {};
			LoginResult loginRet = {};
			recv(_sock, (char*)&retHeader, sizeof(DataHeader), 0);
			recv(_sock, (char*)&loginRet, sizeof(LoginResult), 0);

			cout << "LoginResult: " << loginRet.result << endl;
		}
		else if (0 == strcmp(cmdBuf, "logout"))
		{
			Logout logout = {"her"};
			DataHeader dh = {sizeof(logout), CMD_LOGOUT};

			// 5 向服务器发送请求命令
			send(_sock, (const char*)&dh, sizeof(DataHeader), 0);
			send(_sock, (const char*)&logout, sizeof(Logout), 0);

			// 接收服务器消息
			DataHeader retHeader = {};
			LoginResult logoutRet = {};

			recv(_sock, (char*)&retHeader, sizeof(DataHeader), 0);
			recv(_sock, (char*)&logoutRet, sizeof(LoginResult), 0);
			cout << "LoginResult: " << logoutRet.result << endl;
		}
		else
		{
			cout << "不支持的命令,结束" << endl;
		}
	}

	// 4 关闭套接字
	closesocket(_sock);
	// 清理win socket环境
	WSACleanup();
	getchar();
	return 0;
}


在这里插入图片描述

  • client输入login,server为什么会接收数据是0呢,因为c++ 的枚举默认是从0开始的
  • 这样就能够实现数据结构化 , 但是这样的话会比较麻烦,每次都要发送包头和数据,要是能一次性携带就更好

-------------------------------- the end ---------------------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值