【问题标题】:A smart way to send unknown length string in a struct over TCP? (C, Linux)通过 TCP 在结构中发送未知长度字符串的聪明方法? (C,Linux)
【发布时间】:2012-11-08 15:30:47
【问题描述】:

我正在 Linux 上用 C 语言实现一个简单的服务器/客户端。 可以说,作为客户端,我想询问服务器是否有特定文件。 我希望能够将我的查询发送到结构内的服务器(我更熟悉此操作的问题,但它是家庭作业)。

我的问题是,当使用结构时,我无法为文件名预先分配内存。 所以一个结构体:

struct{
int operation;
char *fileName;
long fileSize;
};

对我没有帮助,因为即使我为 fileName 分配内存,sizeof(struct) 保持不变 (sizeof(char*)+sizeof(fileSize)) 所以我无法将其发送到服务器。

希望我在这里很清楚......我的问题是如何使用 struct 向服务器发送一个未知大小的字符串。 (一种选择是提前发送 fileSize,然后发送该大小的 char*,但我不想这样做)。

谢谢!

【问题讨论】:

  • 为什么有字符串就不能计算大小? sizeof(struct)+fileSize?
  • 你的fileSize 是什么?这是fileName指向的数据的字节大小,还是文件大小?

标签: c networking tcp struct char


【解决方案1】:

不确定我是否理解您的问题。但这是一种仍然使用您的结构并在最后打包文件名的方法。这里重要的是,您需要为结构 + 结构后隐藏的文件名数据分配足够的字节。

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct
{
    // put any data you want here
    // ...
    size_t fileNameLength;
    char fileName[0]; // fake array, used to access extra bytes allocated after the struct
} FileInfo;

int main (int argc, char **argv)
{
    FileInfo *fi = NULL;

    // The fake array has size 0 !
    printf ("sizeof FileInfo is %d\n", sizeof (FileInfo));

    // let's suppose you've guessed your filename
    char filename[] = "homework.txt";
    size_t filename_length = strlen (filename); // this is the length with no NULL byte
    printf ("filename length (without NULL byte) is %d\n", filename_length);

    fi = malloc (sizeof(FileInfo) + filename_length);
    fi->fileNameLength = filename_length;
    memcpy (&fi->fileName, filename, filename_length);

    printf ("filename is %.*s\n", fi->fileNameLength, fi->fileName);

    // To send your struct + filename, just use the same address (fi),
    // but don't use as length sizeof (FileInfo), but this length:
    size_t struct_total_length = sizeof (FileInfo) + fi->fileNameLength;
    return 0;
}

【讨论】:

  • 请注意,只有在发送和接收机器具有相同的基本 CPU 结构以便编译器在两者上创建相同长度和字节序的字段时,这才会在 TCP 上工作。
【解决方案2】:

您不希望通过网络将结构从一台机器发送到另一台机器以及潜在的字节序问题,您还可能面临这样的问题,即一台机器上的结构布局可能与另一台机器上的结构布局不同不同的平台。

你想要的是:

  Client side                                     Server side

将struct转为数据流---(1)---->将数据流转为struct

然后你只需为数据流的语法定义契约 (1) 定义文件名前面有它们的长度似乎是合理的 或者发送文件名 null 终止。

正如 codewarrior 所说,硬连接最大文件名并不是一个好主意。

【讨论】:

  • 请阅读问题“我希望能够将我的查询发送到结构内的服务器(我更熟悉此操作的问题,但它用于家庭作业)。”尽管如此,您绝对正确,在没有任何序列化的情况下在 struct 内部发送数据并不是一个好主意。
【解决方案3】:

您可以将发送拆分为 3 个命令 - 操作 (int)、文件名长度 (int)、文件名 (0..n 字符)。

typedef struct Cmd{
    int operation;
    char *fileName;
    long fileSize; /* not required here but was in the question */
} Cmd;

Cmd cmd;
...
send(sockfd, &cmd.operation, sizeof(cmd.operation), 0);
size_t len = strlen(cmd.fileName);
send(sockfd, &len, sizeof(len), 0);
send(sockfd, fileName, len, 0);

如果您的作业要求您使用单个发送命令,请为所有三个分配一个足够大的缓冲区,然后复制到其中。

【讨论】:

  • 好的,这是“绕过”解决方案。但无论如何我都会使用它。这可能是明智之举。也感谢 codewarrior!
  • INT 将(必然)以大端方式发送。它们将以发送主机存储它们的任何形式发送。 send() 只知道原始字节。 “大端网络顺序”是协议实现使用的约定——用户数据是您选择的任何内容。此外,对于 TCP,不能保证即使是 2 字节长度的字段也会在单个 'recv()' 调用中被接收到。
  • @BrianWhite 谢谢,我已经删除了不正确的大端声明。我同意不能保证单个 recv 会返回什么;我看不到我建议的其他地方。
  • @simonc,不,您没有另外建议。我在同一个回复中将两个不相关的 cmets 分组(一个给你,一个给 OP)。很抱歉造成混乱。
【解决方案4】:

我认为在您的情况下,没有比先发送文件长度然后发送文件名更好的解决方案了。

你也可以使用下面的结构(不预先发送大小)

#define LONG_ENOUGH_FOR_FILE_NAME 128 //this defined to what you need
struct{
  int operation;
  char fileName[LONG_ENOUGH_FOR_FILE_NAME];
  long fileSize;
};

但这是一个非常脆弱的解决方案。您的文件名可能长于

LONG_ENOUGH_FOR_FILE_NAME

缓冲区大小,这会导致一些严重的问题。

【讨论】:

    【解决方案5】:

    您需要“序列化”数据。编码长度,写入长度,编码字符串(这通常是空操作),写入字符串。

    发件人:

    uint32_t net_length = htonl(strlen(filename)+1);  // include trailing NUL
    send(socket, &net_length, 4);
    send(socket, filename, strlen(filename));
    

    接收者(伪代码):

    char buffer[whatever]
    until have at least 4 bytes in buffer {
      got = recv(socket, buffer+have, sizeof(buffer)-have);
      have += got
    }
    uint32_t length = ntohl(*(uint32_t*)buffer)
    until have at least length+4 bytes in buffer {
      got = recv(socket, buffer+have, sizeof(buffer)-have);
      have += got
    }
    char* filename = &buffer[4]
    

    这使用了一个不理想的固定大小的接收缓冲区,但它说明了这个想法。您最好只读取到长度(4 个字节),然后使用malloc() 获得足够大的缓冲区来保存文件名并将其余部分直接读入其中。

    【讨论】:

      猜你喜欢
      • 2010-11-15
      • 2012-08-10
      • 2018-06-26
      • 1970-01-01
      • 2016-01-11
      • 1970-01-01
      • 1970-01-01
      • 2010-12-16
      • 1970-01-01
      相关资源
      最近更新 更多