Linux高性能服务器编程 高级I/O函数 第六章

pipe函数

pipe函数可用于创建一个管道,以实现进程间通信。
在这里插入图片描述
pipe函数的参数是一个包含两个int型整数的数组指针,成功返回0,并将一对打开的文件描述符值填入其参数指向的数组
通过pipe创建的两个文件描述符fd[0]和fd[1]分别构成管道的两端,往fd[1]写入的数据可以从fd[0]读出。不能反过来使用。实现双向传输就使用两个管道。默认情况下都是阻塞的。用read读取一个空的管道,则read将被阻塞,直到管道内有数据可读:如果用write系统调用来往一个满的管道中写入数据,则write也被阻塞,直到管道有足够多的空闲空间可用。
管道内部传输的数据是字节流,这和TCP字节流概念相同二者区别:应用层程序能往一个TCP连接中写入多少字节的数据,取决于对方的接收通告窗口的大小和本端的拥塞窗口的大小。而管道本身就有一个容量限制,如果应用程序不将数据从管道读走的话,该管道最多能被写入多少字节的数据。管道容量大小默认65536字节,可以使用fcntl函数修改管道容量。
socket的基础API有一个socketpair函数,能够创建双向管道
在这里插入图片描述
前三个参数的含义与socket系统调用的三个参数完全相同,但domain只能使用UNIX本地域协议族AF_UNIX,因为我们仅能在本地使用这个双向管道。最后一个参数和pipe系统调用的参数一样,只不过socketpair创建的这对文件描述符都是既可读又可写的,成功返回0

dup函数和dup2函数

有时我们希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接,可以通过下面地用于复制文件描述符地dup和dup2实现:
在这里插入图片描述
dup函数创建一个新的文件描述符,该新文件描述符和原有文件描述符file_descriptor指向相同地文件、管道或者网络连接。并且dup返回地文件描述符总是取系统当前可用地最小整数值。dup2返回第一个不小于file_descriptor_two的整数值
(创建的文件描述符并不继承原文件描述符的属性,比如close-on-exec和non-blocking等)

//一个基本的CGI服务器
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

int main(int argc,char* argv[])
{

    if(argc<=2)
        return 1;

    const char* ip=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 sockfd=socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd>=0);

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

    ret=listen(sockfd,5);

    struct sockaddr_in client;
    socklen_t client_addrlength=sizeof(client);
    int connfd=accept(sockfd,(struct sockaddr*)&client,&client_addrlength);

    close(STDOUT_FILENO);
    dup(connfd);
    printf("abcd\n");
    close(connfd);

    close(sockfd);
    return 0;
}

我们先关闭标准输出文件的描述符STDOUT_FILENO(1),然后复制socket文件描述符connfd。因为dup总是返回系统中最小的可用文件描述符,所以它的返回值实际上是1,即之前关闭的标准输出文件描述符的值。这样一来,服务器输出到标准输出的内容(abcd)就会直接发送到与客户连接对应的socket上,因此printf调用的输出将被客户端获得(不显示在服务器终端)。

readv函数和writev函数

readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数将多块分散的内存数据一并写入文件描述符中,即集中写。它们的定义如下
在这里插入图片描述
fd参数是被操作的目标文件描述符,vector参数是iovec结构数组。该结构体描述一块内存区。count参数是vector数组的长度,即有多少块内存数据需要从fd读出或写到fd。readv和writev在成功时返回读出/写入fd的字节数,失败返回-1,相当于简化版的recvmsg和sendmsg函数

//Web服务器上的集中写
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>

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

int main(int argc,char* argv[])
{

    if(argc<=3)
        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=htons(port);

    int sockfd=socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd>=0);

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

    ret=listen(sockfd,5);

    struct sockaddr_in client;
    socklen_t client_addrlength=sizeof(client);
    int connfd=accept(sockfd,(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",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(sockfd);
    return 0;
}

sendfile函数

sendfile函数在两个文件描述符之间直接传递数据,从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,称为零拷贝
在这里插入图片描述
in_fd参数是待读出内容的文件描述符,out_fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置,count参数指定在文件描述符in_fd和out_fd之间传输的字节数。in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道:而out_fd必须是一个socket。由此可见,sendfile几乎是专门为在网络上传输文件而设计的。

//用sendfile函数传输文件
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sendfile.h>

int main(int argc,char* argv[])
{

    if(argc<=3)
        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);

    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
    {
        sendfile(connfd,filefd,NULL,stat_buf.st_size);
        printf("send suc");
        close(connfd);
    }

    close(sock);
    return 0;
}

没有为目标文件分配任何用户空间的缓存,没有执行读取文件的操作,效率更高

mmap函数和munmap函数

