-
上一篇文章数据传输都只是传输一个字符串
-
纯字符串网络消息
- 优点:处理建议命令方便快捷
- 缺点:传递大量数据是字符串解析消耗大
-
打算改进一下,使用结构话的二进制流来传输网络数据
- 优点:简单、方便、解析快、消耗低
- 缺点:需要严格的网络字节序一致
-
下面就模拟一个登陆登出
- 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 ---------------------------------