第六章|高级I/O函数

pipe

创建一个管道,实现进程间通信。

dup、dup2

复制文件描述符
实例:cgi服务器原理

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int main(int argc, char *argv[]) {
    if (argc <= 2) {
        printf("usage: %s id_address prot_number \n", basename(argv[0]));
        return 1;
    }


    const char *ip = argv[1];
    //字符串转换成整型数的一个函数
    int port = atoi(argv[2]);

    //创建一个专用 IPV4 socket 地址
    struct sockaddr_in address;
    //bzero功能:置字节字符串address的前sizeof( address )个字节为零且包括‘\0’
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;//地址组
    //inet_pton:将用字符串表示ip地址转换成网络字节序整数表示的ip地址。 成功返回1 失败返回0
    inet_pton(AF_INET, ip, &address.sin_addr);
    //小端转大端 、主机字节序转换网络字节序
    address.sin_port = htons(port);


    //创建一个socket PF_INET:ipv4   SOCK_STREAM:流服务
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    //判断是否失败  失败返回-1并设置errno  成功返回socket文件描述符
    assert(sock >= 0);

    //命名socket   将address所指的地址分配给未命名的 sock 变量(文件描述符)
    int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
    // bind 成功返回0 失败返回-1 并设置errno
    assert(ret !=-1);

    //监听socket sock参数:指定被监听的socket   backlog参数:提示内核监听队列的最大长度
    ret = listen(sock,5);
    //listen 成功返回0  失败返回-1 并设置errno
    assert(ret !=-1);


    //创建一个专用 IPV4 socket 地址
    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock,(struct sockaddr*)&client ,&client_addrlength);
    if (connfd < 0)
    {
        printf("error is :%d\n",errno);
    }
    else
    {
        //关闭标准输出文件描述符
        close(STDOUT_FILENO);
        //复制socket文件描述符connfd
        dup(connfd);
        printf("adcd\n");
        close(connfd);
    }

    //关闭socket
    close(sock);
    return 0;
}

B主机执行该程序


image.png

A主机执行该程序


image.png
readv() 、wirtev()函数用法

readv和writev函数和前面提过的readmsg和writemsg函数类似,也是用来对数据的集中写和分散读,相当于前面两个函数的简化版。举一个例子来说明,在Web服务器解析完HTTP请求后如果客户端请求的文件存在并且有权限时,就需要返回一个HTTP首部状态码和状态信息,然后再返回该文件,但是我们考虑效率问题,如果每次我们都需要将两个不相关的存储空间合并到一起再发送势必会很影响效率,所以我们可以事先将HTTP不同的头部存储好,找到文件后使用sendv函数直接发送即可。我们建立一个index.html文件模拟一下,服务器代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 1024
static const char *status_line[2] = {"200 OK", "500 Internal 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];

    //创建一个专用 IPV4 socket 地址
    struct sockaddr_in address;
    //bzero功能:设置字符串address的前sizeof(address)个字节为0且包括'\0'
    bzero(&address, sizeof(address));
    //地址族
    address.sin_family = AF_INET;
    //将用字符串表示ip地址转换成网络字节序整数表示的ip地址,成功返回1 失败返回0
    inet_pton(AF_INET, ip, &address.sin_addr);
    //小端转大端、主机字节序转换网络字节序
    address.sin_port = htons(port);

    //创建一个socket   PF_INET:ipv4  SOCK_STREAM:流服务
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    //判断是否失败  失败返回-1 并设置error  成功返回socket文件描述符
    assert(sock >= 0);

    //命名socket  并将address所指向的地址分配给未命名的sock变量(文件描述符)
    int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
    //判断是否失败  失败返回-1 并设置error
    assert(ret != -1);

    //监听socket   sock: 指定被监听的socket    backlog:提示内核监听队列的最大长度
    ret = listen(sock, 5);
    //成功返回0 失败返回-1 并设置error
    assert(ret != -1);

    //创建一个专用ipv4 socket地址
    struct sockaddr_in client;
    //获取client类型长度
    socklen_t client_addrlength = sizeof(client);
    //接收连接  成功返回一个新的 socket 失败返回-1 并设置error
    //sock : 监听套接字,即服务器端创建的用于listen的socket描述符。
    //client :  struct sockaddr*这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址
    //client_addrlength : 描述 client 的长度  socklen_t*
    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];
        //将已开辟内存空间 header_buf 的首 BUFFER_SIZE 个字节的值设为值 '\0'。
        memset(header_buf, '\0', BUFFER_SIZE);
        //用于存放目标文件内容的应用程序缓存
        char *file_buf;
        //用于获许目标文件的属性,比如是否为目录,文件大小等
        struct stat file_stat;

        //记录目标文件是否是有效文件
        bool valid = true;
        //缓存区header_buf 目前已经使用了多少个字节的空间
        int len = 0;
        //stat(): 获取一些文件相关的信息  filename : 文件路径名  file_stat ;保存文件信息的结构体  成功返回0。失败返回-1 并设置error
        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 ];
