簡體   English   中英

使用 recv() 確定數據包大小的最佳方法是什么?

[英]What is the best way to determine packet size with recv()?

一般而言,對套接字編程和 C 非常陌生。 我正在嘗試編寫一個基本程序來在兩台機器之間發送和接收數據。 我知道recv不會立即獲取您的所有數據——您基本上必須循環它,直到它讀取了整個消息。

代替只是在兩台機器上設置限制,我在客戶端創建了一個簡單的Message結構:

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 結構的大小相同——但假設它不是......) .

然后我像這樣從緩沖區中提取Message.length

size_t messagelength;
messagelength = *((size_t *) &buffer);

然后我在received < messagelength時將recv循環到緩沖區中。 這有效,但我不禁覺得它真的很丑,而且感覺很糟糕。 (特別是如果第一個recv調用讀取的值小於sizeof(size_t)或者機器是不同的位架構,在這種情況下 size_t 轉換將不起作用..)。 有一個更好的方法嗎?

你有一個固定大小的消息,所以你可以使用這樣的東西:

#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);
}

筆記:

  • size_t不會在任何地方都一樣,所以我切換到uint32_t

  • 我獨立讀取字段,因為結構內的填充可能因實現而異。 他們也需要以這種方式發送。

  • 接收者用來message.length的信息填充message.length ,但實際上並沒有使用它。

  • 惡意或有問題的發件人可能會為message.length提供一個太大的值,如果它不驗證它會使接收器崩潰(或更糟)。 contents 如果這是預期的,它可能不會以 NUL 終止。


但是如果長度不固定呢? 然后發送者需要以某種方式傳達讀者需要閱讀多少。 一種常見的方法是長度前綴。

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

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

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

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

Message *message = malloc(sizeof(Message)+contents_size);
if (!message) {
   perror("malloc");
   exit(EXIT_FAILURE);
}

message->length = contents_size;

bytes_read = recv_fixed_amount(sockfd, &(message->contents), contents_size);
if (bytes_read < 0) {
   perror("recv");
   exit(EXIT_FAILURE);
}

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

筆記:

  • message->length包含message->contents的大小而不是結構的大小。 這更有用。

另一種方法是使用哨兵值。 這是一個告訴讀者消息結束的值。 這就是終止 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();      
     }
 }

使用哨兵值的優點是不需要提前知道消息的長度(因此發送者可以在完全創建之前開始發送它。)

缺點與 C 字符串相同:消息不能包含標記值,除非使用某種形式的轉義機制。 在這與閱讀器的復雜性之間,您可以看到為什么長度前綴通常比標記值更受歡迎。 :)


最后,對於要在完全創建之前開始發送的大消息,有一個比哨兵值更好的解決方案:長度前綴塊的序列。 一直讀取塊,直到遇到大小為 0 的塊,表示結束。

HTTP 支持長度前綴消息(以Content-Length: <length> header 的形式)和這種方法(以Transfer-Encoding: chunked header 的形式)。

有兩種方法可以做到這一點......

1.) 使用二進制同步協議。 (使用 STX - 文本開始和 ETX - 文本結束)用於識別文本開始和結束。

2.) 在 Data 的開頭附加正在發送的數據的字節數。 套接字將讀取這些字節數並獲取要從套接字接收的字節數。 然后讀取所有數據並獲取所需的數據量。

嗯……好像很難……?? 讓我給你舉個例子。

需要發送的實際數據:ABCDEFGHIJ

新數據格式:0010ABCDEFGHIJ

服務器端需要的數據:ABCDE

recv 函數將讀取前 4 個字節以獲取實際數據的字節數(在循環中直到獲得 4 個字節):

int received1= recv(ioSock, recvbuf, 4, 0);

根據上述情況,'recvbuf' 將 0010 轉換為整數將給出值 '10',它可以存儲在某個整數變量中。 所以我們有:

int toReadVal = 10

現在我們只需要在下一次 recv 調用中讀取這 10 位數字:

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

最后,我們將 recvbuf1 的值設為 ABCDEFGHIG。 現在您可以根據您的要求截斷該值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM