在套接字上写入和读取返回不同的字节数

时间:2016-05-22 18:52:04

标签: c sockets

我正在编写一个使用AF_UNIX套接字的客户端 - 服务器应用程序。 客户端生成一个字符串,然后在发送标头后在套接字上发送它。然后服务器读取标头,为字符串分配空间,然后读取字符串。

标题定义为:

typedef struct {
    unsigned long key;
    op_t op; // op_t is an enum
} header_t;

字符串以其长度存储:

typedef struct {
    unsigned int len;
    char* buf;
} data_t;

还有另一个结构将这两个东西组合成一个(不是我的选择,我必须使用这些东西)。

typedef struct {
    header_t hdr;
    data_t data;
} message_t;

我正在使用writev()系统调用通过套接字发送数据,这样:

int sendRequest(long fd, message_t *msg) {
    struct iovec to_send[3];
    /* Header */
    to_send[0].iov_base = &(msg->hdr);
    to_send[0].iov_len = sizeof(header_t);

    /* Data */
    to_send[1].iov_base = &(msg->data.len);
    to_send[1].iov_len = sizeof(msg->data.len);

    to_send[2].iov_base = msg->data.buf;
    to_send[2].iov_len = msg->data.len;

    int c;
    if((c = writev(fd, to_send, (msg->data.len > 0) ? 3 : 2)) < 0) {
        return -1;
    }
    printf("#### %i BYTES WRITTEN (header: %i) ####\n",c, to_send[0].iov_len);

    return 0;
}

要阅读,我使用两个不同的函数,一个用于读取标题,另一个用于读取数据:

int readHeader(long fd, header_t *hdr) {
    struct iovec to_read[1];

    to_read[0].iov_base = hdr;
    to_read[0].iov_len = sizeof(header_t);

    errno = 0;
    int c;
    if((c = readv(fd, to_read, 1)) <= 0) {
        return -1;
    }
    printf("[H] %i BYTES READ \n",c);

    return 0;
}

int readData(long fd, data_t *data) {
    struct iovec to_read[2];
    /* First, read how long is the buffer */
    to_read[0].iov_base = &(data->len);
    to_read[0].iov_len = sizeof(data->len);

    int c;
    if((errno = 0, c = readv(fd, to_read, 1)) <= 0)
        return -1;

    if(data->len > 0) {
        data->buf = calloc(data->len, sizeof(char));
        if(data->buf == NULL)
            return -1;
        /* Read the string */
        to_read[1].iov_base = data->buf;
        to_read[1].iov_len = data->len;

        if((errno = 0, c += readv(fd, &to_read[1], 1)) <= 0) {
            free(data->buf);
            return -1;
        }
    }
    else {
        data->buf = NULL;
    }
    printf("[D] %i BYTES READ (%i + %i)",c, to_read[0].iov_len, to_read[1].iov_len);

    return 0;
}

这就是问题所在。 如果我发送一个长度为8193字节的字符串,那么在客户端上输出的一切正常 8213 bytes written (header: 16)这是正确的,因为16个字节来自标头,4个字节来自len字段,8193来自字符串。 但服务器打印这个: [H] 16 bytes read(好)然后[D] 8176 bytes read(错!)。所以,剩下21个字节要读。为什么?如果我尝试发送长度为8192或更短的字符串,一切正常。假设readv()可以读取的字节数有限制,那么读取所有内容的正确方法是什么?

2 个答案:

答案 0 :(得分:1)

  

阅读所有内容的正确方法是什么?

无法保证readv会立即返回所有数据。如果第一次读取没有返回所有请求的字节,那么你需要再次调用read / readv来完成其余的工作。

答案 1 :(得分:0)

  

但服务器打印出来:[H]读取16个字节(好),然后读取[D] 8176个字节(错误!)。

肯定说“意外”比“错误”更安全。可以安全地假设readv()返回实际读取的字节数,正如指定的那样。您似乎已陷入这些POSIX低级I / O函数的经典陷阱之一:在任何给定的调用中,它们无法保证传输您请求它们执行的完整字节数。这就是为什么在成功时它们返回传输的字节数。

  

阅读所有内容的正确方法是什么?

我经常看到人们使用read()write()函数遇到此问题,但readv()writev()的工作方式相同,只是必要时将输入分散到多个缓冲区或分别从多个缓冲区收集输出。使用任一组函数,如果要传输特定数量的字节,则必须准备好在循环中执行多次读取或写入,在每次迭代时拾取上一次迭代停止的位置。

在这一点上你可能会发现,在多个调用之间传输多个不同大小的缓冲区的全部内容可能会相当快速地复杂化(如果它之前没有那么它应该立即执行)。 readv()writev()函数并非真正用于您尝试使用它们的用途。它们本身几乎不比read()write()更高,它们最适合将多个固定大小的缓冲区视为一个更大的缓冲区。

对于像您这样的案例,我认为使用read()write()会更容易。您可以考虑编写辅助函数来包装那些执行所需循环的函数来完全读取指定数量的字节;这将节省您为要传输的每个单独数据项重复此类代码。编写完这些函数之后,主代码实际上比现在简单,因为你不需要设置iovectors。