解析通过 C 中包含 2 个字符串的套接字发送的单个消息



我正在尝试读取包含 2 个字符串的消息。此消息包含 2 个字符串,可以是任何内容,并通过套接字发送。

请注意,我在 Ubuntu 环境中使用 C。

消息的格式为,在单个void* buffer

[string1][string2]

我想一旦它们到达,我就可以将它们分开,使用"\0"来确定在哪里拆分它们。我正在使用一个函数来读取字符串并且它有点工作,但我不断收到 Valgrind 的投诉,我不明白为什么。

我将使用一个仅从缓冲区读取 1 个字符串的示例,但我提到了该策略,因为我无法将消息放入char* buffer中。我需要该函数从更复杂的缓冲区中提取字符串。

一切从这样开始:

void* buffer = malloc(msgSize * sizeof(char)); //the message size is properly calculated to include the '' at the end
char* instanceId = malloc(msgSize * sizeof(char));
if(recv(socket_desc, (void*) buffer, msgSize * sizeof(char), MSG_WAITALL) <= 0) {
log_error(logger, "Message failed.");
return;
}
bufferToString(buffer, &instanceId, 0);
bufferToString2(buffer, instanceId, 0);

我做了几次尝试使bufferToString工作,如您所见...当然,我不会同时调用它们,但我想分享这些行,以防我在那里犯错。

尝试 #Number 1:逐个字符

int bufferToString(void* buffer, char** string, int startPtr) {
//startPtr can be used to read strings that are in the middle of a buffer
char a;
int thisStringPtr = 0; 
do {
a = *(char*) (buffer + startPtr);
(*string)[thisStringPtr] = a;
startPtr++;
thisStringPtr++;
} while (a != '');
return startPtr; //return end position to use for extracting more values later

}

这个抱怨:

==23047== Invalid read of size 1
==23047==    at 0x403A27A: bufferToString (buffer.c:16)
==23047==    by 0x804A0C2: handleHiloInstancia (coordinador.c:232)
==23047==    by 0x8049C54: procesarConexion (coordinador.c:85)
==23047==    by 0x4066294: start_thread (pthread_create.c:333)
==23047==    by 0x41650AD: clone (clone.S:114)
==23047==  Address 0x423bc8a is 0 bytes after a block of size 10 alloc'd
==23047==    at 0x402C17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==23047==    by 0x804A063: handleHiloInstancia (coordinador.c:225)
==23047==    by 0x8049C54: procesarConexion (coordinador.c:85)
==23047==    by 0x4066294: start_thread (pthread_create.c:333)
==23047==    by 0x41650AD: clone (clone.S:114)

bufferToString 的第 16 行是do语句中的第一行。

尝试 2:投射和复制

int bufferToString2(void* buffer, char* string, int startPtr) {
strcpy(string, (char*) (buffer + startPtr));
return (strlen(string) + 1)*sizeof(char);
}

无论是否使用 +startPtr,这都会导致略有不同的问题:

==23190== Invalid read of size 1
==23190==    at 0x402F489: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==23190==    by 0x403A1E3: bufferToString2 (buffer.c:3)
==23190==    by 0x804A0C1: handleHiloInstancia (coordinador.c:232)
==23190==    by 0x8049C54: procesarConexion (coordinador.c:85)
==23190==    by 0x4066294: start_thread (pthread_create.c:333)
==23190==    by 0x41650AD: clone (clone.S:114)
==23190==  Address 0x423bc8a is 0 bytes after a block of size 10 alloc'd

我尝试了其他一些组合(例如使用 char** 字符串和 bufferToString2 中所有必需的修改),但我不断收到类似的错误消息。我没有看到什么?

更新:消息的发送方式:

int bufferSize;
void* buffer = serializePackage(HANDSHAKE_INSTANCE_ID ,instancia_config->nombre, &bufferSize );
printf("Buffer size: %i - Instancia Name = %s - Socket num: %in", bufferSize, instancia_config->nombre, socket_coordinador); //this shows right data
if (send(socket_coordinador,buffer,bufferSize, 0) <= 0) {
log_error(logger, "Could not send ID.");
endProcess(EXIT_FAILURE);
}

