使用C在套接字编程中创建数据包以及发送和接收数据包的正确方法

时间:2011-07-22 03:47:49

标签: c sockets networking network-programming

我写了一个小的客户端 - 服务器代码,我在其中发送从客户端到服务器的整数和字符。所以我知道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)

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

3 个答案:

答案 0 :(得分:4)

指针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是可靠的传输协议。

答案 1 :(得分:4)

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

    [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. 有效负载中的可变长度数据
    2. 如果您的结构有字符串,则应始终在字符串之前附加字符串的大小。

      1. 字节序
      2. 如果要将数据包发送到在不同机器上运行的两个不同应用程序,则需要事先商定如何表示字节数,即是先发送MSB还是先发送LSB。

答案 2 :(得分:3)

直接将struct写入网络是诱人干净,简单,整洁......不幸的是,错误。 (这并不能阻止很多人,包括许多应该知道更多的人,无论如何都要这样做。)

这个 1 有几个原因:

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

正确的方法是使用一个名为序列化的过程:对于要通过网络发送的每个数据结构,你有一个功能,它将内容规范化并将它们打包成一个char缓冲区以定义的格式。这包括将整数转换为特定的字节序(像htonl()这样的函数在这里很有用)。还有一个相应的函数可以将char缓冲区解压缩到接收方使用的struct形式。

现有几个用于序列化的代码库 - 例如,Google的Protocol Buffers

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


1。预见到一些反对意见:是的,大多数问题都有解决方法。但这就是它们的原因:通常依赖于编译器特定功能的解决方法,取消了大多数简单优势,但仍然不是完全可靠的。