我正在编写一个用于创建,发送,接收和解释ARP数据包的程序。我有一个代表ARP头的结构,如下所示:
struct ArpHeader
{
unsigned short hardwareType;
unsigned short protocolType;
unsigned char hardwareAddressLength;
unsigned char protocolAddressLength;
unsigned short operationCode;
unsigned char senderHardwareAddress[6];
unsigned char senderProtocolAddress[4];
unsigned char targetHardwareAddress[6];
unsigned char targetProtocolAddress[4];
};
这仅适用于长度为6的硬件地址和长度为4的协议地址。地址长度也在标题中给出,因此要正确,结构必须如下所示:
struct ArpHeader
{
unsigned short hardwareType;
unsigned short protocolType;
unsigned char hardwareAddressLength;
unsigned char protocolAddressLength;
unsigned short operationCode;
unsigned char senderHardwareAddress[hardwareAddressLength];
unsigned char senderProtocolAddress[protocolAddressLength];
unsigned char targetHardwareAddress[hardwareAddressLength];
unsigned char targetProtocolAddress[protocolAddressLength];
};
这显然不起作用,因为在编译时不知道地址长度。模板结构也不是一个选项,因为我想填充结构的值,然后将它从(ArpHeader *)转换为(char *)以获得可以在网络上发送的字节数组或转换从(char *)到(ArpHeader *)接收的字节数组,以便解释它。
一种解决方案是创建一个包含所有头字段作为成员变量的类,一个创建表示可以在网络上发送的ARP头的字节数组的函数,以及一个只需要一个字节数组的构造函数(在通过读取所有头字段并将它们写入成员变量来解释它。这不是一个很好的解决方案,因为它需要更多的代码。
相反,例如UDP报头的类似结构很简单,因为所有报头字段都是已知的恒定大小。我用#pragma pack(push, 1)
#pragma pack(pop)
围绕结构声明,这样我就可以实际进行简单的C样式转换,以获得在网络上发送的字节数组。
我在这里可以使用哪种解决方案可以接近结构,或者至少不需要比结构更多的代码? 我知道结构中的最后一个字段(如果它是一个数组)不需要特定的编译时大小,我可以使用类似的东西来解决我的问题吗?只是将这4个数组的大小留空将编译,但我不知道它将如何实际起作用。从逻辑上讲,它不起作用,因为如果第一个数组的大小未知,编译器就不知道第二个数组的起始位置。
答案 0 :(得分:9)
您需要一个相当低级别的东西,一个ARP数据包,并且您正在尝试找到一种正确定义数据结构的方法,以便您可以将blob转换为该结构。相反,您可以在blob上使用接口。
struct ArpHeader {
mutable std::vector<uint8_t> buf_;
template <typename T>
struct ref {
uint8_t * const p_;
ref (uint8_t *p) : p_(p) {}
operator T () const { T t; memcpy(&t, p_, sizeof(t)); return t; }
T operator = (T t) const { memcpy(p_, &t, sizeof(t)); return t; }
};
template <typename T>
ref<T> get (size_t offset) const {
if (offset + sizeof(T) > buf_.size()) throw SOMETHING;
return ref<T>(&buf_[0] + offset);
}
ref<uint16_t> hwType() const { return get<uint16_t>(0); }
ref<uint16_t> protType () const { return get<uint16_t>(2); }
ref<uint8_t> hwAddrLen () const { return get<uint8_t>(4); }
ref<uint8_t> protAddrLen () const { return get<uint8_t>(5); }
ref<uint16_t> opCode () const { return get<uint16_t>(6); }
uint8_t *senderHwAddr () const { return &buf_[0] + 8; }
uint8_t *senderProtAddr () const { return senderHwAddr() + hwAddrLen(); }
uint8_t *targetHwAddr () const { return senderProtAddr() + protAddrLen(); }
uint8_t *targetProtAddr () const { return targetHwAddr() + hwAddrLen(); }
};
如果您需要const
正确,请删除mutable
,创建const_ref
,然后将访问者复制到非const
版本中,并制作const
}版本返回const_ref
和const uint8_t *
。
答案 1 :(得分:3)
简答:你不能在C ++中拥有可变大小的类型。
C ++中的每个类型在编译期间都必须具有已知(且稳定)的大小。 IE运算符sizeof()
必须给出一致的答案。注意,您可以使用堆来保存包含可变数据量的类型(例如:std::vector<int>
),但实际对象的大小始终是常量。
因此,您永远不能生成一个类型声明,您将投射并获得神奇调整的字段。这深入到基本对象布局 - 每个成员(aka字段)必须具有已知(和稳定)的偏移。
通常,通过编写(或生成)解析输入数据并初始化对象数据的成员函数来解决问题。这基本上是古老的数据序列化问题,在过去30多年中已经无数次地解决了。
这是一个基本解决方案的模型:
class packet {
public:
// simple things
uint16_t hardware_type() const;
// variable-sized things
size_t sender_address_len() const;
bool copy_sender_address_out(char *dest, size_t dest_size) const;
// initialization
bool parse_in(const char *src, size_t len);
private:
uint16_t hardware_type_;
std::vector<char> sender_address_;
};
注意:
上面的代码显示了可以让您执行以下操作的基本结构:
packet p;
if (!p.parse_in(input, sz))
return false;
通过RAII做同样事情的现代方式看起来像这样:
if (!packet::validate(input, sz))
return false;
packet p = packet::parse_in(input, sz); // static function
// returns an instance or throws
答案 2 :(得分:2)
如果您希望保持对数据的简单访问和数据本身public
,那么可以在不改变访问数据的方式的情况下实现您的需求。首先,您可以使用std::string
而不是char数组来存储地址:
#include <string>
using namespace std; // using this to shorten notation. Preferably put 'std::'
// everywhere you need it instead.
struct ArpHeader
{
unsigned char hardwareAddressLength;
unsigned char protocolAddressLength;
string senderHardwareAddress;
string senderProtocolAddress;
string targetHardwareAddress;
string targetProtocolAddress;
};
然后,您可以重载转化运算符operator const char*()
和构造函数arpHeader(const char*)
(当然也包括operator=(const char*)
),以便保持当前的发送/接收功能正常工作,如果这就是你所需要的。
一个简化的转换运算符(跳过一些字段,使它不那么复杂,但你应该没有问题将它们添加回来),看起来像这样:
operator const char*(){
char* myRepresentation;
unsigned char mySize
= 2+ senderHardwareAddress.length()
+ senderProtocolAddress.length()
+ targetHardwareAddress.length()
+ targetProtocolAddress.length();
// We need to store the size, since it varies
myRepresentation = new char[mySize+1];
myRepresentation[0] = mySize;
myRepresentation[1] = hardwareAddressLength;
myRepresentation[2] = protocolAddressLength;
unsigned int offset = 3; // just to shorten notation
memcpy(myRepresentation+offset, senderHardwareAddress.c_str(), senderHardwareAddress.size());
offset += senderHardwareAddress.size();
memcpy(myRepresentation+offset, senderProtocolAddress.c_str(), senderProtocolAddress.size());
offset += senderProtocolAddress.size();
memcpy(myRepresentation+offset, targetHardwareAddress.c_str(), targetHardwareAddress.size());
offset += targetHardwareAddress.size();
memcpy(myRepresentation+offset, targetProtocolAddress.c_str(), targetProtocolAddress.size());
return myRepresentation;
}
虽然构造函数可以这样定义:
ArpHeader& operator=(const char* buffer){
hardwareAddressLength = buffer[1];
protocolAddressLength = buffer[2];
unsigned int offset = 3; // just to shorten notation
senderHardwareAddress = string(buffer+offset, hardwareAddressLength);
offset += hardwareAddressLength;
senderProtocolAddress = string(buffer+offset, protocolAddressLength);
offset += protocolAddressLength;
targetHardwareAddress = string(buffer+offset, hardwareAddressLength);
offset += hardwareAddressLength;
targetProtocolAddress = string(buffer+offset, protocolAddressLength);
return *this;
}
ArpHeader(const char* buffer){
*this = buffer; // Re-using the operator=
}
然后使用你的课程就像:
ArpHeader h1, h2;
h1.hardwareAddressLength = 3;
h1.protocolAddressLength = 10;
h1.senderHardwareAddress = "foo";
h1.senderProtocolAddress = "something1";
h1.targetHardwareAddress = "bar";
h1.targetProtocolAddress = "something2";
cout << h1.senderHardwareAddress << ", " << h1.senderProtocolAddress
<< " => " << h1.targetHardwareAddress << ", " << h1.targetProtocolAddress << endl;
const char* gottaSendThisSomewhere = h1;
h2 = gottaSendThisSomewhere;
cout << h2.senderHardwareAddress << ", " << h2.senderProtocolAddress
<< " => " << h2.targetHardwareAddress << ", " << h2.targetProtocolAddress << endl;
delete[] gottaSendThisSomewhere;
哪个应该为您提供所需的实用程序,并保持您的代码正常运行而不会改变课堂外的任何内容。
但是请注意,如果你愿意稍微更改其余的代码(在这里谈论你已经写过的那个,在课堂的外面), jxh 的答案应该是工作速度与此一样快,在内侧更优雅。