上节服务端与客户端1.1——持续处理“单个客户端”发送的请求_贪睡的蜗牛的博客-CSDN博客
目录
使用结构体进行传递数据1.21
使用网络数据报文(增加数据头)1.22
服务端修改
客户端修改
网络数据报文一次发送1.23
客户端代码
服务器修改后代码
使用dataLength来明确具体需要接收多少1.24
服务端修改后的代码
上节是使用字符串的形式充当的请求命令,使用getName获得名字,使用getAge获得年龄,其他命令统一用一个信息进行回复。本节将改进字符串的消息传递方式, 构建结构化的网络消息,使网络传输功能更复杂。这里我们使用结构体,一次性的将数据全部传递
使用结构体进行传递数据1.21一定要保证服务端和客户端( *** 作系统)中
数据结构字节序和大小保证一致 内存对齐(要求结构体里的变量的相对顺序是一样的)添加结构体
服务端和客户端统一添加同一个结构体,然后交互时传递的是这个结构体
struct DataPackage
{
int age;
char name[32];
};
服务端,将getAge和getName统一命令为getInfo
if (0 == strcmp(_recvBuf, "getInfo"))
{
DataPackage dp = { 24,"Evila" };
send(_clientSock, (const char*)&dp, sizeof(DataPackage), 0);
}
客户端,将返回数据后进行修改
//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;
}
注意,这样将会导致如果不是返回的DataPackage结构体,将会出现问题
使用网络数据报文(增加数据头)1.22本节是先传递数据头,然后再传递结构体
报文有两个部分,包头和包体,是网络消息的基本单元
包头:描述本次消息包的大小(比如固定前4个字节表示报文的大小)
包体:数据内容
传输数据时,客户端发送请求应先发送 包头 再发送包体; 服务器端接收请求时,先接收请求头,再接收请求体;同样的,服务器端返回时和客户端接收时。
这里枚举下命令,添加消息头结构体,添加登录、退出登录、登录结果,退出登录结果四个结构体
下列枚举,从0开始的
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;
};
服务端修改
服务端在收到DataHeader(里面包含cmd),判断cmd是啥,然后做出处理,先返回一个头,再返回处理结果(这里的头用客户端请求来的头)
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
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;
};
int main()
{
//启动 windows socket 2.x 环境
WORD versionCode = MAKEWORD(2, 2); //创建一个版本号
WSADATA data;
WSAStartup(versionCode, &data); //启动Socket网络API的函数
///
//(1) 用Socket API建立简易的TCP服务端
// 1. 建立一个Socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // ipv4 面向字节流的 tcp协议
// 2. 绑定接受客户端连接的端口 bind
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567); //端口号 host to net sockaddr_in中port是USHORT类型
//网络中port是 unsigend short类型 因此需要Htons进行转换
//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //服务器绑定的IP地址 127.0.0.1是本地地址
_sin.sin_addr.S_un.S_addr = INADDR_ANY; //不限定访问该服务端的IP
if (bind(_sock, (sockaddr*)&_sin, sizeof(_sin)) == SOCKET_ERROR) //sockaddr 不利于编码
{
cout << "ERROR: 绑定用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 绑定端口成功..." << endl;
}
// 3. 监听网络端口 listen
if (listen(_sock, 5) == SOCKET_ERROR)//第二个参数 backbag 最大允许连接数量
{
cout << "ERROR: 监听用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 监听端口成功..." << endl;
}
// 4. 等待接受客户端连接 accept
sockaddr_in _clientAddr = {};
int cliendAddrLen = sizeof(_clientAddr);
SOCKET _clientSock = INVALID_SOCKET; // 初始化无效的socket 用来存储接入的客户端
_clientSock = accept(_sock, (sockaddr*)&_clientAddr, &cliendAddrLen);
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;
}
}
// 6. 关闭socket
closesocket(_sock);
// 清除Windows socket环境
WSACleanup();
return 0;
}
客户端修改
客户端先两次发送,再两次接收,两次分别都是包头和具体内容
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
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;
};
int main()
{
//启动 windows socket 2.x 环境
WORD versionCode = MAKEWORD(2, 2); //创建一个版本号
WSADATA data;
WSAStartup(versionCode, &data); //启动Socket网络API的函数
///
//(1) 用Socket API建立简易的TCP客户端
// 1. 建立一个Socket 下面第三个参数不需要指定
SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0); // ipv4 面向字节流的 tcp协议
if (INVALID_SOCKET == _sock)
{
cout << "错误,建立socket失败"<> 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;
//}
}
// 6. 关闭socket
closesocket(_sock);
// 清除Windows socket环境
WSACleanup();
getchar();//防止一闪而过
return 0;
}
网络数据报文一次发送1.23
本节是一次发送数据,大体流程是先接受请求头,根据请求头进入相应的逻辑处理,然后再接收剩下的内容
首先在服务端,客户端补充消息枚举
2、对每个消息添加继承消息头
3、注意下面内容,偏移量
客户端代码#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
enum CMD //消息枚举
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGOUT_RESULT,
CMD_ERROR
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
struct Login : public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char Password[32];
};
struct Logout :public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGINOUT;
}
char userName[32];
};
struct LoginResult :public DataHeader
{
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct LogoutResult :public DataHeader
{
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
int main()
{
//启动 windows socket 2.x 环境
WORD versionCode = MAKEWORD(2, 2); //创建一个版本号
WSADATA data;
WSAStartup(versionCode, &data); //启动Socket网络API的函数
///
//(1) 用Socket API建立简易的TCP客户端
// 1. 建立一个Socket 下面第三个参数不需要指定
SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0); // ipv4 面向字节流的 tcp协议
if (INVALID_SOCKET == _sock)
{
cout << "错误,建立socket失败"<> cmdBuf;
// 4 处理请求
if (strcmp(cmdBuf, "exit") == 0)
{
break;
}
else if (0 == strcmp(cmdBuf, "login"))
{
Login _login;
strcpy_s(_login.userName, "Evila");
strcpy_s(_login.Password, "Evila_Password");
// 5 向服务器发送请求命令
send(_sock, (const char*)&_login, sizeof(Login), 0);
//6. 接受服务器信息 recv
LoginResult _lgRes;
recv(_sock, (char*)&_lgRes, sizeof(LoginResult), 0);
cout << "LoginResult: " << _lgRes.result << endl;
}
else if (0 == strcmp(cmdBuf, "logout"))
{
Logout _logout;
strcpy_s(_logout.userName, "Evila");
// 5 向服务器发送请求命令
send(_sock, (const char*)&_logout, sizeof(Logout), 0);
//6. 接受服务器信息 recv
LogoutResult _lgRes;
//返回数据
recv(_sock, (char*)&_lgRes, sizeof(LogoutResult), 0);
cout << "LogoutResult: " << _lgRes.result << endl;
}
else
{
cout << "不支持的命令,请重新输入。" << endl;
}
}
// 6. 关闭socket
closesocket(_sock);
// 清除Windows socket环境
WSACleanup();
getchar();//防止一闪而过
return 0;
}
服务器修改后代码
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
enum CMD //消息枚举
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGOUT_RESULT,
CMD_ERROR
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
struct Login : public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char Password[32];
};
struct Logout :public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGINOUT;
}
char userName[32];
};
struct LoginResult :public DataHeader
{
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct LogoutResult :public DataHeader
{
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
int main()
{
//启动 windows socket 2.x 环境
WORD versionCode = MAKEWORD(2, 2); //创建一个版本号
WSADATA data;
WSAStartup(versionCode, &data); //启动Socket网络API的函数
///
//(1) 用Socket API建立简易的TCP服务端
// 1. 建立一个Socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // ipv4 面向字节流的 tcp协议
// 2. 绑定接受客户端连接的端口 bind
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567); //端口号 host to net sockaddr_in中port是USHORT类型
//网络中port是 unsigend short类型 因此需要Htons进行转换
//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //服务器绑定的IP地址 127.0.0.1是本地地址
_sin.sin_addr.S_un.S_addr = INADDR_ANY; //不限定访问该服务端的IP
if (bind(_sock, (sockaddr*)&_sin, sizeof(_sin)) == SOCKET_ERROR) //sockaddr 不利于编码
{
cout << "ERROR: 绑定用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 绑定端口成功..." << endl;
}
// 3. 监听网络端口 listen
if (listen(_sock, 5) == SOCKET_ERROR)//第二个参数 backbag 最大允许连接数量
{
cout << "ERROR: 监听用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 监听端口成功..." << endl;
}
// 4. 等待接受客户端连接 accept
sockaddr_in _clientAddr = {};
int cliendAddrLen = sizeof(_clientAddr);
SOCKET _clientSock = INVALID_SOCKET; // 初始化无效的socket 用来存储接入的客户端
_clientSock = accept(_sock, (sockaddr*)&_clientAddr, &cliendAddrLen);
while (true)
{
DataHeader header = {};
//5 首先接收数据包头
int nlen = recv(_clientSock, (char*)&header, sizeof(DataHeader), 0); //经过改进 包头信息已经继承到了包体中 按照内存对齐 首先读取sizeof(DataHeader)的字节序列
if (nlen <= 0)
{
//客户端退出
cout << "客户端已退出,任务结束" << endl;
break;
}
switch (header.cmd)
{
case CMD_LOGIN:
{
Login _login;
recv(_clientSock, (char*)&_login + sizeof(DataHeader), sizeof(Login) - sizeof(DataHeader), 0); //这里要注意 程序已经读取了Dataheader了,因此这里应该从_login + sizeof(DataHeader) 开始读取,读取的长度也需要剪掉包头的长度 实现传输的数据内存对齐
cout << "收到命令:CMD_LOGIN" << " 数据长度 = " << header.dataLength << " UserName = " << _login.userName << " Password = " << _login.Password << endl;
//忽略了判断用户名密码是否正确的过程
LoginResult _loginres;
send(_clientSock, (char*)&_loginres, sizeof(LoginResult), 0);
}break;
case CMD_LOGINOUT:
{
Logout _logout;
recv(_clientSock, (char*)&_logout + sizeof(DataHeader), sizeof(Logout) - sizeof(DataHeader), 0);
cout << "收到命令:CMD_LOGOUT" << " 数据长度 = " << header.dataLength << " UserName = " << _logout.userName << endl;
LogoutResult _logoutres;
send(_clientSock, (char*)&_logoutres, sizeof(LogoutResult), 0);
}break;
default:
header.cmd = CMD_ERROR;
header.dataLength = 0;
send(_clientSock, (char*)&header, sizeof(DataHeader), 0);
break;
}
}
// 6. 关闭socket
closesocket(_sock);
// 清除Windows socket环境
WSACleanup();
return 0;
}
使用dataLength来明确具体需要接收多少1.24
dataLength的作用是告知收发文件的大小,本次改进通过使用dataLength提供的长度进行收发。
1、创建一个缓冲区首先只接收数据头
2、通过将数据头转换为标准数据头去判断请求的类型,然后进去相应的处理流程,继续将后续的数据读入缓冲区内
服务端修改后的代码#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
enum CMD //消息枚举
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGOUT_RESULT,
CMD_ERROR
};
//消息头
struct DataHeader
{
short dataLength; //数据长度 32767字节
short cmd;
};
struct Login : public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char Password[32];
};
struct Logout :public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGINOUT;
}
char userName[32];
};
struct LoginResult :public DataHeader
{
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct LogoutResult :public DataHeader
{
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
int main()
{
//启动 windows socket 2.x 环境
WORD versionCode = MAKEWORD(2, 2); //创建一个版本号
WSADATA data;
WSAStartup(versionCode, &data); //启动Socket网络API的函数
///
//(1) 用Socket API建立简易的TCP服务端
// 1. 建立一个Socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // ipv4 面向字节流的 tcp协议
// 2. 绑定接受客户端连接的端口 bind
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567); //端口号 host to net sockaddr_in中port是USHORT类型
//网络中port是 unsigend short类型 因此需要Htons进行转换
//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //服务器绑定的IP地址 127.0.0.1是本地地址
_sin.sin_addr.S_un.S_addr = INADDR_ANY; //不限定访问该服务端的IP
if (bind(_sock, (sockaddr*)&_sin, sizeof(_sin)) == SOCKET_ERROR) //sockaddr 不利于编码
{
cout << "ERROR: 绑定用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 绑定端口成功..." << endl;
}
// 3. 监听网络端口 listen
if (listen(_sock, 5) == SOCKET_ERROR)//第二个参数 backbag 最大允许连接数量
{
cout << "ERROR: 监听用于接受客户端连接的网络端口失败..." << endl;
}
else
{
cout << "SUCCESS: 监听端口成功..." << endl;
}
// 4. 等待接受客户端连接 accept
sockaddr_in _clientAddr = {};
int cliendAddrLen = sizeof(_clientAddr);
SOCKET _clientSock = INVALID_SOCKET; // 初始化无效的socket 用来存储接入的客户端
_clientSock = accept(_sock, (sockaddr*)&_clientAddr, &cliendAddrLen);
while (true)
{
//使用一个缓冲区接收数据 暂定最大收发1024个字节 后续会改进大文件的传输
char* szRecv = new char[1024];
//5 首先接收数据包头
int nlen = recv(_clientSock, szRecv, sizeof(DataHeader), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象
if (nlen <= 0)
{
//客户端退出
cout << "客户端已退出,任务结束" << endl;
break;
}
//用一个指针指向这个头部
DataHeader* header = (DataHeader*)szRecv;
switch (header->cmd)
{
case CMD_LOGIN:
{
Login* _login;
//读取Header->dataLength的数据长度 将数据继续读入saRecv这个块里面
recv(_clientSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
_login = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN" << " 数据长度 = " << header->dataLength << " UserName = " << _login->userName << " Password = " << _login->Password << endl;
//忽略了判断用户名密码是否正确的过程
LoginResult _loginres;
_loginres.result = 200;
send(_clientSock, (char*)&_loginres, sizeof(LoginResult), 0);
}break;
case CMD_LOGINOUT:
{
Logout* _logout;
recv(_clientSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
_logout = (Logout*)szRecv;
cout << "收到命令:CMD_LOGOUT" << " 数据长度 = " << header->dataLength << " UserName = " << _logout->userName << endl;
LogoutResult _logoutres;
_logoutres.result = 200;
send(_clientSock, (char*)&_logoutres, sizeof(LogoutResult), 0);
}break;
default:
{
header->cmd = CMD_ERROR;
header->dataLength = 0;
send(_clientSock, (char*)&header, sizeof(DataHeader), 0);
}
break;
}
}
// 6. 关闭socket
closesocket(_sock);
// 清除Windows socket环境
WSACleanup();
return 0;
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)