簡體   English   中英

如何保證 write() 將通過套接字傳輸整個 int?

[英]How to guarantee that write() will transfer entire int over socket?

首先我在這里發現了類似的問題。 但我不明白這應該如何工作:

int data = rand();
char* tosend = (char*)&data;
int remaining = sizeof(data);

我想發送我存儲到int變量中的文件大小。 我為此使用下一個代碼:

服務器端:

struct stat st;
stat("file", &st);
int file_size = st.st_size;
write(client_fd, &file_size, sizeof(file_size));

客戶端:

int file_size;
read(server_fd, &file_size, sizeof(int));

它工作正常。 但是在write的文檔中說:

請注意,成功的 write() 可能傳輸的字節數少於 count

read一樣:

如果此數字小於請求的字節數,則不是錯誤

如果我要發送字符緩沖區,每個緩沖區長 1 個字節。 我只需將偏移量添加到緩沖區並將每個字節的下一個存儲在緩沖區中。 像這樣:

int write_all(int fd, void* buffer, int count) {
    int offset = 0, b = 0, status;

    while ((status = write(fd, buffer + offset, count)) != count) {
        if (status == -1) {
            /error check/
            return -1;
        }

        count -= status;
        offset += status;
        b += status;
    }

    b += status;

    return b;
}

但是如何為變量做同樣的事情呢?

您引用的示例不是很好,因為它沒有考慮 integer 字節順序 - 並且您無法保證接收主機上的字節順序與您發送的主機上的字節順序相同。

我的建議是

  1. 選擇一個字節順序 - LSB 優先(小端)或 MSB 優先(大端),然后
  2. 分配相關大小的charuint8_t緩沖區(例如,16 位 integer 為 2 個字節,32 位整數為 4 個字節)並逐字節存儲 integer
  3. 使用您現有的write_all() function 發送它。

Big-endian 對於網絡數據可能更典型,因此使用它可能會更好。 那么,如果要發送一個32位的integer...

void send_u32(int client_fd, int32_t n) {
  uint8_t buf[4];

  // Big-endian byte order ...
  for (int i=0; i<4; i++) {
      buf[i] = n >> ((3-i)*8);
  }

  int res = write_all(client_fd, buf, 4);
}

對於 16 位 integer 只需使用 2 字節緩沖區。

您將需要#include <stdint.h>用於上述內容。

struct stat中的 PS st_sizeoff_t - 我認為這可能與操作系統有關。 也許對 POSIX 有更多了解的人可以對此有所了解。 (無論如何,您可能會忘記使用 16 位 integer - 正如我所期望的那樣,它總是至少是 32 位 integer,除非在某些嵌入式系統上?)

您的 write_all() 實現有問題。 它會在短寫時失敗。

考慮這些:

size_t write_some(const int desc, const void *buffer, const size_t min_length, const size_t max_length)
{
    const char       *ptr  = (const char *)buffer;
    const char *const need = (const char *)buffer + min_length;
    const char *const end  = (const char *)buffer + max_length;

    while (ptr < need) {
        ssize_t  len = write(desc, ptr, (size_t)(end - ptr));
        if (len > 0) {
            ptr += len;
        } else
        if (len != -1) {
            errno = EIO;
            return (size_t)(ptr - (const char *)buffer);
        } else {
            /* errno set by write() */
            return (size_t)(ptr - (const char *)buffer);
        }
    }

    errno = 0;
    return (size_t)(ptr - (const char *)buffer);
}

size_t  read_some(const int desc, void *buffer, const size_t min_length, const size_t max_length)
{
    char *const end  = (char *)buffer + max_length;
    char *const need = (char *)buffer + min_length;
    char       *ptr  = (char *)buffer;

    while (ptr < need) {
        ssize_t  len = read(desc, ptr, (size_t)(end - ptr));
        if (len > 0) {
            ptr += len;
        } else
        if (len == 0) {
            errno = 0;
            return (size_t)(ptr - (char *)buffer);
        } else
        if (len != -1) {
            errno = EIO;
            return (size_t)(ptr - (char *)buffer);
        } else {
            /* errno set by read() */
            return (size_t)(ptr - (char *)buffer);
        }
    }

    errno = 0;
    return (size_t)(ptr - (char *)buffer);
}

它們都始終將errno為零,如果成功則設置為 errno,否則設置為 errno,並返回發送/接收的實際字節數。

如果收到 stream (使用描述符desc )以四字節大端(network-endian)長度字段開頭的數據包,長度包括四字節長度字段本身(使其最小有效值為4),您可以使用以下內容:

static inline size_t packet_size4(const void *const from)
{
    const unsigned char *src = (const unsigned char *)from;
    return ((size_t)(src[0]) << 24)
         | ((size_t)(src[1]) << 16)
         | ((size_t)(src[2]) <<  8)
         |           src[3];
}

int handle_all_packets(int desc, unsigned char *buffer, size_t maxlen)
{
    size_t  have = 0;
    size_t  len = 0;

    while (1) {
        if (have < 4) {
            have += read_some(desc, buffer + have, 4 - have, maxlen - have);
            if (have < 4)
                return errno;
            len = packet_size4(buffer);
            if (len < 4)
                return errno = EBADMSG;
            if (len > maxlen)
                return errno = EOVERFLOW;
        }
        if (have < len) {
            have += read_some(desc, buffer + have, len - have, maxlen - have);
            if (errno)
                return errno;
            if (have < len)
                return errno = EIO;
        }

        /* Process len-byte packet in buffer. */

        if (have > len) {
            memmove(buffer, buffer + len, have - len);
            have -= len;
            len   = 0;
        } else {
            have  = 0;
            len   = 0;
        }
    }
}

如果沒有錯誤,它將返回 0,否則返回非零 errno 錯誤代碼。

要發送這樣的數據包,您可以使用以下命令:

static inline void set_packet_size4(unsigned char *dst, size_t size)
{
    dst[0] = (size >> 24) & 255;
    dst[1] = (size >> 16) & 255;
    dst[2] = (size >>  8) & 255;
    dst[3] =  size        & 255;
}

int send_packet(const int desc, const void *data, const size_t len)
{
    size_t  n;

    {
        unsigned char  buf[4];
        set_packet_size4(buf, len + 4);
        n = write_some(desc, buf, 4, 4);
        if (errno)
            return errno;
        if (n != 4)
            return errno = EIO;
    }

    n = write_some(desc, data, len, len);
    if (errno)
        return errno;
    if (n != len)
        return errno = EIO;
    return 0;
}

如果數據包寫入正確,則 function 返回 0,否則返回非零 errno 錯誤碼。

實際上有一個標准化的網絡字節順序(恰好是大端),以及從主機轉換為網絡的標准函數,反之亦然:

  • htons()主機到網絡短
  • htonl()主機到網絡長
  • ntohl()網絡到主機長
  • ntohs()網絡到主機短

將這些用於通過網絡傳輸二進制整數的可移植方式。 您可能還會查看writen()是否真的在嘗試寫入n個字節,盡管 function 有自己的一組陷阱。

寫入和讀取都分別返回發送或接收的字節數。 在您收到足夠的字節之前不要開始處理您的數據。

更詳細地說:TCP/IP 將嘗試盡快傳遞它擁有的數據。 因此,如果數據在數據包之間拆分,您的讀取可能會在收到預期的字節數之前返回,這可以讓您在數據處理上搶占先機,而不是等待。

暫無
暫無

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

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