创建一个数据包作为一个字符串,然后在 C 中提取它的字段

Creating a packet as a string and then extracted its fields in C

我需要实现自己的数据包以通过 UDP 发送。我决定通过发送一个字符缓冲区来完成此操作,该缓冲区具有序列号、校验和、大小以及数据包的数据(来自文件的字节)。我发送的字符串用分号分隔每个字段。然后,当我收到字符串(这是我的数据包)时,我想提取每个猫科动物,相应地使用它们(序列号、大小和校验和)并将字节写入文件。到目前为止,我已经编写了一个创建 100 个数据包的方法,我正在尝试提取字节并将其写入文件(我还没有在接收方中执行此操作,首先我正在测试发送方中的解析)。出于某种原因,写入我的文件的字节不正确,当我尝试打开它时出现 "JPEG DATATSTREAM CONTAINS NO IMAGE" 错误。

struct packetNode{
    char packet[1052]; // this is the entire packet data including the header
    struct packetNode *next;
};

这就是我创建数据包的方式:

//populate initial window of size 100

    for(i = 0; i < 100; i++){
      memset(&data[0], 0, sizeof(data));
      struct packetNode *p; // create packet node
      p = (struct packetNode *)malloc(sizeof(struct packetNode));
      bytes = fread(data, 1, sizeof(data), fp); // read 1024 bytes from file into data buffer
       int b = fwrite(data, 1, bytes, fpNew);

      printf("read: %d\n", bytes);
      memset(&p->packet[0], 0, sizeof(p->packet));
      sprintf(p->packet, "%d;%d;%d;%s", s, 0, numPackets, data); // create packet

      //calculate checksum
      int check = checksum8(p->packet, sizeof(p->packet));
      sprintf(p->packet, "%d;%d;%d;%s", s, check, numPackets, data); //put checksum in packet
      s++; //incremenet sequence number



      if(i == 0){
            head = p;
            tail = p;
            tail->next = NULL;
        }
        else{
            tail->next = p;
            tail = p;
            tail->next = NULL;
        }


    }
  fclose(fp);

这是我解析字节并将其写入文件的地方:

void test(){
 FILE *fpNew = fopen("test.jpg", "w");
 struct packetNode *ptr = head;
 char *tokens;


 int s, c, size;
 int i = 0;
 char data[1024];

  while(ptr != NULL){
    memset(&data[0], 0, sizeof(data));
    tokens = strtok(ptr->packet,";");
    s = atoi(tokens);
    tokens = strtok(NULL, ";");
    c =  atoi(tokens);
    tokens = strtok(NULL, ";");
    size = atoi(tokens);
    tokens = strtok(NULL, ";");

    if(tokens != NULL)
    strcpy(data, tokens);

    printf("sequence: %d, checksum: %d, size: %d\n", s,c,size);
    int b = fwrite(data, 1, sizeof(data), fpNew);

    ptr = ptr->next;
   i++;
  }

fclose(fpNew);
}

由于传输的是二进制数据,即 JPEG 流,因此不能将此数据视为字符串。最好全部使用二进制文件。例如,而不是

  sprintf(p->packet, "%d;%d;%d;%s", s, 0, numPackets, data); // create packet

你会做

  sprintf(p->packet, "%d;%d;%d;", s, 0, numPackets);
  memcpy(&p->packet[strlen(p->packet)], data, bytes);

但这会导致解析问题:我们需要更改此设置:

tokens = strtok(NULL, ";");

if(tokens != NULL)
strcpy(data, tokens);

像这样:

tokens += 1 + ( size < 10 ? 1 : size < 100 ? 2 : size < 1000 ? 3 : size < 10000 ? 4 : 5 );
memcpy(data, tokens, sizeof(data));

#二进制协议

使用二进制数据包更简单:

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

#pragma push(pack,1)
typedef struct Packet {
    int seq, maxseq, size;
    unsigned short cksum;
    unsigned char payload[]; 
} Packet;
#pragma pop(pack)

typedef struct PacketNode{
    struct PacketNode * next;
    Packet packet;
} PacketNode;


