我希望通过编写自己的小二进制协议,通过TCP / IP与C ++交换数据。我的想法是通过以下列字节的形式定义值来实现协议:
BEGIN
LENGTH
FUNCTIONCODE
[data bytes] (optional)
CRC
END
如何定义“BEGIN”的二进制值,该值是唯一的,允许接收者识别新电报的开头?例如。如果我这样做:
static const int BEGIN = 0x41;
并且可选的数据字节也随机包含0x41值,这可能是接收器的问题。还是我错了?如果没有,我如何定义唯一的BEGIN和END值?
答案 0 :(得分:1)
我通常会为简单的C ++网络编写类似的东西。 (但这次我从头开始编写这段代码并且根本没有测试过它!)
class Packet : public std::string {
public:
struct __attibute__((__packed__)) Header {
static const int BEGIN = 0x41;
uint32_t begin;
uint16_t length;
uint16_t funcode;
void init(uint16_t len) { begin = BEGIN; length = len; }
bool verify() { return (BEGIN == begin); }
char *buffer() { return reinterpret_cast<char *>(this); }
};
class __attibute__((__packed__)) Footer {
uint32_t crc;
char term;
char newline;
void init() { term = ';'; newline = '\n'; }
bool verify() { return (';' == term && '\n' == newline); }
char *buffer() { return reinterpret_cast<char *>(this); }
};
public:
void init(size_t n = 0) {
resize(sizeof(Header) + n + sizeof(Footer));
Header * const h = header();
h->init(n);
Footer * const f = footer();
f->init();
}
// these two are valid only after init()
Header *header() { return reinterpret_cast<Header*>(&(*this)[0]); }
Footer *footer() { return reinterpret_cast<Footer*>(&(*this)[size() - sizeof(Footer)]); }
template <typename T>
T *buffer() { return reinterpret_cast<T *>(&(*this)[sizeof(Header)]); }
void extend(size_t n) { resize(size() + n); }
};
int sendMessage(int funcode) {
Packet p;
switch (funcode) {
case TIME: {
timeval tv;
gettimeofday(&tv, NULL);
p.init(sizeof tv);
Packet::Header * const h = p.header();
h->funcode = funcode;
timeval * const dst = p.buffer<timeval>();
*dst = tv;
} break;
case PID: {
const pid_t pid = getpid();
p.init(sizeof pid);
Packet::Header * const h = p.header();
h->funcode = funcode;
pid_t * const dst = p.buffer<pid_t>();
*dst = pid;
} break;
...
}
Packet::Footer * const f = p.footer();
f->crc = ...;
const ssize_t nSent = send(sock, p.data(), p.size(), 0);
...
}
Packet receiveMessage() {
Packet ret;
ret.init();
Packet::Header * const h = ret.header();
ssize_t nRecv = recv(sock, h->buffer(), sizeof *h, 0);
...
if ( ! h->verify()) ...
p.extend(h->length);
nRecv = recv(sock, p.buffer<char>(), h->length, 0);
switch (h->funcode) {
case TIME: {
timeval tv = *p->buffer<timeval>();
tm theTm;
localtime_r(&tv.tv_sec, &theTm);
char buf[128];
strftime(buf, sizeof buf, "%x %X", &tv);
cout << "Server time == " << buf << endl;
} break;
case PID: {
const pid_t pid = *p.buffer<pid_t>();
cout << "Server PID == " << pid << endl;
} break;
...
}
Packet::Footer * const f = ret.footer();
nRecv = recv(sock, f->buffer(), sizeof *f, 0);
if ( ! f->verify() || f->crc != ...) ...
return ret; // inefficient data copy, only for a sample code
}
我建议您将BEGIN代码定义为类似0x48454c4f
的内容,可以读取为ASCII "HELO"
,而不是像0x41
这样的随机整数。我向页脚添加了两个额外的字节';'
和'\n'
;当您将协议的数据包捕获转储到控制台时,您会发现它们很有用。请记住,可以始终使用sed
,perl
,python
或其他任何内容(但不是grep
;它不会接受任意十六进制搜索字符串)分析包括数据包转储在内的任意二进制数据。如果您很好地设计协议,您的调试将更容易。 (首先,您应该考虑使用ASCII协议而不是二进制协议。)
答案 1 :(得分:0)
协议定义通常如下:
FUNCTIONCODE
LENGTH
DATA
CRC
并且没有必要钓“BEGIN”。读取通过使用固定长度读取FUNCTIONCODE和LENGTH,然后根据LENGTH读取数据+ CRC,然后再次...
但是,如果您认为额外的“BEGIN”标记可以帮助您,请选择“BEGIN”作为不是FUNCTIONCODE的值,然后您有
BEGIN
FUNCTIONCODE
LENGTH
DATA
CRC
对于BEGIN等于BEGIN之后的每个字节,发送BEGIN两次。但是,在这种情况下,阅读要复杂得多......