【问题标题】:Proper way to create packets along with sending and receiving them in socket programming using C使用 C 在套接字编程中创建数据包以及发送和接收数据包的正确方法
【发布时间】:2011-07-22 03:47:48
【问题描述】:

我编写了一个小的客户端-服务器代码,其中我将整数和字符从客户端发送到服务器。所以我知道 C 中套接字编程的基础知识,比如要遵循的步骤等等。现在我想创建一个数据包并将其发送到我的服务器。我以为我会创建一个结构

    struct packet  
    { 
    int srcID;
    long int data;
    .....
    .....
    .....
    };
    struct packet * pkt;

在执行 send() 之前,我想我会使用在数据包内写入值

   pkt-> srcID = 01
   pkt-> data = 1 2 3 4 

我需要知道我是否在正确的道路上,如果是,那么我可以使用发送

      send(sockfd, &packet, sizeof(packet), 0)

用于接收

    recv(newsockfd, &PACKET, sizeof(PACKET), 0)

我刚开始使用网络编程,所以我不确定我是否走在正确的道路上。如果有人能以任何形式(理论、示例等)指导我提出我的问题,那将是非常有帮助的。提前致谢。

【问题讨论】:

  • 是的,你在正确的轨道上。但是,如果要具有前瞻性,则不能假设发送方和接收方是相同的架构……因此,您要认真考虑如何在线上对数据进行编码。搜索“序列化”。此外,recv 可以返回比您请求的字节少的字节,因此您始终需要在循环中调用它,直到获得所需的所有字节。
  • 感谢您的回复....将记住您的提示。
  • @Nemo:这取决于传输协议。在 TCP 中,是的 recv() 可能会返回更少的字节,但在 UDP 中,recv() 会返回整个数据包。
  • 从昨晚开始,当我开始使用这个程序时,当我编译时,我得到了很多解引用错误,我刚开始使用C和C++,所以有点业余指针,我正在发布我的代码,你能解释一下我哪里出错了吗:

标签: c sockets networking network-programming


【解决方案1】:

指针pkt 未在您的应用程序中定义。你有两个选择: 1)将pkt声明为普通变量

 struct packet pkt;

 pkt.srcID = 01;
 ....
 send(sockfd, &pkt, sizeof(struct packet), 0);

2) 当您的数据包包含一个标头后跟一个有效负载时,第二种方法很有用:

 char buffer[MAX_PACKET_SIZE];
 struct packet *pkt = (struct packet *) buffer;
 char *payload = buffer + sizeof(struct packet);
 int packet_size;  /* should be computed as header size + payload size */

 pkt->srcID = 01;
 ...
 packet_size = sizeof(struct packet) /* + payload size */ ;

 send(sockfd, pkt, packet_size, 0);
 .... 

已更新(回答您的评论): 首先,您应该知道从 TCP 套接字接收可能不会提供整个数据包。您需要实现循环(如 Nemo 建议的那样)来读取整个数据包。由于您更喜欢第二个选项,因此您需要两个循环。第一个循环是读取数据包头以提取有效负载大小,第二个循环是读取数据。在 UDP 的情况下,您无需担心部分接收。这是一个示例代码(没有循环),其中 sockfd 是一个 UDP 套接字:

char buffer[MAX_PACKET_SIZE];
struct packet *pkt = (struct packet *) buffer;
char *payload = buffer + sizeof(struct packet);
int packet_size;  /* should be computed as header size + payload size */

.....
/* read the whole packet */
if (recv(sockfd, pkt, MAX_PACKET_SIZE, 0) < 0) {
    /* error in receiving the packet. It is up to you how to handle it */
}
/* Now, you can extract srcID as  pkt->srcID */
/* you can get data by processing payload variable */

记住: * 你需要实现其他用户提到的序列化 * UDP 是不可靠的传输协议,而 TCP 是可靠的传输协议。

【讨论】:

  • 感谢您的回复。我想选择第二个选项(标题+数据)。在接收端,当我想存储数据包的数据部分时,我可以做 recvd_data.srcID = myID;类似的东西,对吧??
  • 我在“GOOGLE”中寻找专门用于套接字编程的序列化概念......有没有好的指南......
【解决方案2】:
  1. 数据包结构

当您要通过网络发送数据时,您需要考虑使用固定大小的标头,后跟可变长度的负载。

[1 字节头] [可变字节有效载荷]

标头应该为您提供要发送的数据大小,以便接收方始终读取固定大小的标头,然后确定数据包长度并读取其余字节。

例如:

int nRet = recv(nSock,(char*)pBuffer,MESSAGE_HEADER_LENGTH,0);  
if (nRet ==MESSAGE_HEADER_LENGTH)               
{
   int nSizeOfPayload = //Get the length from pBuffer;
   char* pData = new Char(nSizeOfPayload );
   int nPayloadLen = recv(nSock, (char*)pData,nSizeOfPayload ,0); 
}
  1. 有效载荷中的可变长度数据

如果你的结构有字符串,你应该总是在字符串之前附加字符串的大小。

  1. 字节序

如果您要将数据包发送到在不同机器上运行的两个不同应用程序,您需要事先就如何表示字节达成一致,即您是要先发送 MSB 还是先发送 LSB。

【讨论】:

    【解决方案3】:

    直接将structs 写到网络上是非常干净、简单、整洁的……不幸的是,这是错误的。 (无论如何,这并不能阻止很多人,包括许多应该更了解的人这样做)。

    这有几个原因1

    • 数据表示。这包括各种类型的大小、字节顺序和浮点格式等问题。您不能假设这些在连接的另一端与在您的一端相同。它们因架构而异。
    • 结构布局。当 structs 的成员大小不均匀时,编译器通常会在它们之间添加不可见的填充 - 并且此填充的布局也因架构而异。位域是另一个结构布局变量。
    • 规范化。特别是,指针成员通过套接字发送毫无意义——它们对另一方没有任何意义。您必须发送 它指向的内容。这方面的另一个方面是发送整个数组,其中只有一部分填充了有意义的值 - 未初始化的成员可能会泄漏您不希望它们的内存内容(无论如何发送它们是浪费的)。
    • 部分读取。无论如何,您最终需要在读取时使用char * 指针,因为您可能必须在struct 中途恢复读取(例如,您的struct 可能有830 个字节长,但第一次读取仅返回500 个字节) .

    解决此问题的正确方法是使用称为序列化的过程:对于您要通过网络发送的每个数据结构,您都有一个函数可以规范化内容并将它们打包成一个char 定义格式的缓冲区。这包括将整数转换为特定的字节序(htonl() 之类的函数在这里很有用)。还有一个相应的函数将char 缓冲区解压缩到struct 表单中,用于接收端。

    有几个现有的序列化代码库 - 例如,谷歌的Protocol Buffers

    另一个可行的替代方法是将您的数据结构序列化为文本格式,例如 JSON。这对于故障排除非常有用,因为这意味着您的网络协议在某种程度上是人类可读的。


    1.预料到一些反对意见:是的,大多数问题都有解决方法。但这就是它们的本质:变通方法,通常依赖于编译器特定的功能,抵消了大部分简单性的好处,而且仍然不完全可靠。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-07-04
      • 1970-01-01
      • 1970-01-01
      • 2010-11-13
      • 1970-01-01
      • 2013-08-28
      相关资源
      最近更新 更多