Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用recv()确定数据包大小的最佳方法是什么?_C_Sockets_Send_Recv - Fatal编程技术网

使用recv()确定数据包大小的最佳方法是什么?

使用recv()确定数据包大小的最佳方法是什么?,c,sockets,send,recv,C,Sockets,Send,Recv,对socket编程和C语言来说是非常新的。我正在尝试编写一个基本程序,在两台机器之间发送和接收数据。我知道,recv不会一次获取所有数据——您必须循环它,直到它读取整个消息 我在客户端创建了一个简单的消息结构,而不只是在两台机器上设置限制: struct Message { size_t length; char contents[1024 - sizeof(size_t)]; } message; message.length = sizeof(struct Message)

对socket编程和C语言来说是非常新的。我正在尝试编写一个基本程序,在两台机器之间发送和接收数据。我知道,
recv
不会一次获取所有数据——您必须循环它,直到它读取整个消息

我在客户端创建了一个简单的
消息
结构,而不只是在两台机器上设置限制:

struct Message {
    size_t length;
    char contents[1024 - sizeof(size_t)];
} message; 
message.length = sizeof(struct Message);
message.contents = information_i_want_to_send;
当它到达服务器时,我将
recv
读入一个缓冲区:
received=recv(ioSock,&buffer,1024,0)
(它恰好与我的消息结构大小相同,但假设它不是…)

然后我从缓冲区中提取
Message.length
,如下所示:

size_t messagelength;
messagelength = *((size_t *) &buffer);
#include <errno.h>
#include <limits.h>

// Returns the number of bytes read.
// EOF was reached if the number of bytes read is less than requested.
// On error, returns -1 and sets errno.
ssize_t recv_fixed_amount(int sockfd, char *buf, size_t size) {
   if (size > SSIZE_MAX) {
      errno = EINVAL;
      return -1;
   }

   ssize_t bytes_read = 0;
   while (size > 0) {
      ssize_t rv = recv(sockfd, buf, size, 0); 
      if (rv < 0)
         return -1;
      if (rv == 0)
         return bytes_read;

      size -= rv;
      bytes_read += rv;
      buf += rv;
   }

   return bytes_read;
}
typedef struct {
   uint32_t length;
   char contents[1020];
} Message;

Message message;

ssize_t bytes_read = recv_fixed_amount(sockfd, &(message.length), sizeof(message.length));
if (bytes_read == 0) {
   printf("EOF reached\n");
   exit(EXIT_SUCCESS);
}

if (bytes_read < 0) {
   perror("recv");
   exit(EXIT_FAILURE);
}

if (bytes_read != sizeof(message.length)) {
   fprintf(stderr, "recv: Premature EOF.\n");
   exit(EXIT_FAILURE);
}

bytes_read = recv_fixed_amount(sockfd, &(message.content), sizeof(message.content));
if (bytes_read < 0) {
   perror("recv");
   exit(EXIT_FAILURE);
}

if (bytes_read != msg_size) {
   fprintf(stderr, "recv: Premature EOF.\n");
   exit(EXIT_FAILURE);
}
然后我将
recv
循环到缓冲区,同时
接收

这是可行的,但我忍不住觉得它真的很难看,而且很粗糙。(特别是如果第一个
recv
调用读取的值小于
sizeof(size\u t)
或者机器是不同的位体系结构,在这种情况下,size\u t cast将无法工作。)。有更好的方法吗?

有两种方法

1.) 使用二进制同步协议。(使用STX-文本开始和ETX-文本结束)标识文本开始和结束

(二) 附加在数据开头发送的数据字节数。套接字将读取这些字节数,并获取从套接字接收的字节数。然后读取所有数据并获得所需的数据量

嗯。。。看起来很难。。。??让我给你举个例子

需要发送的实际数据:ABCDEFGHIJ

新数据格式:0010ABCDEFGHIJ

服务器端需要的数据:ABCDE

recv函数将读取前4个字节,以获得实际数据的字节数(在循环中,直到获得4个字节):

根据上述情况,“recvbuf”将被0010转换为整数,其值为“10”,可存储在某个整数变量中。因此,我们:

int toReadVal = 10
现在我们只需要在下一次recv呼叫中读取以下10位数字:

int received= recv(ioSock, recvbuf1, toReadVal, 0);

最后,我们得到recvbuf1作为ABCDEFGHIG的值。现在,您可以根据需要截断值。

有两种方法可以做到这一点

1.) 使用二进制同步协议。(使用STX-文本开始和ETX-文本结束)标识文本开始和结束

(二) 附加在数据开头发送的数据字节数。套接字将读取这些字节数,并获取从套接字接收的字节数。然后读取所有数据并获得所需的数据量

嗯。。。看起来很难。。。??让我给你举个例子

需要发送的实际数据:ABCDEFGHIJ

新数据格式:0010ABCDEFGHIJ

服务器端需要的数据:ABCDE

recv函数将读取前4个字节,以获得实际数据的字节数(在循环中,直到获得4个字节):

根据上述情况,“recvbuf”将被0010转换为整数,其值为“10”,可存储在某个整数变量中。因此,我们:

int toReadVal = 10
现在我们只需要在下一次recv呼叫中读取以下10位数字:

int received= recv(ioSock, recvbuf1, toReadVal, 0);

最后,我们得到recvbuf1作为ABCDEFGHIG的值。现在,您可以根据需要截断该值。

您有一条固定大小的消息,因此可以使用如下内容:

size_t messagelength;
messagelength = *((size_t *) &buffer);
#include <errno.h>
#include <limits.h>