PacketNode * allocPacketNode(int maxPayloadSize) {
    void * ptr = malloc(sizeof(PacketNode) + maxPayloadSize); // FIXME: error checking
    memset(ptr, 0, sizeof(PacketNode) + maxPayloadSize);    // mallocz wouldn't cooperate
    return (PacketNode*) ptr;
}

PacketNode * prepare(FILE * fp, int fsize, int chunksize)
{
    PacketNode * head = allocPacketNode(chunksize);
    PacketNode * pn = head;
    int rd, seq = 0;
    int maxseq = fsize / chunksize + ( fsize % chunksize ? 1 : 0 );

    while ( ( rd = fread(pn->packet.payload, 1, chunksize, fp ) ) > 0 )
    {
        printf("read %d bytes\n", rd);
        pn->packet.seq = seq++;
        pn->packet.maxseq = maxseq;
        pn->packet.size = rd + sizeof(Packet);
        pn->packet.cksum = 0;
        pn->packet.cksum = ~checksum(&pn->packet, pn->packet.size);

        if ( rd == chunksize )
            pn = pn->next = allocPacketNode(chunksize);
    }
        
    return head;
}

int checksum(unsigned char * data, int len)
{
    int sum = 0, i;
    for ( i = 0; i < len; i ++ )
        sum += data[i];
    if ( sum > 0xffff )
        sum = (sum & 0xffff) + (sum>>16);
    return sum;
}

void test( PacketNode * ptr ) {
    FILE *fpNew = fopen("test.jpg", "w");

    while (ptr != NULL) 
    {
        printf("sequence: %d/%d, checksum: %04x, size: %d\n", 
            ptr->packet.seq,
            ptr->packet.maxseq,
            ptr->packet.cksum,
            ptr->packet.size - sizeof(Packet)
        );
        int b = fwrite(ptr->packet.payload, ptr->packet.size - sizeof(Packet), 1, fpNew);

        ptr = ptr->next;
    }

    fclose(fpNew);
}

void fatal( const char * msg ) { printf("FATAL: %s\n", msg); exit(1); }

int main(int argc, char** argv)
{
    if ( ! argv[1] ) fatal( "missing filename argument" );
    FILE * fp = fopen( argv[1], "r" );
    if ( ! fp ) fatal( "cannot open file" );
    fseek( fp, 0, SEEK_END );
    long fsize = ftell(fp);
    fseek( fp, 0, SEEK_SET );
    printf("Filesize: %d\n", fsize );

    test( prepare(fp, fsize, 1024) );
}

#pragma push(pack,1) 改变了编译器对齐结构字段的方式。我们希望它们紧凑,用于网络传输。使用 1 是字节对齐的。 #pragma pop(pack) 恢复 pack pragma 的先前设置。

校验和方法的注意事项

首先我们计算数据包中所有字节的总和:

    int sum = 0, i;
    for ( i = 0; i < len; i ++ )
        sum += data[i];

由于数据包使用无符号短整型(16 位,最大值 65535 或 0xffff)来存储校验和,我们确保结果适合:

    if ( sum > 0xffff )      // takes up more than 16 bits.

获取此 int 的低 16 位是使用 sum & 0xffff 完成的,屏蔽除低 16 位以外的所有内容。我们可以简单地 return 这个值,但是我们会丢失来自更高校验和位的信息。因此,我们将高 16 位添加到低 16 位。访问高 16 位是通过将 int 向右移动 16 位来完成的,如下所示:sum >> 16。这与 sum / 65536 相同,因为 65536 = 216 = 1 << 16.

        sum = (sum & 0xffff) + (sum>>16);    // add low 16 bits and high 16 bits

我应该注意到一次 network packet checksums are usually computed 2 个字节(或 'octets',因为他们喜欢在那里称呼它们)。为此,data 应转换为 unsigned short *,并且 len 应除以 2。但是! len 可能是奇数,所以在这种情况下我们需要特别注意最后一个字节。例如,假设最大数据包大小为偶数,并且 len 参数始终为 <= max_packet_size:

unsigned short * in = (unsigned short *) data;
if ( len & 1 ) data[len] = 0;   // make sure last byte is 0
len = (len + 1) / 2;

校验和方法的其余部分可以保持不变,除了它应该在 in 而不是 data 上运行。