跨平台C++服务器程序开发 (3)Linux文件描述符

Linux文件描述符

在Linux系统中几乎所有可读写的对象都可视为文件,比如标准输入输出、磁盘文件、套接字、管道等,都可以用read函数读取数据,用write函数写入数据。 这一点相对于Windows系统来说更加统一,在Windows系统中上面列举的每个对象都有其专门的一组函数读写。
下面分别描述这几种文件对象。

标准输入输出

在C语言中,printf向控制台窗口输出信息,scanf从控制台窗口输入信息,并且内置了stdin、stdout、stderr 3个文件描述符,分别对应数字0、1、2。

#define stdin  (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

在C++语言中,cout向控制台输出,cin从控制台输入,cerr错误输出。
在Linux系统头文件unistd.h定义了类似的3个宏。

       STDERR_FILENO
              File number of stderr; 2.

       STDIN_FILENO
              File number of stdin; 0.

       STDOUT_FILENO
              File number of stdout; 1.

每当我们在shell里运行一个新程序时,shell进程都会为新程序打开这3个描述符,并且指向shell终端。 所以标准输出、标准错误输出在shell上输出,标准输入在shell上输入。

当然我们也可以修改这3个描述符的指向,比如将标准输出指向一个文件,那么在程序里printf、cout输出的内容将不在shell上显示,而是存入了文件里,常见的命令比如:

./a.out > aaa.txt

将标准输出存入aaa.txt文件中。

磁盘文件

在C语言里使用fopen打开文件,fread读取文件,fwrite写入文件,fclose关闭文件。
在C++语言里有ifstream读取文件,ofstream写入文件。但通常更习惯于使用C语言提供的接口读写文件。
在Linux系统api里提供了open打开文件,read读取文件,write写入文件,close关闭文件。实际上C和C++库函数最终调用了系统api函数完成真正的读写功能。

套接字

套接字用于网络数据的读写,套接字相关函数操作的对象都是socket套接字,Linux提供了socket函数用于创建一个socket对象。

管道

管道是一种进程间通信接口,既可用于两个不同进程间通信,也可以用于同一个进程内部通信,Linux提供了pipe函数用于创建一个管道。

描述符分配规则

下面一段代码(fd.c)输出了描述符具体的数值,用于分析文件描述符的分配规则。

#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
    /*标准输入、输出*/
    printf("stdin = %d\n", STDIN_FILENO);
    printf("stdout = %d\n", STDOUT_FILENO);
    printf("stderr = %d\n\n", STDERR_FILENO);

    /*打开两个磁盘文件*/
    int fd1 = open("./a1.txt", O_RDONLY);
    int fd2 = open("./a2.txt", O_RDONLY);
    printf("fd1 = %d\n", fd1);
    printf("fd2 = %d\n\n", fd2);

    /*创建两个套接字*/
    int sfd1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    int sfd2 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    printf("sfd1 = %d\n", sfd1);
    printf("sfd2 = %d\n\n", sfd2);

    /*关闭之前打开的两个磁盘文件*/
    printf("close fd1 fd2\n");
    close(fd1);
    close(fd2);

    /*创建一个管道*/
    int pipefd[2];
    pipe(pipefd);
    printf("pipefd1 = %d\n", pipefd[0]);
    printf("pipefd2 = %d\n\n", pipefd[1]);

    /*再次打开刚才那两个磁盘文件*/
    int fd3 = open("./a1.txt", O_RDONLY);
    int fd4 = open("./a2.txt", O_RDONLY);
    printf("fd3 = %d\n", fd3);
    printf("fd4 = %d\n\n", fd4);

    getchar();
    return 0;
}

运行结果:

stdin = 0
stdout = 1
stderr = 2

fd1 = 3
fd2 = 4

sfd1 = 5
sfd2 = 6

close fd1 fd2
pipefd1 = 3
pipefd2 = 4

fd3 = 7
fd4 = 8

代码分析:
(1)启动程序后,首先测试标准输入、输出、错误输出的fd(File descriptor,简称fd)值,分别为0、1、2。
(2)然后测试打开磁盘文件a1.txt、a2.txt后返回的fd值,若没有这个文件,可先使用命令在测试代码目录下创建两个空文件

touch a1.txt a2.txt

测试结果显示,打开两个磁盘文件返回的fd为3、4,因为0、1、2已经被标准输入输出占用。
(3)再测试套接字的fd,创建两个套接字对象,返回的fd分别为5、6。
(4)关闭第2步打开的两个磁盘文件fd。
(5)再测试管道的fd,创建一个管道,返回的两个fd分别为3、4,这说明已经关闭的fd可再次使用。
(6)再次打开那两个磁盘文件,返回的fd分别为7、8。

分配规则总结:

(1)Linux 系统里标准输入输出、磁盘文件、套接字、管道共用一套fd编号。
(2)fd编号总是从0开始逐步递增。
(3)若某个已使用的fd被关闭释放后,可以被再次使用。

另外我们可以在/proc/(进程id)/fd目录下更直观的看到进程fd状态。
这里写图片描述

由于Linux系统将多种读写对象统一抽象为文件类型,并提供一致的接口,因此在实际开发中我们可以更为灵活的处理文件对象。
比如select/epoll等函数一般用于检测socket的读写状态,但我们也可以将其用于检测标准输入输出、管道的读写状态,这就是本文分析文件描述符的意义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值