mmap函数用于申请一段内存空间,可以作为进程间通信的共享内存,也可以将文件直接映射到其中,munmap函数则释放由mmap创建的这段内存空间
在这里插入图片描述
start参数允许用户使用某个特定的地址作为这段内存的起始地址。如果它被设置成NULL,则系统自动分配一个地址。length参数指定内存段的长度。port参数用来设置内存段的访问权限
在这里插入图片描述
flags参数控制内存段内容被修改后程序的行为。它可以被设置为某些值的按位或
mmap的flags参数的常用值及其含义
fd参数是被映射文件对应的文件描述符。它一般通过open系统调用获得。offset参数设置从文件的何处开始映射(对于不需要读入整个文件的情况)。mmap函数成功时返回指向目标内存区域的指针,失败则返回MAP_FAILED(void*)-1.munmap函数成功返回0

splice函数

splice函数用于在两个文件描述符之间移动数据,也就是零拷贝操作
在这里插入图片描述
fd_in参数是待输入数据的文件描述符。如果fd_in是一个管道文件描述符,那么off_in参数必须设置为NULL,如果fd_in不是一个管道文件描述符(socket),那么off_in表示从输入数据流的何处开始读取数据。此时若off_in被设置为NULL,则表示从输入数据流的当前偏移位置读入;若不为NULL,则他指出具体的偏移位置。len参数指定移动数据的长度,flags参数则控制数据如何移动
splice的flags参数的常用值及其含义
使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。splice函数调用成功时返回移动字节的数量。它可能返回0,表示没有数据要移动,这发生在从管道中读取数据(fd_in是管道文件描述符)而该管道没有被写入任何数据时。常见的errno:
splice函数可能产生的errno及其含义
我们通过splice函数将客户端的内容读入到pipefd[1]中,然后再使用splice函数从pipefd[0]中读出内容到客户端,从而实现了简单高效的回射服务,整个过程未执行recv/send操作。

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

int main(int argc,char* argv[])
{

//    if(argc<=2)
//        return 1;

    const char* ip=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,&client_addrlength);

    if(connfd<0)
    {
        printf("errno is: %d\n",errno);
    }
    else
    {
        printf("new con!!");
        int pipefd[2];
        assert(ret!=-1);
        ret=pipe(pipefd);/*创建管道*/
        assert(ret!=-1);
        /*将connfd上流入的客户端定向到管道中*/
        ret=splice(connfd,NULL,pipefd[1],NULL,32768,
                SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret!=-1);
        /*将管道的输出定向到connfd客户连接文件描述符*/
        ret=splice(pipefd[0],NULL,connfd,NULL,32768,
                SPLICE_F_MORE |SPLICE_F_MOVE);
        assert(ret!=-1);
        close(connfd);
    }
    close(sock);
    return 0;
}

tee函数

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作
在这里插入图片描述
该函数的参数含义与splice相同(但fd_in和fd_out必须都是管道文件描述符),tee函数成功时返回两个文件描述符之间复制数据的数量(字节数),返回0表示没有复制任何数据

//将标准输入的数据同时输出到终端和文件
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        return 1;
    }
    int filefd=open(argv[1],O_CREAT | O_WRONLY | O_TRUNC,0666);
    assert(filefd>0);

    int pipefd_stdout[2];
    int ret=pipe(pipefd_stdout);
    assert(ret!=-1);

    int pipefd_file[2];
    ret=pipe(pipefd_file);
    assert(ret!=-1);

    /*将标准输入内容输入管道pipefd_stdout*/
    ret=splice(STDIN_FILENO,NULL,pipefd_stdout[1],NULL,
            32768,SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret!=-1);
    /*将管道pipefd_stdout的输出复制到管道pipefd_file的输入端*/
    ret=tee(pipefd_stdout[0],pipefd_file[1],32768,SPLICE_F_NONBLOCK);
    assert(ret!=-1);
    /*将管道pipefd_file的输出定向到文件描述符filefd上,从而将标准输入的内容写入文件*/
    ret=splice(pipefd_file[0],NULL,filefd,NULL,
            32768,SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret!=-1);
    /*将管道pipefd_stdout的输出定向到标准输出,其内容和写入文件的内容完全一致*/
    ret=splice(pipefd_stdout[0],NULL,STDOUT_FILENO,NULL,
            32768,SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret!=-1);

    close(filefd);
    close(pipefd_file[0]);
    close(pipefd_file[1]);
    close(pipefd_stdout[0]);
    close(pipefd_stdout[1]);
    return 0;
}

fcntl函数(file control)

提供了对文件描述符的各种控制操作,另外一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制。但是,对于控制文件描述符常用的属性和行为,fcntl函数是由POSIX规范指定的首选方法
在这里插入图片描述
fd参数是被操作的文件描述符,cmd参数指定执行何种类型的操作。根据操作类型的不同,该函数可能还需要第三个可选参数arg,fcntl函数支持的常用操作及其参数列表:
在这里插入图片描述
在这里插入图片描述
在网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的:

int setnonblocking(int fd)
{
    int old_option=fcntl(fd,F_GETFL);
    int new_option=old_option | P_NONBLOCK; /*设置非阻塞标志*/
    fcntl(fd,F_SETFL,new_option);
    return old_option;    /**返回文件描述符旧的状态标志,以便日后恢复该状态标志/
     
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值