• 高级I/O函数


    1、pipe函数:

        pipe函数用来创建一个管道,以实现进程之间通信。

    #include<unistd.h>

    int pipe(int fd[2]);

        pipe函数的参数是一个包含两个int 类型整数的数组指针。该函数成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组。如果失败,则返回-1并设置errno。

        pipe函数创建的这两个文件描述符fd[0],fd[1]分别构成管道的两端,往fd[1]写入的数据可以从fd[0]读出。并且fd[1]只能用于往管道中写入数据,fd[0]只能用于从管道读出数据。而不能反过来使用。若是想实现双向通信,就必须要使用两个管道。

        管道内部传输的数据是字节流,这和TCP字节流的概念相同。但二者又有细微的区别。应用程程序能往一个TCP连接中写入多少字节的数据,取决于对方的接受通告的大小和本端的拥塞窗口的大小。

        socket的基础API中有一个skcketpair函数。它能够方便地创建双向管道。定义如下:

    #include<sys/types.h>

    #include<sys/socket.h>

    int socketpair(int domain,int type,int protocol,int fd[2]);

        socketpair前三个参数的含义与socket系统调用的三个参数完全相同,但domain只能使用unix本地与协议族af——unix,因为我们仅能在本地使用这个双向管道。最后一个参数则和pipe系统调用的参数一样,只不过socketpair创建的这对文件描述符都是即可读有可写的。socketpair成功时返回0失败时返回-1并设置errno。

     2、readv函数和writefv函数。

        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结构数组。我们再第五章讨论过了结构体iovec,该结构体描述一块内存区。count参数式vector数组的长度,既有多少块内存数据需要从fd读出或写到fd。readv和writev在成功时返回读出/写入fd字节数,失败则返回-1并设置errno。

     2、dup函数和dup2函数。

        我们希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接。还可以通过下面的用与复制文件的dup或者dup2函数来实现:

        #include<unistd.h>

    int dup(int file_descroptor);

    int dup2(int file_descriptor);

        dup函数创建一个新的文件描述符,该描述符和原有文件描述符file_descriptor指向相同的文件,管道,或者网络连接。并且dup返回的文件描述符总是去系统当前可用的最小的整数值。dup2和dup相似,不过他将返回一个不小于file_descriptor_two的整数值。dup和dup2系统调用失败时则返回-1并设置errno。

        通过dup和dup2创建的文件描述符并不继承元文件描述符的属性,比如close-on-exec和non-blocking等。。。

    3、什么事CGI服务器?

        在互联网行业中有一个词叫CGI,这个是服务器中的专业术语,所以很多人不知道CGI是什么。CGI是一个用于定Web服务器与外部程序之间通信方式的标准,使得外部程序能生成HTML、图像或者其他内容,而服务器处理的方式与那些非外部程序生成的HTML、图像或其他内容的处理方式是相同的。因此,CGI程序册仅使你能生成表态内容而能生动态内容。使用CGI的原因在于它是一个定义良好并被广泛支持的标准,没有CGI就不可能实现动态的Web页面,除非使用一些服务器中提供的特殊方法(如今,也有除CGI之外的其他技术逐渐在成为标准)。

        在互联网行业中有一个词叫CGI,这个是服务器中的专业术语,所以很多人不知道CGI是什么。CGI是一个用于定Web服务器与外部程序之间通信方式的标准。

    CGI 服务器原理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    #include<sys/socket.h>
    #include<netinet./in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    int main(int argc,char * argv[])
    {
        if(argc<=2)
        {
            printf("usage: %s ip_address port_num ",basename(argv[0]));
            return 1;
        }
        const char * p = argv[1];
        int port = atoi(argv[2]);
     
        struct sockaddr_in address;
        bzero(&address,sizeof(address));
     
        address.sin_family=AF_INET;
        inet_pton(AF_INET,ip,&address.sin_addr);
        address.sin_port=htons(port);
     
        int sock=socket(PF_INET,SOCK_STREAM,0);
     
        assert(sock>=0);
        int ret=bind(sock,(struct sockaddr*)&address,sizeof(address));
     
        assert(ret!=-1);
        ret=listen(sock,5);
     
        struct sockaddr_in client;
     
        socklen_t client_addrlength = sizeof(client);
        int connfd=accept(sock,(struct sockaddr*)&client,sizeof(client));
     
        if(connfd<0)
        {
            printf("errno is %d ",errno);
        }
        else
        {
            close(STDOUT_FILENO);
            dup(connfd);
            printf("abcd ");
            close(connfd);
        }
        close(sock);
        return 0;
    }

        在代码清单中,我们先关闭标准输出文件描述符STDOUT_FILENO(其值是1)然后赋值socket文件描述符connfd。因为dup总是返回系统中最小的可用文件描述符,所以他的返回值实际上是1,即之前关闭的标准输出文件描述符的值。这样一来,服务器输出到标准输出的内容(abcd)就会直接发送到与客户连接对应的socket上,因此printf调用的输出将被客户端得到。这就是CGI服务器的基本工作原理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include<sys/socket.h>
    #include<netinet./in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/stat.h>
    #include<sys/types.h>
    #include<fcntl.h>
     
    #define buffer_size  1024;
     
    static const char * status_line[2]=("200 ok","50 internet" );
     
    int main(int argc,char * argv[])
    {
        if(argc<=3)
        {
            printf("usage: ip_address port_num ");
            return 1''
        }
        const char * ip=argv[1];
        int port=atoi(argv[2]);
        .................
    }

    3、sendfile函数:

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

    #include<sys/sendfile.h>

    ssize_t sendfile(int out_fd,int in_fd,off_t* offser,size_t count);

    int fd 参数是带读出内容的文件描述符,out_ fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件默认的起始位置count参数指定在文件描述符in_fd 和 out_fd 之间传输的字节数。sendfile成功时返回传输的字节数,失败时返回-1,并设置errno。该函数的man手册明确指出,in_fd必须是一个支持类似mmap函数的文件描述符,即他必须指向真实的文件,不能是socket和管道:而out_fd则必须是一个socket。由此可见,sendfile几乎是专门为在网络上传输文件而设计的。

    如下,利用sendfile函数将服务器上的一个文件传送给客户端。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    #include<sys/sendfile.h>
     
    int main(int argc,char * argv[])
    {
        if(argc<=3)
        {
            printf("usage : %s ip_address port_file file");
            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);
     
        struct sockaddr_in address;
        bzero(&address,sizeof(address));
        address.sin_family=AF_INET;
        inet_pton(AF_INET,ip,&address.sin_addr);
        address.sin_port=htons(port);
     
        int sock= socket(PF_INET,SOCK_STREAM,0);
        assert(sock>=0);
     
        int ret=bind(sock,(struct sockaddr* )&address,sizeof(address));
     
        assert(ret!=-1);
     
        ret=listen(sock,5);
        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 ",errno);
        }
        else
        {
            sendfile(connfd,filefd,NULL,stat_buf.st_size);
            close(connfd);
        }
        close(sock);
        return 0;
     
    }

    在代码中,我们将目标文件作为第三个参数传递给服务器程序,客户telnet到该服务器上即可获得该文件。

    4、mmap函数和munmao函数。

        mmap函数用于申请一段内存空间。我们可以将这段内存作为进程之间通信的共享内存,也可以将文件直接映射到其中。munmap函数则释放有mmap创建的这段内存空间。他们的定义如下:

    1
    2
    3
    #include<sys/mman.h>
    void * mmap(void * start ,size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void * start,size_t  length);

    start参数允许用户使用某个特定的地址作为这段内存的起始地址。如果他被指为NULL,则系统自动分配一个地址。length参数指定内存段的长度。prot参数用来设置内存的访问权限。它可以取以下几个值的按位或:

    PROT_READ,内存段可读

    PROT_WRITE,内存段可写

    PROT_EXEC,内存段可执行

    PROT_NONE,内存段不能被访问

    flags参数控制内存段内容被修改后程序的行为。它可以被设置为下标中的某些值的按位或。

    麦库截图20171029105433118.jpg 

    fd参数是被映射文件对应的文件描述符。它一般通过open系统调用获得。offset参数设置从文件的何处开始映射(对于不需要读入整个文件的情况)。

        mmap函数成功时返回指向目标内存区域的指针,失败则返回MAP_FAILED(void * )-1)并设置errno。munmap函数成功时返回0,失败则返回-1并设置errno。

    我们将在以后,在讨论如何利用mmap函数实现进程间的共享内存。

    5、solice函数:

    splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。splice函数定义如下:

    #include<fcnt1.h>

    ssize_t splice(int fd_in , loff_t * off_in  , int fd_out ,  loff_t * off_out , size_t len, unsigned int flages );

    fd_in 参数是待输入数据的文件描述符。如果fd_in 是一个管道文件描述符,那么off_in参数必须设置为NULL。如果fd_in 不是一个管道文件描述符(比如scoket),那么off_in表示从输入数据流的何处开始读取数据。此时,若off_in 被设置为NULL,则表示从输入数据流的当前偏移位置读入;若off_inn部位NULL,则它将指出具体的偏移位置,fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。len参数指定移动数据长度;flags参数则控制数据如何移动,它可以被设置为下标内容中的某些值的按位或。

    麦库截图20171129111005011.jpg 

    使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。splice函数调用成功时返回移动字节的数量。他可能返回0,表示没有数据需要流动,这发生在管道中读取数据(fd_in是管道文件描述符)而该管道没有被写入任何数据时。splice函数失败时则返回-1并设置一个errno。常见的errno如表:

    麦库截图20171129111300855.jpg 

    下面我们使用splice函数来实现一个零拷贝的回射服务器,它将客户端发送的数据原样返回给客户端,具体实现如下:

                                          

  • 相关阅读:
    成本直降50% | 阿里云发布云原生网关,开启下一代网关新进程
    阿里云容器服务全面升级为 ACK Anywhere,让云的边界拓展至企业需要的每个场景
    云拨测助力节卡机器人 全面优化海外网站性能
    如何加速云原生数据应用?这个开源项目备受关注
    课程升级 | 极速构建知识体系,即学即用 Serverless
    ECS 选款利器!PTS助您快速上云!
    Morphling:云原生部署 AI , 如何把降本做到极致?
    报名领奖|云栖大会,10月19-22日杭州不见不散!
    Dubbo3.0|阿里巴巴服务框架三位一体的选择与实践
    告别Kafka Stream,让轻量级流处理更加简单
  • 原文地址:https://www.cnblogs.com/yjds/p/8597331.html
Copyright © 2020-2023  润新知