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

来源:互联网 发布:手机端如何查看源码 编辑:程序博客网 时间:2024/06/06 02:59

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 = 0stdout = 1stderr = 2fd1 = 3fd2 = 4sfd1 = 5sfd2 = 6close fd1 fd2pipefd1 = 3pipefd2 = 4fd3 = 7fd4 = 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的读写状态,但我们也可以将其用于检测标准输入输出、管道的读写状态,这就是本文分析文件描述符的意义。

0 0
原创粉丝点击