在C语言中将原始字节转换为字符串的方法是什么?

3

我在使用C语言(通过HTTP)发送一些原始字节。目前我的做法如下:

// response is a large buffer
int n = 0; // response length
int x = 42; // want client to read x
int y = 43; // and y 

// write a simple HTTP response containing a 200 status code then x and y in binary format
strcpy(response, "HTTP/1.1 200\r\n\r\n");
n += 16; // status line we just wrote is 16 bytes long
memcpy(response + n, &x, sizeof(x));
n += sizeof(x);
memcpy(response + n, &y, sizeof(y));
n += sizeof(y);
write(client, response, n);

在JavaScript中,我可以使用以下代码读取此数据:
request = new XMLHttpRequest();
request.responseType = "arraybuffer";
request.open("GET", "/test");
request.onreadystatechange = function() { if (this.readyState === XMLHttpRequest.DONE) { console.log(new Int32Array(this.response)) } }
request.send();

这将按预期打印[42, 43]

但我想知道是否有更优雅的服务器端操作方式,例如:

n += sprintf(response, "HTTP/1.1 200\r\n\r\n%4b%4b", &x, &y);

在这里%4b是一个虚构的格式说明符,它表示:将那个地址中的4个字节复制到字符串中(它们将是“*\0\0\0”)。是否有像虚构的%4b这样的格式说明符来执行类似的操作?


7
sprintf 用于字符串,也就是以 \0 结尾的 char 数组。因此,它不适用于任意的二进制数据。 - Eugene Sh.
2
那些 memcpy 调用会使你的代码具有端序依赖性。 - jamesdlin
1
如果你想发送二进制数据,你应该使用一个明确定义的序列化格式。 - Barmar
1
另外需要注意的是:如果你正在输出原始二进制数据,应该添加一个 Content-Type: application/octet-stream 头部来指示这一点。 - tadman
1
然后,在客户端......如果您控制连接的两端,只需将其作为文本发出,不必担心字节序或编码。从文本到二进制的转换以及从四个字节扩展到更多字节的内容不会显著影响性能,但它将使数据交换在未来更容易处理。对于要发送的每个32位int,请使用sprintf(line,“%d \ r \ n”,data); write(client,line,strlen(line));。以任何您想要的方式解析它。 - Andrew Henle
显示剩余7条评论
2个回答

1
这是一个XY问题,你询问如何使用来解决问题,而不是直接询问如何解决问题。你实际的问题是如何使代码更加“优雅”。
没有特别的理由在单个写操作中发送数据 - 网络堆栈缓冲将确保数据被有效地分组:
static const char header[] = "HTTP/1.1 200\r\n\r\n" ;
write( client, header, sizeof(header) - 1 ) ;
write( client, &x, sizeof(x) ) ;
write( client, &y, sizeof(y) ) ; 

请注意,X和Y将以本地机器字节顺序编写,接收方可能会出现错误。更通用的做法是:
static const char header[] = "HTTP/1.1 200\r\n\r\n" ;
write( client, header, sizeof(header) - 1 ) ;

uint32_t nl = htonl( x ) ;
write( client, &nl, sizeof(nl) ) ;

nl = htonl( y ) ;
write( client, &nl, sizeof(nl) ) ; 

0
有类似虚构的 %4b 的格式说明符吗? 没有,你的方法是可以的。我建议使用snprintf并进行一些检查,以避免缓冲区溢出,例如添加static_assert(sizeof(int) == 4, "")检查平台是否使用big endian和类似环境和错误处理,并避免未定义行为检查。 话虽如此,您可以多次使用 %c printf说明符,例如 "%c%c%c%c",((char*)&x)[3], ((char*)&x)[2], ((char*)&x)[1], ((char*)&x)[0]来打印4个字节。您可以将其包装在宏中并执行:
#include <stdio.h>

#define PRI_BYTES_4  "%c%c%c%c"
#define ARG_BYTES_BE_4(var) \
    ((const char*)&(var))[3], \
    ((const char*)&(var))[2], \
    ((const char*)&(var))[1], \
    ((const char*)&(var))[0]

int main() {
    int var = 
        'l' << 24 |
        'a' << 16 | 
        'm' << 8 |
        'e';
    printf("Hello, I am " PRI_BYTES_4 ".\n",
        ARG_BYTES_BE_4(var));
    // will print `Hello, I am lame.`
}

int var = 'l' << 24 | ... 的风险在于 l 实际上是一个 int 值,因此进行左移可能导致溢出,从而造成未定义行为。使用 uint32_t var = ( ( uint8_t ) 'l' ) << 24 | ... 更好些。 - Andrew Henle
1
@AndrewHenle ( ( uint8_t ) 'l' ) << 24'l' << 24 一样存在溢出的风险。你是想要 ( ( uint32_t ) 'l' ) << 24 吗? - chux - Reinstate Monica
1
@chux-ReinstateMonica 是的。哎呀!我会辩称今天非常漫长... - Andrew Henle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接