// Returns the number of bytes read.
// EOF was reached if the number of bytes read is less than requested.
// On error, returns -1 and sets errno.
ssize_t recv_fixed_amount(int sockfd, char *buf, size_t size) {
   if (size > SSIZE_MAX) {
      errno = EINVAL;
      return -1;
   }

   ssize_t bytes_read = 0;
   while (size > 0) {
      ssize_t rv = recv(sockfd, buf, size, 0); 
      if (rv < 0)
         return -1;
      if (rv == 0)
         return bytes_read;

      size -= rv;
      bytes_read += rv;
      buf += rv;
   }

   return bytes_read;
}
typedef struct {
   uint32_t length;
   char contents[1020];
} Message;

Message message;

ssize_t bytes_read = recv_fixed_amount(sockfd, &(message.length), sizeof(message.length));
if (bytes_read == 0) {
   printf("EOF reached\n");
   exit(EXIT_SUCCESS);
}

if (bytes_read < 0) {
   perror("recv");
   exit(EXIT_FAILURE);
}

if (bytes_read != sizeof(message.length)) {
   fprintf(stderr, "recv: Premature EOF.\n");
   exit(EXIT_FAILURE);
}

bytes_read = recv_fixed_amount(sockfd, &(message.content), sizeof(message.content));
if (bytes_read < 0) {
   perror("recv");
   exit(EXIT_FAILURE);
}

if (bytes_read != msg_size) {
   fprintf(stderr, "recv: Premature EOF.\n");
   exit(EXIT_FAILURE);
}
注:

  • message->length
    包含
    message->contents
    的大小,而不是结构的大小。这是非常有用的

另一种方法是使用sentinel值。这是一个告诉读者消息结束的值。这就是终止C字符串的NUL。这是更复杂的,因为你不知道有多少提前阅读。逐字节读取成本太高,因此通常使用缓冲区

 while (1) {
     extend_buffer_if_necessary();
     recv_into_buffer();
     while (buffer_contains_a_sentinel()) {
        // This also shifts the remainder of the buffer's contents.
        extract_contents_of_buffer_up_to_sentinel();
        process_extracted_message();      
     }
 }
使用sentinel值的优点是不需要事先知道消息的长度(这样发送者就可以在消息完全创建之前开始发送消息)

缺点与C字符串相同:除非使用某种形式的转义机制,否则消息不能包含sentinel值。在这一点和读取器的复杂性之间,您可以看出为什么长度前缀通常比sentinel值更受欢迎。:)


最后,对于要在完全创建之前开始发送的大型消息,有一个比sentinel值更好的解决方案:一系列长度前缀块。一个人一直在读块,直到遇到大小为0的块,表示结束


HTTP支持长度前缀消息(以
内容长度:
标题的形式)和此方法(以的形式)。

您有一个固定大小的消息,因此您可以使用如下内容:

size_t messagelength;
messagelength = *((size_t *) &buffer);
#include <errno.h>
#include <limits.h>

// Returns the number of bytes read.
// EOF was reached if the number of bytes read is less than requested.
// On error, returns -1 and sets errno.
ssize_t recv_fixed_amount(int sockfd, char *buf, size_t size) {
   if (size > SSIZE_MAX) {
      errno = EINVAL;
      return -1;
   }

   ssize_t bytes_read = 0;
   while (size > 0) {
      ssize_t rv = recv(sockfd, buf, size, 0); 
      if (rv < 0)
         return -1;
      if (rv == 0)
         return bytes_read;

      size -= rv;
      bytes_read += rv;
      buf += rv;
   }

   return bytes_read;
}
typedef struct {
   uint32_t length;
   char contents[1020];
} Message;

Message message;

ssize_t bytes_read = recv_fixed_amount(sockfd, &(message.length), sizeof(message.length));
if (bytes_read == 0) {
   printf("EOF reached\n");
   exit(EXIT_SUCCESS);
}

if (bytes_read < 0) {
   perror("recv");
   exit(EXIT_FAILURE);
}

if (bytes_read != sizeof(message.length)) {
   fprintf(stderr, "recv: Premature EOF.\n");
   exit(EXIT_FAILURE);
}

bytes_read = recv_fixed_amount(sockfd, &(message.content), sizeof(message.content));
if (bytes_read < 0) {
   perror("recv");
   exit(EXIT_FAILURE);
}

if (bytes_read != msg_size) {
   fprintf(stderr, "recv: Premature EOF.\n");
   exit(EXIT_FAILURE);
}
注:

  • message->length
    包含
    message->contents
    的大小,而不是结构的大小。这是非常有用的

另一种方法是使用sentinel值。这是一个告诉读者消息结束的值。这就是终止C字符串的NUL。这是更复杂的,因为你不知道有多少提前阅读。逐字节读取成本太高,因此通常使用缓冲区

 while (1) {
     extend_buffer_if_necessary();
     recv_into_buffer();
     while (buffer_contains_a_sentinel()) {
        // This also shifts the remainder of the buffer's contents.
        extract_contents_of_buffer_up_to_sentinel();
        process_extracted_message();      
     }
 }
使用sentinel值的优点是不需要事先知道消息的长度(这样发送者就可以在消息完全创建之前开始发送消息)

缺点与C字符串相同:除非使用某种形式的转义机制,否则消息不能包含sentinel值。在这一点和读取器的复杂性之间,您可以看出为什么长度前缀通常比sentinel值更受欢迎。:)


最后,对于要在完全创建之前开始发送的大型消息,有一个比sentinel值更好的解决方案:一系列长度前缀块。一个人一直在读c