14.7 Socket 循环结构体传输

在上述内容中笔者通过一个简单的案例给大家介绍了在套接字编程中如何传递结构体数据,本章将继续延申结构体传输,在某些时候例如我们需要传输一些当前系统的进程列表信息,或者是当前主机中的目录文件,此时就需要使用循环结构体传输功能,循环传输结构体的关键点在于,客户端发送结构体数据之前需要通过一次通信来告诉服务端需要接收的次数,当服务端接收到次数时则可利用接收计数器依次循环接收数据直到客户端完整所有数据包的发送。

14.7.1 服务端实现

多条结构体的传输方式与单条从原理上一致,只是多条结构体在传输时需要提前告知服务端我需要分几次将结构体传输给对方,因为数据包最大单次可发送8192字节,所以如果结构过多则需要分批次进行传输,如下是服务端实现代码片段,在代码中首先我们接收客户端发来的循环次数,该次数是一个字符串类型的,为了能用于循环体内,需要通过atoi(count)将其转换为一个整数,接着就是在循环体内不断地调用recv函数接收数据包,直到循环结束为止。

#include <iostream>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")

typedef struct
{
char HostName[32];
char Buffer[32];
}message;

int main(int argc, char* argv[])
{
WSADATA WSAData;
SOCKET sock;

WSAStartup(MAKEWORD(2, 0), &WSAData);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
std::cout << "创建套接字失败" << std::endl;
}

struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(9999);
ServerAddr.sin_addr.s_addr = INADDR_ANY;

auto res = bind(sock, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));
if (res == SOCKET_ERROR)
{
std::cout << "绑定失败" << std::endl;
}

res = listen(sock, 10);
if (res == SOCKET_ERROR)
{
std::cout << "侦听失败" << std::endl;
}

SOCKET msgsock;

msgsock = accept(sock, (LPSOCKADDR)0, (int*)0);
if (msgsock != INVALID_SOCKET)
{
// 接收循环次数
char count[32] = { 0 };
int recv_count_flag = recv(msgsock, count, sizeof(count), 0);
if (recv_count_flag != 0)
{
// 得到需要循环接收的次数
int index = atoi(count);
std::cout << "总共循环接收: " << count << " 次" << std::endl;

for (int x = 0; x < index; x++)
{
char recv_buf[4096] = { 0 };

// 循环输出接收结果
int recv_flag = recv(msgsock, recv_buf, sizeof(recv_buf), 0);
if (recv_flag != 0)
{
// 接收到结构,强制类型转换
message* msg = (message*)recv_buf;

std::cout << "用户名: " << msg->HostName << "数据: " << msg->Buffer << std::endl;

// 发送成功标志
send(msgsock, "success", 7, 0);
}
}
}
}

closesocket(sock);
WSACleanup();
return 0;
}

14.7.2 客户端实现

相对于服务端而言,客户端首先需要准备好一个待发送结构体链表,此处通过使用vector<message>的方式接收结构体链表,并通过sprintf()函数将循环次数由整数格式化为字符串,并将次数发送给服务端,当服务端接收到发送次数后会等待客户端向其发送对应数量的结构体,此时客户端只需要send循环发送即可。

#include <iostream>
#include <vector>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")

using namespace std;

typedef struct
{
char HostName[32];
char Buffer[32];
}message;

message msg;

int main(int argc, char* argv[])
{
WSADATA WSAData;
SOCKET sock;

WSAStartup(MAKEWORD(2, 0), &WSAData);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
std::cout << "创建套接字失败" << std::endl;
}

struct sockaddr_in ClientAddr;
ClientAddr.sin_family = AF_INET;
ClientAddr.sin_port = htons(9999);
ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

auto res = connect(sock, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr));

if (res == SOCKET_ERROR)
{
std::cout << "链接失败." << std::endl;
}

// 模拟元素填充
std::vector<message> vect;

for (int x = 0; x < 10; x++)
{
message ptr;
// 填充参数
sprintf(ptr.HostName, "lyshark %d", x);
sprintf(ptr.Buffer, "hello lyshark %d", x);
vect.push_back(ptr);
}

// 发送循环次数
char count[32] = { 0 };

// 整数转为字符串
sprintf(count, "%d", vect.size());

int send_count_flag = send(sock, count, sizeof(count), 0);

if (send_count_flag != 0)
{
std::cout << "发送循环次数: " << count << std::endl;

// 循环发送数据
for (int x = 0; x < vect.size(); x++)
{
char send_buf[4096] = { 0 };

// 发送字节序
memcpy(send_buf, &vect[x], sizeof(message));
int send_flag = send(sock, send_buf, sizeof(send_buf), 0);
if (send_flag != 0)
{
char recv_buf[32] = { 0 };
recv(sock, recv_buf, sizeof(recv_buf), 0);
std::cout << "发送完成,接收状态码: " << recv_buf << std::endl;
}
}
}

closesocket(sock);
WSACleanup();
return 0;
}

至此读者可分别编译并运行服务端与客户端,此时会看到如下图所示的结构体输出;