如何准备 UDP 数据报以发送到服务器

Posted

技术标签:

【中文标题】如何准备 UDP 数据报以发送到服务器【英文标题】:How to prepare UDP datagrams for sending to server 【发布时间】:2015-05-28 08:49:22 【问题描述】:

我必须编写一个程序,将文件从 UDP 客户端发送到 UDP 服务器。

我通过这种连接发送数据没有问题,但另一件事是处理我们的协议规范。也许有人可以帮助我了解如何正确实现这些步骤:

第 1 步:

发送第一个包:数据数组的第一个元素,int 类型说明的值。

之后文件名大小(无路径)为unsigned short

之后文件名(无路径)为 C 字符串,没有 NULL 终止。

至少应该有unsigned integer的文件长度。

两种尺寸都应该按网络字节顺序发送!

所有这些信息都必须在一个单个数据报中。所以实际上我只有一个由客户端发送的unsigned char 数组。

我想我可以一个接一个地为这个数组分配任何东西。但不要认为这行得通。

第 2 步:

发送第二个包:类型规范为int 作为第一个元素。然后是给定文件的数据(我也将其保存为unsigned char 数组)。这要容易得多,因为我只有可以轻松放入 unsigned char 数组的信息。

所以,我认为,必须有一种方法来构建一个通用方法或类似的东西,它返回一个数据报,所以我可以填写我想要在这个数据报中拥有的所有数据类型,并让我的数据报准备好发送, 你知道?

如果有人知道我如何以正确的方式“准备”数据报,那就太好了——对我来说最重要的是:

我的数据报数组应该有哪种类型?要使用sendTo() 发送它,它必须是unsigned char 数组,对吧?

最好的问候,非常感谢!

【问题讨论】:

您应该查看执行此操作的软件包,例如 XDR。 【参考方案1】:

您可以使用结构来映射您的信息,例如:

struct Packet 
   unsigned short filnemanesize;
   char [MAX_LEN] filename;
   unsigned int   filesize;
   //etc.

可以直接通过套接字发送这个结构

send(mySocket, (void*) myStruct, sizeof(myStuct), 0);

但是编译器使用不同的成员打包,因此服务器会看到不同的数据。 (它对于完全相同的平台/编译器是安全的)。

一个常见的做法是,将你的结构序列化为一个字符数组,这是一个完整的例子:

#include <stdint.h>
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")
using namespace std;


#define MAX_FILENAME_SIZE 10
typedef struct 
    int32_t type;
    uint16_t fileNameSize;
    uint32_t extraPadding; //just some manual padding (example)
    char fileName [MAX_FILENAME_SIZE];
    uint32_t fileSize;
 Packet;

const size_t PACKET_RAW_SIZE = sizeof(int32_t) + sizeof(uint16_t) + (MAX_FILENAME_SIZE * sizeof(char)) + sizeof(uint32_t);

size_t serialize(unsigned char * const dst, const Packet * src) 

    /*  Protocoll Specification:
        [Type:4Byte][fileNameSize:2Byte][fileName:nByte][fileSize:4Byte]
    */


    uint16_t offset = 0;

    //type
    memcpy(dst + offset, &src->type, sizeof(src->type));
    offset += sizeof(src->type);

    //fileNameSize
    memcpy(dst + offset, &src->fileNameSize, sizeof(src->fileNameSize));
    offset += sizeof(src->fileNameSize);

    //fileName
    memcpy(dst + offset, &src->fileName, ntohs(src->fileNameSize));
    offset += ntohs(src->fileNameSize);

    //fileSize
    memcpy(dst + offset, &src->fileSize, sizeof(src->fileSize));
    offset += sizeof(src->fileSize);

    return offset;


int main(int argc, _TCHAR* argv[])

    unsigned char buffer[PACKET_RAW_SIZE];
    for (int i = 0; i < PACKET_RAW_SIZE; buffer[i++] = 0);  //nice visual effect, not nessasarry
    Packet myPacket;
    myPacket.type = htonl(0xAFFEAFFE);                  
    myPacket.fileNameSize = htons(4);                       //convert 4 to network byte order as required
    myPacket.fileSize = htonl(42);
    myPacket.fileName[0] = 'T';                         
    myPacket.fileName[1] = 'E';
    myPacket.fileName[2] = 'S';
    myPacket.fileName[3] = 'T';

    myPacket.extraPadding = 0xFFFFFFFF;                     // better visual effect

    cout << "struct Packet size: " << sizeof(Packet) << endl;
    cout << "PACKET_RAW_SIZE: " << PACKET_RAW_SIZE << endl;

    size_t bufferSize = serialize(&buffer[0], &myPacket);

    cout << "myPacket: " << endl;
    for (int i = 0; i < sizeof(myPacket); i++) 
        unsigned char * ptr = (unsigned char *) &myPacket;
        cout << std::showbase << std::hex << "Offset: " << i * sizeof(unsigned char) << "\t Value: " <<  (uint16_t)*(ptr + i) << "\t" << *(ptr + i) << endl;
    
    cout << endl;

    cout << "buffer: " << endl;
    for (int i = 0; i < bufferSize; i++) 
        cout << std::showbase << std::hex << "Offset: " << i * sizeof(unsigned char) << "\t Value: " << (uint16_t)buffer[i] << "\t" << buffer[i] << endl;
    

    cout << endl;

    return 0;


