我有一个小型客户端服务器应用程序,我希望通过C而不是C ++的TCP套接字发送整个结构。假设结构如下:
struct something{
int a;
char b[64];
float c;
}
我发现很多帖子说我需要使用pragma pack或在发送和接收之前序列化数据。
我的问题是,使用JUST pragma pack还是仅仅序列化是否足够?或者我需要同时使用它们吗?
此外,由于序列化是处理器密集型过程,这会使您的性能急剧下降,因此在没有使用外部库的情况下序列化结构的最佳方法是什么(我想要一个示例代码/算法)?
答案 0 :(得分:15)
您需要以下内容通过网络轻松发送struct:
打包结构。对于gcc和兼容的编译器,请使用__attribute__((packed))
。
除了固定大小的无符号整数,满足这些要求的其他压缩结构或任何前者的数组之外,不要使用任何其他成员。有符号整数也可以,除非你的机器不使用二进制补码表示。
确定您的协议是使用整数的小端还是大端编码。在阅读和编写这些整数时进行转换。
此外,不会获取打包结构成员的指针,除了那些大小为1或其他嵌套打包结构的指针。请参阅this answer。
接下来是一个简单的编码和解码示例。它假设字节顺序转换函数hton8()
,ntoh8()
,hton32()
和ntoh32()
可用(前两个是无操作,但为了保持一致性)。
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
// get byte order conversion functions
#include "byteorder.h"
struct packet {
uint8_t x;
uint32_t y;
} __attribute__((packed));
static void decode_packet (uint8_t *recv_data, size_t recv_len)
{
// check size
if (recv_len < sizeof(struct packet)) {
fprintf(stderr, "received too little!");
return;
}
// make pointer
struct packet *recv_packet = (struct packet *)recv_data;
// fix byte order
uint8_t x = ntoh8(recv_packet->x);
uint32_t y = ntoh32(recv_packet->y);
printf("Decoded: x=%"PRIu8" y=%"PRIu32"\n", x, y);
}
int main (int argc, char *argv[])
{
// build packet
struct packet p;
p.x = hton8(17);
p.y = hton32(2924);
// send packet over link....
// on the other end, get some data (recv_data, recv_len) to decode:
uint8_t *recv_data = (uint8_t *)&p;
size_t recv_len = sizeof(p);
// now decode
decode_packet(recv_data, recv_len);
return 0;
}
就字节顺序转换函数而言,对于16位和32位整数,可以使用系统的htons()
/ ntohs()
和htonl()
/ ntohl()
分别转换为/从big-endian转换。但是,我不知道64位整数的任何标准函数,或者转换为/从小端转换。您可以使用my byte order conversion functions;如果你这样做,你必须通过定义BADVPN_LITTLE_ENDIAN
或BADVPN_BIG_ENDIAN
告诉它你机器的字节顺序。
就有符号整数而言,转换函数可以像我编写和链接的那样安全地实现(直接交换字节);只需将unsigned更改为signed。
UPDATE :如果你想要一个有效的二进制协议,但又不喜欢摆弄字节,你可以尝试Protocol Buffers(C implementation)之类的东西。这允许您在单独的文件中描述消息的格式,并生成用于编码和解码您指定格式的消息的源代码。我自己也实现了类似的东西,但大大简化了;请参阅my BProto generator和some examples(查看.bproto文件,使用addr.h作为用法示例)。
答案 1 :(得分:3)
在通过TCP连接发送任何数据之前,请制定协议规范。它不必是一个充满技术术语的多页文档。但它必须指定谁在什么时候传输什么,它必须在字节级别指定所有消息。它应该指定如何建立消息的结尾,是否有任何超时以及谁强加它们等等。
如果没有规范,很容易提出根本无法回答的问题。如果出现问题,哪一端有问题?根据规范,不符合规范的一端是错误的。 (如果两端都遵循规范并且仍然不起作用,那么规范是错误的。)
一旦你有了规范,就可以更容易地回答有关如何设计一端或另一端的问题。
我还强烈建议不围绕硬件细节设计网络协议。至少,并非没有经证实的性能问题。
答案 2 :(得分:2)
这取决于您是否可以确定连接两端的系统是否同质。如果您确定(我们大多数人都不可能),那么您可以采取一些捷径 - 但您必须意识到它们是捷径。
struct something some;
...
if ((nbytes = write(sockfd, &some, sizeof(some)) != sizeof(some))
...short write or erroneous write...
和类似的read()
。
但是,如果系统可能有所不同,那么您需要确定数据的正式传输方式。您可能会对数据进行线性化(序列化) - 可能与ASN.1类似,或者更简单地使用可以轻松重读的格式。为此,文本通常是有益的 - 当您可以看到出现问题时,它更容易调试。如果不这样做,您需要定义传输int
的字节顺序,并确保传输遵循该顺序,字符串可能会获得一个字节数,然后是适当的数据量(考虑是否传输一个终端是否为null),然后浮点数的一些表示。这更加繁琐。编写序列化和反序列化函数来处理格式化并不是那么困难。棘手的部分是设计(决定)协议。
答案 3 :(得分:1)
您可以将union
与您要发送的结构和数组一起使用:
union SendSomething {
char arr[sizeof(struct something)];
struct something smth;
};
这样你就可以发送和接收arr。当然,您必须关注字节序问题,并且sizeof(struct something)
可能因机器而异(但您可以使用#pragma pack
轻松克服此问题)。
答案 4 :(得分:1)
为什么你会在那里有好的和快速的序列化库(如Message Pack为你完成所有艰苦的工作)时这样做,作为奖励他们为你提供套接字协议的跨语言兼容性?
使用Message Pack或其他序列化库来执行此操作。
答案 5 :(得分:1)
通常,序列化带来了几个好处,例如:通过线路发送结构的比特(例如fwrite
)。
char b[80];
代替char b[64];
通常,会生成序列化例程。甚至在20年前,RPCXDR已经存在,并且XDR序列化原语仍然存在于许多libc中。
答案 6 :(得分:0)
Pragma pack用于另一端的struct的二进制兼容性。 因为发送结构的服务器或客户端可能使用其他语言编写,也可能与其他c编译器或其他c编译器选项一起构建。
序列化,据我所知,正在从你的struct生成字节流。当您在套接字中编写struct时,您将进行序列化。
答案 7 :(得分:0)
Google协议缓冲区为该问题提供了一个很好的解决方案。在这里{{3}}
根据有效负载的结构创建一个.proto文件,并将其另存为 payload.proto
Controller
public function lodging() {
$url = 'https://vkysten-api.bookingstudio.dk/rest/v1Admin/lodgings.xml';
$username = 'xxxxxxxxxxxxxxx';
$headers = array( 'Authorization' => 'Basic ' . base64_encode( "$username" ) );
$response = wp_remote_get( $url, array( 'headers' => $headers ) );
$body = wp_remote_retrieve_body($response);
$data = simplexml_load_string($body);
return $data;
}
}
Frontpage with the link
<div class="huse">
<?php $i = 0; ?>
@foreach ($lodging->Lodging as $lodge)
@php
$i++;
$images = $lodge->Images->Image['Location'];
$address = $lodge['Address'];
$postalcode = $lodge['PostalCode'];
$city = $lodge['City'];
$items = array();
foreach($lodge->Images->Image as $item) {
$items[] = $item;
};
usort ($items, function($a, $b) {
return strcmp($a['SortOrder'], $b['SortOrder']);
});
@endphp
@if (!empty($items))
<div class="lodging-items">
@foreach ($items as $item)
<a href="feriehuse/?hus={{$address}}">
@if ($item['SortOrder']==0)
<div class="lodge-image">
<img src="{{$item['Location']}}" Width="400" Height="300" alt="">
<div class="lodge-address">{{$address}} | {{$postalcode}} {{$city}}</div>
</div>
@endif
@endforeach
<div class="frontlodge">{{$lodge->Localizations->Localization['Title']}}</div>
</a>
</div>
@endif
<?php if ($i === 8) break; ?>
@endforeach
</div>
Single page
<?php $item = $lodging->Lodging['Address']; ?>
@foreach ($lodging as $item)
{{$item['Address']}}
@endforeach
使用
编译.proto文件syntax="proto3"
message Payload {
int32 age = 1;
string name = 2;
} .
这将在您的目录中创建头文件 payload.pb-c.h 及其相应的 payload.pb-c.c 。
创建您的 server.c 文件,并包含protobuf-c头文件
protoc --c_out=. payload.proto
在接收方 client.c
#include<stdio.h>
#include"payload.pb.c.h"
int main()
{
Payload pload = PLOAD__INIT;
pload.name = "Adam";
pload.age = 1300000;
int len = payload__get_packed_size(&pload);
uint8_t buffer[len];
payload__pack(&pload, buffer);
// Now send this buffer to the client via socket.
}
确保使用 -lprotobuf-c 标志编译程序
....
int main()
{
uint8_t buffer[MAX_SIZE]; // load this buffer with the socket data.
size_t buffer_len; // Length of the buffer obtain via read()
Payload *pload = payload_unpack(NULL, buffer_len, buffer);
printf("Age : %d Name : %s", pload->age, pload->name);
}