主要内容概览:
文件描述符:系统分配给文件或套接字的整数。
标准输入输出错误的文件描述符分别为:0,1,2
文件IO函数: open close write read
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* path,int flag);
成功返回文件描述符,失败返回-1
#include <unistd.h>
int close(int fd); //fd为文件 、 套接字的描述符
成功返回0,失败返回-1
// fd:数据传输对象的文件描述符;buf:保存要传输数据的缓冲地址;nbytes:要传输数据的字节数
// 其中size_t: typedef unsigned int size_t; ssize_t: typedef signed int ssize_t
#include <unistd.h>
ssize_t write(int fd,const void * buf,size_t nbytes)
#include <unistd.h>
/** fd:显示数据接受对象的文件描述 buf:要保存的数据的缓冲地址值 nbytes:要接收数据的最大字节数*/
ssize_t read(int fd, void* buf,size_t nbytes);
成功时返回接收的字节数,(但遇到文件结尾则返回0),失败返回-1
正文
1.2 基于Linux的文件操作
对于Linux,socket操作和文件操作没有区别,被认为是文件的一种,因此要详细了解文件。
对于Windows,这俩东西是不同的,需要调用特殊的数据传输相关函数。
1.2.1 底层文件访问(Low-Lever File Access)和文件描述符(File Descriptor)
底层的含义:与标准无关!是操作系统独立提供的(这里指的就是Linux提供的)
想要使用Linux提供的文件IO函数,首先要理解文件描述符。
文件描述符:系统分配给文件或套接字的整数。
实际上,学习C语言过程中用过的标准输入输出 及 标准错误在Linux中也被分配下表中的文件描述符
¥¥¥¥这里是一个表
从上面的表我们可以看到对于标准输入输出以及错误分配的文件描述符,在接下来的编程过程中,可能会对这个返回值进行判断,以及相应的操作~
文件和套接字一般经过创建过程才会被分配文件描述符。上表的3种输入输出对象即使没有经过特殊的创建过程,程序开始运行后也会被自动分配文件描述符。(稍后详细讲解)
知识扫盲:
文件描述符(文件句柄)(在LInux下为描述符,Win下称作句柄)
是为了避免文件名过于复杂的替代品,是程序员和系统之间沟通时,文件的替代。
是为了方便称呼系统创建的文件或套接字而赋予的数字。
1.2.2 打开文件
需要两个参数:第一个是目标文件的文件名路径信息path,第二个是文件打开模式flag
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* path,int flag);
成功返回文件描述符,失败返回-1
下表示flag的可能参数取值:
1.2.3 关闭文件
#include <unistd.h>
int close(int fd); //fd为文件 、 套接字的描述符
成功返回0,失败返回-1
注意:此函数可以关闭文件,也可以关闭套接字,再次体现了Linux系统不区分文件和套接字的特点
1.2.4 将数据写入文件
通过套接字向其他计算机传递数据时也会用到该函数,之前的实例也是用它传递字符串"Hello World"(在上一节文章中Part 1 start network programming:chapter one:1.1理解网络编程和套接字 )
这一节的实例在下方~
// fd:数据传输对象的文件描述符;buf:保存要传输数据的缓冲地址;nbytes:要传输数据的字节数
// 其中size_t: typedef unsigned int size_t; ssize_t: typedef signed int ssize_t
#include <unistd.h>
ssize_t write(int fd,const void * buf,size_t nbytes)
知识扫盲:以_t为后缀的数据类型
一般为了给基本数据类型赋别名,会在头文件 sys/types.h 中大量 typedef 声明,为了同程序员定义的新数据类型区分,操作系统定义的数据类型后会添加_t
下面通过示例来理解前面的函数
文件名:low_open.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
void error_handling(char* message);
int main(void)
{
/* code */
int fd;
char buf[] = "Let's go!\n"; //写入的字符串内容
fd = open("data.txt",O_CREAT|O_WRONLY|O_TRUNC); //打开文件,创建|只写|清空
if(fd == -1){
error_handling("open() error!"); //抛出异常
}
printf("file descriptor: %d\n",fd);
if(write(fd,buf,sizeof(buf)) == -1){ //向文件fd写入,字符串buf,字节数sizeof
error_handling("write() error!");
}
close(fd); //关闭文件
return 0;
}
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
1.2.5 读取文件中的数据
与之前的write对应 这里用read函数来输入(接收)数据
#include <unistd.h>
/** fd:显示数据接受对象的文件描述 buf:要保存的数据的缓冲地址值 nbytes:要接收数据的最大字节数*/
ssize_t read(int fd, void* buf,size_t nbytes);
成功时返回接收的字节数,(但遇到文件结尾则返回0),失败返回-1
下面示例通过read函数 读取data.txt中保存的数据
文件名:low_read.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
void error_handling(char* message);
int main(void)
{
int fd;
char buf[BUF_SIZE];
fd = open("data.txt",O_RDONLY); //打开想要读取的文件 并返回文件描述符fd
if(fd == -1){
error_handling("open() error\n"); //如果打开失败,抛出异常
}
printf("file descriptor : %d\n",fd );
if(read(fd,buf,sizeof(buf)) == -1){ //读取文件fd的内容,存入buf中,sizeof字节数
error_handling("read() error!");
}
printf("file data: %s\n",buf);
close(fd); //关闭文件
return 0;
}
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
1.2.7 文件描述符与套接字
下面将同时创建文件和套接字,并用整数形态比较返回的文件描述符值
文件名:fd_seri.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
int main(void)
{
int fd1,fd2,fd3;
fd1 = socket(PF_INET,SOCK_STREAM,0); //这里创建一个文件和两个套接字
fd2 = open("test.dat",O_CREAT|O_WRONLY|O_TRUNC);
//套接字其实在Linux系统中和文件真的是一样的,在下面还要关闭它,sokect就是打开了文件
fd3 = socket(PF_INET,SOCK_DGRAM,0);
printf("file descriptor 1: %d\n",fd1);
printf("file descriptor 2: %d\n",fd2);
printf("file descriptor 3: %d\n",fd3);
close(fd1);close(fd2);close(fd3);
return 0;
}
从输出的文件描述符可以看出,从3开始,由小到大的顺序编号,因为0、1、2分配给了标准I/O的描述符