//                char file_buf[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;
            }
        }

        if (valid) {
            //下面这部分内容讲http应答的状态行 、"Content-Length" 头部字段和一个空行依次加入header_buf种

            //snprintf() :最多从源串中拷贝size-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为size的话,将不会溢出。
            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_buff 和 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;
}

readv、wirtev函数调用实例
sendfile

函数可以在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率高,被称为零拷贝。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 1024
static const char *status_line[2] = {"200 OK", "500 Internal 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];

    int filefd = open(file_name, O_RDONLY);
    assert(filefd > 0);

    struct stat stat_buf;
    fstat(filefd,&stat_buf);

    //创建一个专用 IPV4 socket 地址
    struct sockaddr_in address;
    //bzero功能:设置字符串address的前sizeof(address)个字节为0且包括'\0'
    bzero(&address, sizeof(address));
    //地址族
    address.sin_family = AF_INET;
    //将用字符串表示ip地址转换成网络字节序整数表示的ip地址,成功返回1 失败返回0
    inet_pton(AF_INET, ip, &address.sin_addr);
    //小端转大端、主机字节序转换网络字节序
    address.sin_port = htons(port);

    //创建一个socket   PF_INET:ipv4  SOCK_STREAM:流服务
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    //判断是否失败  失败返回-1 并设置error  成功返回socket文件描述符
    assert(sock >= 0);

    //命名socket  并将address所指向的地址分配给未命名的sock变量(文件描述符)
    int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
    //判断是否失败  失败返回-1 并设置error
    assert(ret != -1);

    //监听socket   sock: 指定被监听的socket    backlog:提示内核监听队列的最大长度
    ret = listen(sock, 5);
    //成功返回0 失败返回-1 并设置error
    assert(ret != -1);

    //创建一个专用ipv4 socket地址
    struct sockaddr_in client;
    //获取client类型长度
    socklen_t client_addrlength = sizeof(client);
    //接收连接  成功返回一个新的 socket 失败返回-1 并设置error
    //sock : 监听套接字,即服务器端创建的用于listen的socket描述符。
    //client :  struct sockaddr*这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址
    //client_addrlength : 描述 client 的长度  socklen_t*
    int connfd = accept(sock, (struct sockaddr *) &client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        sendfile(connfd,filefd,NULL,stat_buf.st_size);
        close(connfd);
    }

    close(sock);
    return 0;
}
sendfile函数调用实例
splice

函数用于在两个文件描述符之间移动数据,也是零拷贝。
在使用该函数时、fd_in 和 fd_out 必须至少有一个是管道文件描述符。splice函数调用成功返回移动字节的数量,可能返回0,表示没有数据需要移动,比如从管道中读取数据(fd_in是管道文件描述符)而该管道没有被写入任何数据时。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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


    //创建一个专用 IPV4 socket 地址
    struct sockaddr_in address;
    //bzero功能:设置字符串address的前sizeof(address)个字节为0且包括'\0'
    bzero(&address, sizeof(address));
    //地址族
    address.sin_family = AF_INET;
    //将用字符串表示ip地址转换成网络字节序整数表示的ip地址,成功返回1 失败返回0
    inet_pton(AF_INET, ip, &address.sin_addr);
    //小端转大端、主机字节序转换网络字节序
    address.sin_port = htons(port);

    //创建一个socket   PF_INET:ipv4  SOCK_STREAM:流服务
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    //判断是否失败  失败返回-1 并设置error  成功返回socket文件描述符
    assert(sock >= 0);

    //命名socket  并将address所指向的地址分配给未命名的sock变量(文件描述符)
    int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
    //判断是否失败  失败返回-1 并设置error
    assert(ret != -1);

    //监听socket   sock: 指定被监听的socket    backlog:提示内核监听队列的最大长度
    ret = listen(sock, 5);
    //成功返回0 失败返回-1 并设置error
    assert(ret != -1);

    //创建一个专用ipv4 socket地址
    struct sockaddr_in client;
    //获取client类型长度
    socklen_t client_addrlength = sizeof(client);
    //接收连接  成功返回一个新的 socket 失败返回-1 并设置error
    //sock : 监听套接字,即服务器端创建的用于listen的socket描述符。
    //client :  struct sockaddr*这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址
    //client_addrlength : 描述 client 的长度  socklen_t*
    int connfd = accept(sock, (struct sockaddr *) &client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        int pipefd[2];
        assert(ret != -1);
        ret = pipe(pipefd);
        ret = splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
        ret = splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
        close(connfd);
    }

    close(sock);
    close(ret);
    return 0;
}
splice函数调用实例

你可能感兴趣的:(第六章|高级I/O函数)