readv函数和writev函数

Posted qq_34132502

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了readv函数和writev函数相关的知识,希望对你有一定的参考价值。

readv:分散读
writev:集中写

#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec* vector, int count);
ssize_t writev(int fd, const struct iovec* vector, int count);

fd参数是被操作的目标文件描述符。
vector参数类型是iovec结构数组。该结构体描述一块内存区。
count表示数组长度

iovec结构体定义如下:

struct iovec{
	void *iov_base;		// 内存起始地址
	size_t iov_len;		// 这块内存的长度
};

当Web服务器解析完一个HTTP请求之后,如果目标文档存在且客户有读取该文档的权限,那么它就需要发送一个HTTP应答来传输该文档。
这个HTTP应答包含一个状态行、多个头部字段、一个空行和文档的内容。
其中前三部分的内容可能被Web服务器放置在一块内存中,而文档的内容则通常被读入到另外一个单独的内存中(通过read函数或mmap函数)。我们并不需要把这两部分内容拼接到一起在发送,而是可以使用writev函数将它们同时写出

Web服务器上的集中写:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <libgen.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdbool.h>
#include <new>


#define BUFFER_SIZE     1024
#define BACKLOG         5

// 定义两种HTTP状态码和状态信息
static const char* status_line[2] = {"200 OK", "500 Inetrnal server error"};

int main(int argc, char* argv[]) {
    if (argc <= 3) {
        printf("usage: %s ip_address port_number filename\\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    const char* file_name = argv[3];

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = port;

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sock, BACKLOG);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\\n", errno);
    } else {
        // 用于保存HTTP应答的状态行、头部字段和一个空行的缓存区
        char header_buf[BUFFER_SIZE];
        memset(&header_buf, '\\0', BUFFER_SIZE);
        // 用于存放目标文件内容的应用程序缓存
        char* file_buf;
        // 用于获取目标文件的属性,比如是否为目录,文件大小等
        struct stat file_stat;
        // 记录目标文件是否是有效文件
        bool valid = true;
        // 缓存区header_buf目前已经使用了多少字节的空间
        int len = 0;
        if (stat(file_name, &file_stat) < 0) {  // 目标文件不存在
            valid = false;
        } else {
            if (S_ISDIR(file_stat.st_mode)) {   // 目标文件是一个目录
                valid = false;
            } else if (file_stat.st_mode & S_IROTH){    // 当前用户有读取目标文件的权限
                // 动态分配缓存区file_buf,并指定其大小为目标文件的大小
                // file_stat.st_size加1,然后将目标文件读入缓存区file_buf中
                int fd = open(file_name, O_RDONLY);
                file_buf = new char[file_stat.st_size + 1];
                memset(file_buf, '\\0', file_stat.st_size + 1);
                if (read(fd, file_buf, file_stat.st_size) < 0) {
                    valid = false;
                }
            } else {
                valid = false;
            }
        }
        // 如果目标文件有效则发送正常的HTTP应答
        if (valid) {
            // 下面这部分内容将HTTP应答的状态行、"Content-Length"头部字段和一个空行依次加入header_buf中
            ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\\r\\n",
                            "HTTP/1.1", status_line[0]);
            len += ret;
            ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, 
            				"Content-Length: %d\\r\\n", int(file_stat.st_size));

            len += ret;
            ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s", "\\r\\n");
            // 利用writev将header_buf和file_buf的内容一并写出
            struct iovec iv[2];
            iv[0].iov_base = header_buf;
            iv[0].iov_len = strlen(header_buf);
            iv[1].iov_base = file_buf;
            iv[1].iov_len = file_stat.st_size;
            ret = writev(connfd, iv, 2);
        } else {
            // 如果目标文件无效,则通知客户端服务器发生了“内部错误”
            ret = snprintf(header_buf, BUFFER_SIZE - 1, 
            				"%s %s\\r\\n", "HTTP/1.1", status_line[1]);
            len += ret;
            ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s", "\\r\\n");
            send(connfd, header_buf, strlen(header_buf), 0);
        }
        close(connfd);
        delete[]file_buf;
    }
    close(sock);
    return 0;
}

【注】:
snprintf(),函数原型为int snprintf(char* str, size_t size, const char* format, ...)
将可变参数“…”按照format的格式格式化为字符串,然后再将其拷贝至str中

  1. 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符(’\\0’);
  2. 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符(’\\0’),返回值为欲写入的字符串长度。

若成功则返回与写入的字符串长度,若出错则返回负值
在头文件stdio.h

以上是关于readv函数和writev函数的主要内容,如果未能解决你的问题,请参考以下文章

readv()、writev()、WSARecv()、WSASend()

套接字I/O函数write/read writev/readv send/recv sendto/recvfrom sendmsg/recvmsg

readv 和 writev

Linux:何时使用分散/收集 IO(readv、writev)与带有 fread 的大缓冲区

c_cpp writev_readv.c

UNIX环境高级编程笔记之高级I/O