创建一个数据包作为一个字符串,然后在 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
上运行。
我需要实现自己的数据包以通过 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
上运行。