/* 
    Program-Output:

    struct Packet size : 28
    PACKET_RAW_SIZE : 20
    myPacket :
    Offset : 0        Value : 0xaf    »
    Offset : 0x1      Value : 0xfe    ■
    Offset : 0x2      Value : 0xaf    »
    Offset : 0x3      Value : 0xfe    ■
    Offset : 0x4      Value : 0
    Offset : 0x5      Value : 0x4     ♦ 
    Offset : 0x6      Value : 0x9a    Ü
    Offset : 0x7      Value : 0xf     ☼
    Offset : 0x8      Value : 0xff
    Offset : 0x9      Value : 0xff
    Offset : 0xa      Value : 0xff
    Offset : 0xb      Value : 0xff
    Offset : 0xc      Value : 0x54    T
    Offset : 0xd      Value : 0x45    E
    Offset : 0xe      Value : 0x53    S
    Offset : 0xf      Value : 0x54    T
    Offset : 0x10     Value : 0x52    R
    Offset : 0x11     Value : 0x17    ↨
    Offset : 0x12     Value : 0x9f    ƒ
    Offset : 0x13     Value : 0xf     ☼
    Offset : 0x14     Value : 0x1     ☺
    Offset : 0x15     Value : 0
    Offset : 0x16     Value : 0
    Offset : 0x17     Value : 0
    Offset : 0x18     Value : 0
    Offset : 0x19     Value : 0
    Offset : 0x1a     Value : 0
    Offset : 0x1b     Value : 0x2a *

    buffer :
    Offset : 0        Value : 0xaf    »     | <= type
    Offset : 0x1      Value : 0xfe    ■     |
    Offset : 0x2      Value : 0xaf    »     |   
    Offset : 0x3      Value : 0xfe    ■     |   
    Offset : 0x4      Value : 0                 | <= fileNameSize
    Offset : 0x5      Value : 0x4     ♦         | (network byte order)
    Offset : 0x6      Value : 0x54    T             |   <= fileName
    Offset : 0x7      Value : 0x45    E             |
    Offset : 0x8      Value : 0x53    S             |
    Offset : 0x9      Value : 0x54    T             |
    Offset : 0xa      Value : 0                         | <= fileSize 
    Offset : 0xb      Value : 0                         | (network byte order)
    Offset : 0xc      Value : 0                         |
    Offset : 0xd      Value : 0x2a                      |

    */

可以看到,缓冲区只包含所需的数据。并且映射是规范中要求的。所有手动和编译器填充都不在缓冲区内。 htons/htonl 用于字节顺序转换。

更好的是某种文本序列化,例如 xml 或 json (imho)

【讨论】:

我认为几乎所有这些都是不好的建议。 OP基本上是在询问如何进行二进制序列化,这个答案列出了两种不好的方式(因为它们都依赖于平台细节),然后建议不要进行二进制序列化。二进制序列化没问题,最像 OP 提议的协议(例如 TFTP)都使用它。 对于完全相同的平台、编译器、编译器版本、编译器选项、周围的#pragmas、源代码版本,......换句话说,不是。 @David Schwartz 为什么解决方案 2 不好? 因为它与解决方案 1 相同,除了额外的按位复制步骤,这只是浪费空间。当然,这不是“常见做法”。 我认为在解决方案 2 中填充位没有问题,您能解释一下为什么解决方案 2 与 1 相同吗?

以上是关于如何准备 UDP 数据报以发送到服务器的主要内容,如果未能解决你的问题,请参考以下文章

UDP、NAT 和设置“连接”

将 UDP 数据从 Python 发送到 Javascript?

如何通过 NAT 通过公共 IP 发送 UDP 数据包?

如何在 Grafana 中配置警报以在例如之后没有新数据时发送警报30分钟?

发送和接收UDP数据[重复]

终端通过GPRS发送UDP数据到固定IP:端口号,socket recvfrom接收不到数据,但却有返回值,如何解决