instancia_config->nombre 的类型为 char*

void* serializePackage(int codigo,char * mensaje, int* tamanioPaquete){
int puntero = 0;
int length = strlen(mensaje);
int sizeOfPaquete = strlen(mensaje) * sizeof(char) + 1 + 2 * sizeof(int);
void * paquete = malloc(sizeOfPaquete);

memcpy((paquete + puntero) ,&codigo,sizeof(int));
puntero += sizeof(int);
memcpy((paquete + puntero),&length,sizeof(int));
puntero += sizeof(int);
memcpy((paquete + puntero),mensaje,length * sizeof(char) + 1);
*tamanioPaquete = sizeOfPaquete;
return paquete;
}

你有正确的 src 和 dst 吗?C 中内存服务的目标(或目标,如果您愿意)是第一个参数,因此: strcpy(string, buffer) 将缓冲区复制到字符串中。(https://www.tutorialspoint.com/c_standard_library/c_function_strcpy.htm)

但是:bufferToString2 被调用时,缓冲区作为第一个参数(在本例中它是源)。

在第一种情况下,正如所指出的,你不能在空白*上做算术,因为数学试图转到第 N 个元素,如果你说: *(x + N) 如果 x 是"void",它就没有大小,因此第 N 个元素没有意义。

由于表面上还没有最终解决问题的答案,并且在这个问题上没有进一步的进展,我将总结我在评论中部分提到的内容,以至少提供从哪里开始寻找问题的提示:

不幸的是,缺少很多信息来认真对待这个问题。

例如,您使用哪种套接字来发送/接收消息? 是管道还是网络插座 - 那么你使用哪种传输方式?

如何确保收到整封邮件? 您至少应该检查recv的返回值(恰好是收到的八位字节的数量),并检查是否有预期的长度。

收到消息后,按原样转储消息 - 如果您不熟悉使用调试器,像这样的函数可以作为启动器:

void dump(const char* buffer, size_t length) {
for(size_t i = 0; i < length; ++i) {
printf("%x", buffer[i] & 0xff);
}
printf("n");
}

并在ssize_t received = recv(...)后在代码中调用它,例如dump(buffer, received).

此外,您没有提供在第一个代码片段中计算msgSize的方式 - 您如何保证传递给serializePacketmensaje中的字符串不超过msgSize

然后,在serializePacket中,创建一个填充如下的缓冲区:

| codigo (int) | length (int) | mensaje ( = terminal zero) |

但是你在另一端读回来的只是一个 C 字符串 - 你也应该读 2 个整数,不是吗?

然后,这个序列化还有另一个问题:即使你读回 2 个整数,你也会有一段完全不可移植的代码,并且只有在发送方和接收方都以完全相同的方式和相同的位顺序表示int架构上时才有效。 如果在 32 位系统上运行发送方,在 64 位系统上运行接收方, 您写入 4 个字节整数,同时尝试读回 8 个字节整数。更好地使用 具有精确定义宽度的类型(如从inttypes.huint32_t),并使用例如ntohl(3)/htonl(3).

最后,我想添加一些注释来提高代码质量,这可能会防止很多潜在的错误:

以您的bufferToString为例:

  1. 你以char**的身份通过了string——为什么?
  2. 使用适当的数据类型 - 不要在处理非负大小时使用带符号int- 改用标准类型size_t
  3. 除非真的有必要,否则不要使用void*作为类型 - 你会丢失很多编译时错误检查。C 会自动从作业中的void*转换。因此,强烈建议将buffer声明为char*

例如,这个函数可以严格简化:

size_t bufferToString(const char* buffer, char* string, size_t offset) {
for(size_t i = offset; 0 != buffer[i]; ++i) {
string[i] = buffer[i];
}
return ++i;
}

我只能强调,网络代码往往难以调试 - 有各种外部错误源您无法真正监督。

希望这些提示有助于追踪问题的根本原因。

最新更新