如何创建自己的数据包并通过UDP发送?

3
我正在使用C语言编写自己的客户端-服务器应用程序,实现TFTP协议。在阅读了TFTP的RFC并制作了一个简单的套接字客户端-服务器应用程序后,我现在有些困惑,不知道如何创建必须为TFTP协议创建的特定数据包。
例如,WRQ数据包必须按照以下方式创建:
        2 bytes     string    1 byte     string   1 byte
        ------------------------------------------------
       | Opcode |  Filename  |   0  |    Mode    |   0  |
        ------------------------------------------------

这是从官方RFC中提取的内容。

我有一个.h文件,其中定义了所有数据包的结构,但我不确定是否做得正确,并且在网上也没有找到相关信息。

我为此数据包创建的结构体如下:

    struct WRQ {
      signed short int opcode; //2 bytes
      char * filename; // N bytes
    char zero_0; // 1 byte
      char * mode; // N Bytes
    char zero_1; // 1 byte
    };

我有两个疑问:

a) 当我执行sizeof(struct WRQ)时,它返回20个字节。这不是我想要的大小。为什么会发生这种情况?

b) 我应该如何定义字符串?因为我希望服务器接收字符串本身,并且我认为这样,它将接收客户端机器上字符串的指针。

我希望一切都清楚,你可以帮助我,因为我目前卡住了!


你可能会在这里找到关于以下问题的答案:https://dev59.com/dW035IYBdhLWcg3wJcjT - Peacelyk
问题:数据包中的字符串长度如何处理?它们是以空字符结尾的,有一个字节长度字段,还是固定长度的? - Duck
感谢Peacelyk的回答! - Javier Manzano
鸭子,字符串都是以空终止的。 - Javier Manzano
4个回答

2
以下代码(没有错误检查)是构建数据包并发送的一种可能的方法。请注意,它假设filenamemode都不为NULL。我不知道这是否是一个有效的假设。即使是有效的,实际代码中使用它们之前最好检查是否为NULL:
struct WRQ *p;
int packetLen;
char *buf;
char *pos;
int ret;

// compute packet length.  Start with fixed size data
packetLen = sizeof( p->opcode ) + sizeof( p->zero_0 ) + sizeof( p->zero_1 );
// This assumes (possibly incorrectly) that filename and mode are not null
packetLen += strlen( p->filename ) + 1;
packetLen += strlen( p->mode ) + 1;

// allocate the buffer
buf = malloc( packetLen ); 
pos = buf;

// and start filling it in
// I am assuming (but didn't study the RFC that it should be network byte order)
*(signed short int*)pos = htons( p->opcode );
pos += sizeof( p->opcode );
strcpy( pos, p->filename );
pos += strlen( p->filename ) + 1;
*pos = p->zero_0;
strcpy( pos, p->mode );
pos += strlen( p->mode ) + 1;
*pos = p->zero_1;

ret = send( s, buf, packetLen, flags );

free( buf );

1
你不能只是简单地把一个 char* 放在那里并期望它能工作,因为那是一个指针,你需要实际的字符数据出现在数据包中(在两个程序之间传递指针几乎永远不会起作用!)。由于数据包的文件名部分长度可变,因此您无法像尝试做的那样将整个数据包表示为 struct。相反,您可能应该通过按需连接片段的函数来动态生成数据包,例如使用具有此签名的函数:

vector<char> makePacket(uint16_t opcode, const char* filename, const char* mode);

我认为不能以那种方式表达,那么怎么办呢?我从未在 C 中使用过 vector<char> 类型... 我不知道如何使用它来确定数据包的字段... 谢谢! - Javier Manzano
抱歉,我说的是 vector<char>,但那是 C++ 的东西!如果你使用的是纯 C,那么该函数需要返回一个 C 字符串(可以让它分配并返回一个 char,调用者必须释放它,或者传入一个 char 缓冲区和大小--我会选择后者)。 - John Zwinck

1
在你的代码中,char * filename;是一个指向字符的指针。这意味着filename只占用4个字节。即使你的字符串长度为1000个字节,由于filename只是一个指针,它只包含你的字符串第一个字符的4个字节内存地址。
因此,你有两个解决方案:使用char filename[MAX_LENGTH]声明一个大小为MAX_LENGTH的字符串,并始终传递它。或者,你可以包含另一个字段,比如"filename_length",告诉你在读取文件名字段时要期望多少字节。
(上述内容实际上是错误的。filename可能不是4个字节长。filename将是sizeof(char*)字节长。在你的计算机上,这可能是4个字节。但是指针并不总是4个字节长,现在人们因为假设64位架构上的4个字节指针而遇到了很多麻烦。所以我只是说一下。请不要给我投反对票。)
回答你的第二个问题——为什么sizeof()是20字节?编译器会用额外的字节填充结构体的部分,以便每个成员都适合4字节边界内。编译器可以高效地处理“字”,而不是奇怪大小的结构体。(同样,“4”取决于架构。每个架构都有自己的“字”长度。)这被称为对齐。有一个很好的SO线程提供了更多细节:为什么结构体的sizeof不等于每个成员的sizeof之和?

0

C结构体不能具有可变大小。您必须将“filename”和“mode”定义为char field_name [preset size];或在运行时手动在内存缓冲区中构建结构体。

最后,如果您需要在C中实现TFTP,则可以肯定地说已经有人编写了它。例如,在各种Linux发行版中使用的是BSD许可的tftp-hpa


我会尝试在一个char * buff变量中完成它,谢谢! - Javier Manzano

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