Linux系统编程(二):标准 I/O 库(下)
参考引用
- UNIX 环境高级编程 (第3版)
- 嵌入式Linux C应用编程-正点原子
1. 标准 I/O 库简介
- 标准 I/O 库是指:标准 C 库中用于文件 I/O 操作(如:读、写文件等)相关的一系列库函数的集合
- 标准 I/O 库函数相关的函数定义都在头文件 <stdio.h> 中
- 标准 I/O 库函数构建于文件 I/O(open()、read()、write()、lseek()、close()等)这些系统调用之上,如:标准 I/O 库函数 fopen() 就利用系统调用 open() 来执行打开文件的操作
为什么需要标准 I/O 库?直接使用文件 I/O 系统调用不是更好吗?
- 设计库函数是为了提供比底层系统调用更为方便、好用的调用接口,虽然标准 I/O 构建于文件 I/O 之上,但标准 I/O 却有它自己的优势
- 标准 I/O 和文件 I/O 的区别如下
- 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux 系统调用
- 标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的
- 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性
- 通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,如:系统调用的定义、功能、参数列表、返回值等往往都不同
- 而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性
- 性能、效率:标准 I/O 库在用户空间维护了自己的 stdio 缓冲区,所以标准 I/O 是带有缓存的,而文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O
2. FILE 指针
- 所有文件 I/O 函数(open()、read()、write()、lseek()等)都是围绕文件描述符进行的,当调用 open()函数打开一个文件时,即返回一个文件描述符 fd,然后该文件描述符就用于后续的 I/O 操作
- 而对于标准 I/O 库函数来说,它们的操作是围绕 FILE 指针进行的,当使用标准 I/O 库函数打开或创建一个文件时,会返回一个指向 FILE 类型对象的指针(FILE *),使用该 FILE 指针与被打开或创建的文件相关联,然后该 FILE 指针就用于后续的标准 I/O 操作
- FILE 指针的作用相当于文件描述符,FILE 指针用于标准 I/O 库函数中,而文件描述符则用于文件 I/O 系统调用中
- FILE 是一个结构体数据类型,它包含了标准 I/O 库函数为管理文件所需要的所有信息,包括用于实际 I/O 的文件描述符、指向文件缓冲区的指针、缓冲区的长度、当前缓冲区中的字节数以及出错标志等
- FILE 数据结构定义在标准 I/O 库函数头文件 stdio.h 中
3. 标准输入、标准输出和标准错误
- 用户通过标准输入设备与系统进行交互,进程将从标准输入 (stdin) 文件中得到输入数据,将正常输出数据(如:printf 打印输出)输出到标准输出(stdout) 文件,而将错误信息(如:函数调用报错)输出到标准错误 (stderr) 文件
- 标准输出文件和标准错误文件都对应终端的显示器,而标准输入文件则对应于键盘
- 每个进程启动之后都会默认打开标准输入、标准输出以及标准错误,得到三个文件描述符,即 0(标准输入)、1(标准输出)、2(标准错误),在应用编程中可以使用宏 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 分别代表 0、1、2,这些宏定义在 unistd.h 头文件中
#define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO1 /* Standard output. */ #define STDERR_FILENO2 /* Standard error output. */
- 0、1、2 是文件描述符,只能用于文件 I/O,标准 I/O 中,需围绕 FILE 类型指针进行,在 stdio.h 头文件定义
// struct _IO_FILE 结构体就是 FILE 结构体,使用 typedef 进行了重命名 extern struct _IO_FILE *stdin; /* Standard input stream. */ extern struct _IO_FILE *stdout; /* Standard output stream. */ extern struct _IO_FILE *stderr; /* Standard error output stream. *//* C89/C99 say they're macros. */ #define stdin stdin #define stdout stdout #define stderr stderr
4. 打开文件 fopen()、关闭文件 fclose()
- 标准 I/O 中,将使用库函数 fopen() 打开或创建文件
#include <stdio.h>// path:参数 path 指向文件路径,可以是绝对路径或相对路径 // mode:参数 mode 指定了对该文件的读写权限,是一个字符串 // 返回值:调用成功返回一个指向 FILE 类型对象的指针(FILE *) FILE *fopen(const char *path, const char *mode);
- 参数 mode 字符串类型,可取值如下
- 虽然调用 fopen()函数新建文件时无法手动指定文件的权限,但却有一个默认值
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)
- 调用 fclose() 库函数可以关闭一个由 fopen() 打开的文件
#include <stdio.h>// 参数 stream 为 FILE 类型指针,调用成功返回 0;失败将返回 EOF(也就是-1) int fclose(FILE *stream);
5. 读文件和写文件
- 可以使用 fread() 和 fwrite() 库函数对文件进行读、写操作
#include <stdio.h>/* ptr:fread() 将读取到的数据存放在参数 ptr 指向的缓冲区中size:fread() 从文件读取 nmemb 个数据项,每一个数据项大小为 size 个字节,所以总共读取 nmemb * size 个字节数据nmemb:参数 nmemb 指定了读取数据项的个数stream:FILE 指针 */ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); /* ptr:将参数 ptr 指向的缓冲区中的数据写入到文件中size:参数 size 指定了每个数据项的字节大小,与 fread() 函数的 size 参数意义相同nmemb:参数 nmemb 指定了写入的数据项个数,与 fread() 函数的 nmemb 参数意义相同stream:FILE 指针 */ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
5.1 使用 fwrite() 将数据写入到文件中
#include <stdio.h>
#include <stdlib.h>int main(void) {char buff[] = "Hello World!\n";FILE *fp = NULL;if ((fp = fopen("./test_file", "w")) == NULL) {perror("fopen error");exit(-1);} printf("open success\n");if (fwrite(buff, 1, sizeof(buff), fp) < sizeof(buff)) {printf("fwrite error\n");fclose(fp);exit(-1);} printf("write success\n");fclose(fp);exit(0);
}
$ gcc fw.c -o fw
$ ./fw
open success
write success$ cat test_file
Hello World!
5.2 使用 fread() 从文件中读取数据
#include <stdio.h>
#include <stdlib.h>int main(void) {char buf[50] = {0};FILE *fp = NULL;int size;/* 只读方式打开文件 */if ((fp = fopen("./test_file", "r")) == NULL) {perror("fopen error");exit(-1);} printf("文件打开成功!\n");/* 读取 12 * 1=12 个字节的数据 */if ((size = fread(buf, 1, 12, fp)) < 12) {if (ferror(fp)) { //使用 ferror 判断是否是发生错误printf("fread error\n");fclose(fp);exit(-1);} /* 如果未发生错误则意味着已经到达了文件末尾 */} printf("成功读取%d 个字节数据: %s\n", size, buf);/* 关闭文件 */fclose(fp);exit(0);
}
$ gcc fr.c -o fr
$ ./fr
文件打开成功!
成功读取12 个字节数据: Hello World!$ cat test_file
Hello World!
6. fseek 定位
- 库函数 fseek() 的作用类似系统调用 lseek(),用于设置文件读写位置偏移量
- 调用库函数 fread()、fwrite() 读写文件时,文件的读写位置偏移量会自动递增,使用 fseek() 可手动设置文件当前的读写位置偏移量
#include <stdio.h>// stream:FILE 指针 // offset:与 lseek() 函数的 offset 参数意义相同 // whence:与 lseek() 函数的 whence 参数意义相同 int fseek(FILE *stream, long offset, int whence);
6.1 使用 fseek() 调整文件读写位置
#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;char rd_buf[100] = {0};char wr_buf[] = "www.baidu.com\n";int ret;/* 打开文件 */if ((fp = fopen("./test_file", "w+")) == NULL) {perror("fopen error");exit(-1);} printf("open seccess\n");/* 写文件 */if (fwrite(wr_buf, 1, sizeof(wr_buf), fp) < sizeof(wr_buf)) {printf("fwrite error\n");fclose(fp);exit(-1);} printf("write success\n");/* 将读写位置移动到文件头部 */if (fseek(fp, 0, SEEK_SET) < 0) {perror("fseek error\n");fclose(fp);exit(-1);} /* 读文件 */if ((ret = fread(rd_buf, 1, sizeof(wr_buf), fp)) < sizeof(wr_buf)) {printf("fread error\n");fclose(fp);exit(-1);} printf("成功读取 %d 个字节数据: %s\n", ret, rd_buf);/* 关闭文件 */fclose(fp);exit(0);
}
$ gcc fseek.c -o fseek
$ ./fseek
open seccess
write success
成功读取 14 个字节数据: www.baidu.com
7. ftell() 函数
- 库函数 ftell() 可用于获取文件当前的读写位置偏移量
#include <stdio.h>// 参数 stream 指向对应的文件,函数调用成功将返回当前读写位置偏移量;调用失败将返回 -1 long ftell(FILE *stream);
7.1 使用 fseek() 和 ftell() 获取文件大小
#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;int ret;/* 打开文件 */if ((fp = fopen("./testApp.c", "r")) == NULL) {perror("fopen error");exit(-1);}printf("open success\n");/* 将读写位置移动到文件末尾 */if (fseek(fp, 0, SEEK_END) < 0>) {perror("fseek error");fclose(fp);exit(-1);}/* 获取当前位置偏移量,也就得到了 testApp.c 整个文件的大小 */if ((ret = ftell(fp)) < 0) {perror("ftell error");fclose(fp);exit(-1);} printf("file size: %d\n", ret);/* 关闭文件 */fclose(fp);exit(0);
}
$ gcc ftell.c -o ftell
$ ./ftell
open success
file size: 13
8. 检查或复位状态
8.1 feof() 函数
- 库函数 feof() 用于测试参数 stream 所指文件的 end-of-file 标志
- 如果 end-of-file 标志被设置了,则调用 feof() 函数将返回一个非零值
- 如果 end-of-file 标志没有被设置,则返回 0
#include <stdio.h>int feof(FILE *stream);
// 当文件的读写位置移动到了文件末尾时,end-of-file 标志将会被设置 if (feof(file)) {/* 到达文件末尾 */ } else {/* 未到达文件末尾 */ }
8.2 ferror() 函数
- 库函数 ferror() 用于测试参数 stream 所指文件的错误标志
- 如果错误标志被设置了,则调用 ferror() 函数将返回一个非零值
- 如果错误标志没有被设置,则返回 0
#include <stdio.h>int ferror(FILE *stream);
// 当对文件的 I/O 操作发生错误时,错误标志将会被设置 if (ferror(file)) {/* 发生错误 */ } else {/* 未发生错误 */ }
8.3 clearerr() 函数
- 库函数 clearerr() 用于清除 end-of-file 标志和错误标志
- 当调用 feof() 或 ferror() 校验这些标志后,通常需要清除这些标志,避免下次校验时使用到的是上一次设置的值,此时可以手动调用 clearerr() 函数清除标志
- 对于 end-of-file 标志,除使用 clearerr() 显式清除外,当调用 fseek() 成功时也会清除文件的 end-of-file 标志
#include <stdio.h>void clearerr(FILE *stream);
- 示例
#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;char buf[20] = {0};/* 打开文件 */if ((fp = fopen("./testApp.c", "r")) == NULL) {perror("fopen error");exit(-1);}printf("文件打开成功!\n");/* 将读写位置移动到文件末尾 */if (fseek(fp, 0, SEEK_END) < 0) {perror("fseek error");fclose(fp);exit(-1);}/* 读文件 */if (fread(buf, 1, 10, fp) < 10) {if (feof(fp))printf("end-of-file 标志被设置, 已到文件末尾!\n");clearerr(fp); // 清除标志}/* 关闭文件 */fclose(fp);exit(0);
}
9. 格式化 I/O
9.1 格式化输出
- C 库函数提供了 5 个格式化输出函数,包括:printf()、fprintf()、dprintf()、sprintf()、snprintf()
#include <stdio.h>int printf(const char *format, ...);int fprintf(FILE *stream, const char *format, ...); int dprintf(int fd, const char *format, ...);int sprintf(char *buf, const char *format, ...); int snprintf(char *buf, size_t size, const char *format, ...);
- 这 5 个函数都是可变参函数,它们都有一个共同的参数 format,这是一个字符串,称为格式控制字符串,用于指定后续的参数如何进行格式转换,所以才把这些函数称为格式化输出
- printf() 函数用于将格式化数据写入到标准输出
printf("Hello World!\n"); printf("%d\n", 5);
- dprintf() 和 fprintf() 函数用于将格式化数据写入到指定的文件中,两者不同之处在于,fprintf() 使用 FILE 指针指定对应的文件,而 dprintf()则使用文件描述符 fd 指定对应的文件
fprintf(stderr, "Hello World!\n"); fprintf(stderr, "%d\n", 5);dprintf(STDERR_FILENO, "Hello World!\n"); dprintf(STDERR_FILENO, "%d\n", 5);
- sprintf()、snprintf() 函数可将格式化的数据存储在用户指定的缓冲区 buf 中
char buf[100]; sprintf(buf, "Hello World!\n");// 一般会使用这个函数进行格式化转换,并将转换后的字符串存放在缓冲区中 // 如:将数字 100 转换为字符串 "100",将转换后得到的字符串存放在 buf 中 // sprintf() 函数会在字符串尾端自动加上一个字符串终止字符 '\0' char buf[20] = {0}; sprintf(buf, "%d", 100);
sprintf() 函数可能会造成由参数 buf 指定的缓冲区溢出,因为缓冲区溢出会造成程序不稳定甚至安全隐患,为解决这个问题,引入 snprintf() 函数,在该函数中,使用参数 size 显式的指定缓冲区的大小
- 如果写入到缓冲区的字节数大于参数 size 指定的大小,超出的部分将会被丢弃
- 如果缓冲区空间足够大,snprintf() 函数就会返回写入到缓冲区的字符数
9.2 格式控制字符串 format:输出
- 格式控制字符串由两部分组成:普通字符(非 % 字符)和转换说明
- 普通字符会进行原样输出,每个转换说明都会对应后续的一个参数,通常有几个转换说明就需要提供几个参数(除固定参数之外的参数),使之一一对应,用于控制对应的参数如何进行转换
printf("转换说明 1 转换说明 2 转换说明 3", arg1, arg2, arg3);
- 每个转换说明都是以 % 字符开头,其格式如下所示(使用 [] 括起来的部分是可选的)
/*flags:标志,用于规定输出样式,可包含 0 个或多个标志width:输出最小宽度,表示转换后输出字符串的最小宽度precision:精度,前面有一个点号 "."length:长度修饰符type:转换类型,指定待转换数据的类型 */ %[flags][width][.precision][length]type
9.2.1 type 类型
- type 用于指定输出数据的类型,type 字段使用一个字符(字母字符)来表示
9.2.2 flags 样式标志
- flags 规定输出样式,% 后面可以跟 0 个或多个以下标志
9.2.3 width 输出宽度
- 最小的输出宽度,用十进制数来表示输出的最小位数
- 若实际的输出位数大于指定的输出的最小位数,则以实际的位数进行输出
- 若实际的位数小于指定输出的最小位数,则可按照指定的 flags 标志补 0 或补空格
9.2.4 precision 精度
- 精度字段以点号 “.” 开头,后跟一个十进制正数,可取值如下
9.2.5 length 长度修饰符
- 长度修饰符指明待转换数据的长度,因为 type 字段指定的的类型只有 int、unsigned int 以及 double 等几种数据类型,但是 C 语言内置的数据类型不止这几种,如:16bit 的 short、unsigned short,8bit 的 char、unsigned char,64bit 的 long long 等,为了能够区别不同长度的数据类型,于是长度修饰符(length)应运而生
- length 长度修饰符也是使用字符(字母字符)来表示,结合 type 字段以确定不同长度的数据类型
printf("%hd\n", 12345); // 将数据以 short int 类型进行转换 printf("%ld\n", 12345); // 将数据以 long int 类型进行转换 printf("%lld\n", 12345); // 将数据以 long long int 类型进行转换
9.3 格式化输入
- C 库函数提供了 3 个格式化输入函数,包括:scanf()、fscanf()、sscanf()
#include <stdio.h>int scanf(const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int sscanf(const char *str, const char *format, ...);
- 这 3 个格式化输入函数也是可变参函数,它们都有一个共同的参数 format,同样也称为格式控制字符串,用于指定输入数据如何进行格式转换
- scanf() 函数可将用户输入(标准输入)的数据进行格式化转换
- 当程序中调用 scanf() 的时候,终端会被阻塞,等待用户输入数据,此时可以通过键盘输入一些字符,如:数字、字母或者其它字符,输入完成按回车即可
int a, b, c; scanf("%d %d %d", &a, &b, &c);
- fscanf() 函数从 FILE 指针指定文件中读取数据,并将数据进行格式化转换
- 标准输入文件的数据就是用户输入的数据,如:通过键盘输入的数据
int a, b, c; fscanf(stdin, "%d %d %d", &a, &b, &c);
- sscanf() 函数从参数 str 所指向的字符串中读取数据,并将数据进行格式化转换
char *str = "5454 hello"; char buf[10]; int a;sscanf(str, "%d %s", &a, buf);
9.4 格式控制字符串 format:输入
- format 字符串包含一个或多个转换说明,每一个转换说明都是以百分号 “%”,转换说明格式如下
/*width:最大字符宽度length:长度修饰符,与格式化输出函数的 format 相同type:指定输入数据的类型 */ %[*][width][length]type %[m][width][length]type
- % 后面可选择性添加星号 * 或字母 m
- 如果添加了星号*,格式化输入函数会按照转换说明的指示读取输入,但是丢弃输入,意味着不需要对转换后的结果进行存储,所以也就不需要提供相应的指针参数
- 如果添加了 m,它只能与 %s、%c 以及 %[ 一起使用,调用者无需分配相应的缓冲区来保存格式转换后的数据,原因在于添加了 m,这些格式化输入函数内部会自动分配足够大小的缓冲区,并将缓冲区的地址值通过与该格式转换相对应的指针参数返回出来,该指针参数应该是指向 char* 变量的指针。随后,当不再需要此缓冲区时,调用者应调用 free() 函数来释放此缓冲区
char *buf;scanf("%ms", &buf); ......free(buf);
9.4.1 type 类型
- 此 type 字段与格式化输出函数中的 format 参数的 type 字段是同样的意义,用于指定输入数据的类型
9.4.2 width 最大字符宽度
- 是一个十进制表示的整数,用于指定最大字符宽度,当达到此最大值或发现不匹配的字符时(以先发生者为准),字符的读取将停止。大多数 type 类型会丢弃初始的空白字符,并且这些丢弃的字符不会计入最大字符宽度。对于字符串转换来说,scanf() 会在字符串末尾自动添加终止符 “\0”,最大字符宽度中不包括此终止符
scanf("%4s", buf); // 匹配字符串,字符串长度不超过 4 个字符 // 用户输入 abcdefg,按回车,那么只能将 adcd 作为一个字符串存储在 buf 数组中
9.4.3 length 长度修饰符
- 与格式化输出函数的格式控制字符串 format 中的 length 字段意义相同,用于对 type 字段进行修饰,扩展识别更多不同长度的数据类型
scanf("%hd", var); // 匹配 short int 类型数据 scanf("%hhd", var); // 匹配 signed char 类型数据 scanf("%ld", var); // 匹配 long int 类型数据 scanf("%f", var); // 匹配 float 类型数据 scanf("%lf", var); // 匹配 double 类型数据 scanf("%Lf", var); // 匹配 long double 类型数据
10. I/O 缓冲
- 1、首先,应用程序调用标准 I/O 库函数将用户数据写入到 stdio 缓冲区中,stdio 缓冲区是由 stdio 库所维护的用户空间缓冲区
- 2、然后,针对不同的缓冲模式,当满足条件时,stdio 库会调用文件 I/O(系统调用 I/O)将 stdio 缓冲区中缓存的数据写入到内核缓冲区中,内核缓冲区位于内核空间
- 3、最终,由内核向磁盘设备发起读写操作,将内核缓冲区中的数据写入到磁盘(或者从磁盘设备读取数据到内核缓冲区)
10.1 文件 I/O 的内核缓冲
- read() 和 write() 系统调用在进行文件读写操作时并不会直接访问磁盘设备,而是仅仅在用户空间缓冲区和内核缓冲区之间复制数据。如:调用 write() 函数将 5 个字节数据从用户空间内存拷贝到内核空间的缓冲区中
write(fd, "Hello", 5); // 写入 5 个字节数据
- 调用 write() 后仅仅只是将这 5 个字节数据拷贝到了内核空间的缓冲区中,拷贝完成之后函数就返回了,在后面的某个时刻,内核会将其缓冲区中的数据写入(刷新)到磁盘设备中
- 由此可知,系统调用 write() 与磁盘操作并不是同步的,write() 函数并不会等待数据真正写入到磁盘之后再返回
- 如果在此期间,其它系统调用如 read() 函数读取该文件的这几个字节数据,那么内核将自动从缓冲区中读取这几个字节数据返回给应用程序
对于读文件而言亦是如此,内核会从磁盘设备中读取文件数据并存储到内核缓冲区中,当调用 read() 读取数据时,read() 调用将从内核缓冲区中读取数据,直至把缓冲区中的数据读完,这时,内核会将文件的下一段内容读入到内核缓冲区中进行缓存,把这个内核缓冲区就称为文件 I/O 的内核缓冲
- 文件 I/O 的内核缓冲区的设计目的?
- 1. 提高文件 I/O 的速度和效率(使得系统调用 read()、write()的操作更为快速)
- 不需要等待磁盘操作(将数据写入到磁盘或从磁盘读取出数据),磁盘操作通常是比较缓慢的
- 2. 减少内核操作磁盘的次数
- 如:线程 1 调用 write() 向文件写入 “abcd”,线程 2 也调用 write() 向文件写入 “1234”,这样,数据 “abcd” 和 “1234” 都被缓存在内核缓冲区中,稍后内核会将它们一起写入磁盘中,只发起一次磁盘操作请求
文件 I/O 的内核缓冲区自然是越大越好,内核会分配尽可能多的内核来作为文件 I/O 的内核缓冲区,但受限于物理内存的总量,操作越大的文件也要依赖于更大空间的内核缓冲区
- 1. 提高文件 I/O 的速度和效率(使得系统调用 read()、write()的操作更为快速)
10.2 刷新文件 I/O 的内核缓冲区
- 强制将文件 I/O 内核缓冲区中缓存的数据写入(刷新)到磁盘设备中,对于某些应用程序来说是很有必要的
- 例如,应用程序在进行某操作之前,必须要确保前面步骤调用 write() 写入到文件的数据已经真正写入到了磁盘中,诸如一些数据库的日志进程
- 在 Ubuntu 系统下拷贝文件到 U 盘时:文件拷贝完成之后,通常在拔掉 U 盘之前,需要执行 sync 命令进行同步操作,这个同步操作其实就是将文件 I/O 内核缓冲区中的数据更新到 U 盘硬件设备,所以如果在没有执行 sync 命令时拔掉 U 盘,很可能就会导致拷贝到 U 盘中的文件遭到破坏
10.2.1 控制文件 I/O 内核缓冲的系统调用
-
fsync() 函数
- 系统调用 fsync() 将参数 fd 所指文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync() 函数才会返回
- 元数据并不是文件内容本身的数据,而是一些用于记录文件属性相关的数据信息,如:文件大小、时间戳、权限等等信息,这里统称为文件的元数据,这些信息也是存储在磁盘设备中的
#include <unistd.h>int fsync(int fd);
- 示例
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>#define BUF_SIZE 4096 #define READ_FILE "./rfile" #define WRITE_FILE "./wfile"static char buf[BUF_SIZE];int main(void) {int rfd, wfd;size_t size;/* 打开源文件 */rfd = open(READ_FILE, O_RDONLY);if (rfd < 0) {perror("open error");exit(-1);}/* 打开目标文件 */wfd = open(WRITE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0664);if (wfd < 0) {perror("open error");exit(-1);}/* 拷贝数据 */while((size = read(rfd, buf, BUF_SIZE)) > 0)write(wfd, buf, size);/* 对目标文件执行 fsync 同步 */fsync(wfd);/* 关闭文件退出程序 */close(rfd);close(wfd);exit(0); }
-
fdatasync() 函数
- 系统调用 fdatasync()与 fsync()类似,不同之处在于 fdatasync() 仅将参数 fd 所指文件的内容数据写入磁盘,并不包括文件的元数据
#include <unistd.h>int fdatasync(int fd);
-
sync() 函数
- 系统调用 sync() 会将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中,该函数没有参数、也无返回值,意味着它不是对某一个指定的文件进行数据更新,而是刷新所有文件 I/O 内核缓冲区
#include <unistd.h>void sync(void);
10.2.2 控制文件 I/O 内核缓冲的标志
- O_DSYNC 标志
- 在调用 open() 函数时,指定 O_SYNC 标志,使得每个 write() 调用都会自动将文件内容数据和元数据刷新到磁盘设备中,其效果类似于在每个 write() 调用之后调用 fsync() 函数进行数据同步
fd = open(filepath, O_WRONLY | O_SYNC);
在程序中频繁调用 fsync()、fdatasync()、sync()(或者调用 open 时指定 O_DSYNC 或 O_SYNC 标志)对性能的影响极大,大部分的应用程序是没有这种需求的
10.3 直接 I/O:绕过内核缓冲
-
Linux 允许应用程序在执行文件 I/O 操作时绕过内核缓冲区,从用户空间直接将数据传递到文件或磁盘设备,把这种操作也称为直接 I/O(direct I/O)或裸 I/O(raw I/O)
- 例如,某应用程序的作用是测试磁盘设备的读写速率,那么在这种应用需要下,就需要保证 read/write 操作是直接访问磁盘设备,而不经过内核缓冲
- 对于大多数应用程序而言,使用直接 I/O 可能会大大降低性能
- 直接 I/O 只在一些特定的需求场合,如:磁盘速率测试工具、数据库系统等
- 可针对某一文件或块设备执行直接 I/O,需要在调用 open() 函数打开文件时指定 O_DIRECT 标志
fd = open(filepath, O_WRONLY | O_DIRECT);
-
因为直接 I/O 涉及到对磁盘设备的直接访问,所以在执行直接 I/O 时,必须要遵守三个对齐限制要求
- 应用程序中用于存放数据的缓冲区,其内存起始地址必须以块大小的整数倍进行对齐
- 写文件时,文件的位置偏移量必须是块大小的整数倍
- 写入到文件的数据大小必须是块大小的整数倍
-
以上所说的块大小指的是磁盘设备的物理块大小,常见的块大小包括 512 字节、1024 字节、2048 以及 4096 字节,如何确定磁盘分区的块大小?通常,Ubuntu 系统的根文件系统挂载在 /dev/sda1 磁盘分区下
$ sudo tune2fs -l /dev/sda1 | grep "Block size" Block size: 4096
10.4 stdio 缓冲
- 标准 I/O 是 C 语言标准库函数,而文件 I/O 是系统调用,虽然标准 I/O 是在文件 I/O 基础上进行封装而实现,但在效率、性能上,标准 I/O 要优于文件 I/O,原因在于标准 I/O 维护了自己的缓冲区,称为 stdio 缓冲区
- 文件 I/O 内核缓冲,这是由内核维护的缓冲区,而标准 I/O 所维护的 stdio 缓冲是用户空间缓冲区
- 当应用程序中通过标准 I/O 操作磁盘文件时,标准 I/O 函数会将用户写入或读取文件的数据缓存在 stdio 缓冲区,然后再一次性将 stdio 缓冲区中缓存的数据通过调用系统调用 I/O(文件 I/O)写入到文件 I/O 内核缓冲区或拷贝到应用程序的 buf 中
10.4.1 对 stdio 缓冲进行设置
1. setvbuf() 函数
-
调用 setvbuf() 库函数可以对文件的 stdio 缓冲区进行设置,如:缓冲区的缓冲模式、缓冲区的大小、起始地址等
#include <stdio.h>int setvbuf(FILE *stream, char *buf, int mode, size_t size);
-
stream
- FILE 指针,用于指定对应的文件,每一个文件都可以设置它对应的 stdio 缓冲区
-
buf
- 如果参数 buf 不为 NULL,那么 buf 指向 size 大小的内存区域将作为该文件的 stdio 缓冲区,因为 stdio 库会使用 buf 指向的缓冲区,所以应该以动态(分配在堆内存,如 malloc)或静态的方式在堆中为该缓冲区分配一块空间,而不是分配在栈上的函数内的自动变量(局部变量)
- 如果 buf 等于 NULL,那么 stdio 库会自动分配一块空间作为该文件的 stdio 缓冲区(除非参数 mode 配置为非缓冲模式)
-
mode:用于指定缓冲区的缓冲类型
- _IONBF:不对 I/O 进行缓冲(无缓冲)。意味着每个标准 I/O 函数将立即调用 write() 或者 read(),并且忽略 buf 和 size 参数,可以分别指定两个参数为 NULL 和 0。标准错误 stderr 默认属于这一种类型,从而保证错误信息能够立即输出
- _IOLBF:采用行缓冲 I/O。这种情况下,当在输入或输出中遇到换行符 “\n” 时,标准 I/O 才会执行文件 I/O 操作
- 对于输出流,在输出一个换行符前将数据缓存(除非缓冲区已经被填满),当输出换行符时,再将这一行数据通过文件 I/O write() 函数刷入到内核缓冲区中
- 对于输入流,每次读取一行数据。对于终端设备默认采用的就是行缓冲模式,譬如标准输入和标准输出
- _IOFBF:采用全缓冲 I/O。这种情况下,在填满 stdio 缓冲区后才进行文件 I/O 操作(read、write)
- 对于输出流,当 fwrite 写入文件的数据填满缓冲区时,才调用 write() 将 stdio 缓冲区中数据刷入内核缓冲区
- 对于输入流,每次读取 stdio 缓冲区大小个字节数据。默认普通磁盘上的常规文件常用这种缓冲模式
-
size:指定缓冲区的大小
-
返回值:成功返回 0,失败将返回一个非 0 值,并设置 errno 指示错误原因
当 stdio 缓冲区中的数据被刷入到内核缓冲区或被读取之后,这些数据就不会存在于缓冲区中了,数据被刷入了内核缓冲区或被读走了
2. setbuf() 函数
- setbuf() 函数构建于 setvbuf() 之上,执行类似的任务
#include <stdio.h>void setbuf(FILE *stream, char *buf);
- setbuf() 调用除了不返回函数结果(void)外,就相当于
// 要么将 buf 设置为 NULL 以表示无缓冲 // 要么指向由调用者分配的 BUFSIZ 个字节大小的缓冲区(BUFSIZ 定义于头文件 <stdio.h> 中,该值通常为 8192) setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
3. setbuffer() 函数
- setbuffer() 函数类似于 setbuf(),但允许调用者指定 buf 缓冲区的大小
#include <stdio.h>void setbuffer(FILE *stream, char *buf, size_t size);
- setbuffer() 调用除了不返回函数结果(void)外,就相当于
setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
10.4.2 标准输出 printf() 的行缓冲模式测试
- 标准 printf() 输出测试
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");for ( ; ; )sleep(1); }
$ gcc io2.c -o io2 $ ./io2 Hello World! # 只有第一个 printf() 打印的信息显示出来了,第二个并没有显示出来
这就是 stdio 缓冲的问题,标准输出默认采用的是行缓冲模式,printf() 输出的字符串写入到了标准输出的 stdio 缓冲区中,只有输出换行符时(不考虑缓冲区填满的情况)才会将这一行数据刷入到内核缓冲区,也就是写入标准输出文件(终端设备)
- 第一个 printf 包含了换行符,所以已经刷入了内核缓冲区
- 第二个 printf 没有包含换行符,所以输出的 “Hello World!” 还缓存在 stdio 缓冲区中,需要等待一个换行符才可输出到终端
- 将标准输出配置为无缓冲模式
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {/* 将标准输出设置为无缓冲模式 */if (setvbuf(stdout, NULL, _IONBF, 0)) {perror("setvbuf error");exit(0);}printf("Hello World!\n");printf("Hello World!");for ( ; ; )sleep(1); }
$ gcc io3.c -o io3 $ ./io3 Hello World! Hello World!
10.4.3 刷新 stdio 缓冲区
- 无论采取何种缓冲模式,在任何时候都可以使用库函数 fflush() 来强制刷新(将输出到 stdio 缓冲区中的数据写入到内核缓冲区)stdio 缓冲区,该函数会刷新指定文件的 stdio 输出缓冲区
#include <stdio.h>// 参数 stream 指定需要进行强制刷新的文件,如果该参数设置为 NULL,则表示刷新所有的 stdio 缓冲区 int fflush(FILE *stream);
- 使用 fflush() 刷新 stdio 缓冲区
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");fflush(stdout); // 刷新标准输出 stdio 缓冲区for ( ; ; )sleep(1); }
$ gcc io4.c -o io4 $ ./io4 Hello World! Hello World!
- 使用库函数 fflush() 是一种强制刷新的手段,在一些其它的情况下,也会自动刷新 stdio 缓冲区
- 关闭文件时系统自动刷新 stdio 缓冲区
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");fclose(stdout); // 关闭标准输出for ( ; ; )sleep(1); }
上面的测试程序中,在最后都使用了一个 for 死循环,让程序处于休眠状态无法退出,为什么要这样做呢?原因在于程序退出时也会自动刷新 stdio 缓冲区,这样的话就会影响到测试结果,下面去掉 for 死循环,让程序结束
- 程序退出时系统自动刷新 stdio 缓冲区
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!"); }
$ gcc io5.c -o io5 $ ./io5 Hello World! Hello World! $
如果使用 exit()、return 或像上述示例代码一样不显式调用相关函数或执行 return 语句来结束程序,这些情况下程序终止时会自动刷新 stdio 缓冲区,但是如果使用 _exit 或 _Exit() 终止程序则不会自动刷新 stdio 缓冲区
11. 文件描述符与 FILE 指针互转
- 在同一个文件上执行 I/O 操作时,可以将文件 I/O(系统调用 I/O)与标准 I/O 混合使用,这个时候就需要将文件描述符和 FILE 指针对象之间进行转换,此时可以借助于库函数 fdopen()、fileno() 来完成
- 库函数 fileno() 可以将标准 I/O 中使用的 FILE 指针转换为文件 I/O 中所使用的文件描述符
- 而 fdopen() 则进行着相反的操作
#include <stdio.h>int fileno(FILE *stream); FILE *fdopen(int fd, const char *mode);
- 对于 fileno() 函数来说
- 根据传入的 FILE 指针得到整数文件描述符,通过返回值得到文件描述符
- 如果转换错误将返回 -1,并且会设置 errno 来指示错误原因
- 得到文件描述符之后,便可以使用诸如 read()、write()、lseek()、fcntl()等文件 I/O 方式操作文件
- fdopen() 函数与 fileno() 功能相反
- 给定一个文件描述符,得到该文件对应的 FILE 指针
- 之后便可以使用诸如 fread()、fwrite() 等标准 I/O 方式操作文件了
当混合使用文件 I/O 和标准 I/O 时,需要特别注意缓冲的问题
- 文件 I/O 会直接将数据写入到内核缓冲区进行高速缓存
- 标准 I/O 则会将数据写入到 stdio 缓冲区,之后再调用 write() 将 stdio 缓冲区中的数据写入到内核缓冲区
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(void) {printf("print"); // 缺换行符 "\n"write(STDOUT_FILENO, "write\n", 6);exit(0);
}
$ gcc test.c -o test
$ ./test
write # 先输出了 "write" 字符串信息,接着再输出了 "print" 字符串信息
print $
相关文章:
Linux系统编程(二):标准 I/O 库(下)
参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 标准 I/O 库简介 标准 I/O 库是指:标准 C 库中用于文件 I/O 操作(如:读、写文件等)相关的一系列库函数的集合 标准 I/O 库函数相关的函数定义都在头文件 &…...
Mr. Cappuccino的第65杯咖啡——MacOS安装Docker
MacOS安装Docker 下载Docker安装Docker查看Docker相关信息镜像加速 下载Docker Docker官网 Docker文档中心 Docker桌面版下载地址 安装Docker 查看Docker相关信息 docker --versiondocker info镜像加速 阿里云镜像加速器 "registry-mirrors": ["https://gq8…...
解决 Docker Hub 国内无法访问的方法(Docker 镜像下载加速)
参考文章: 知乎:解决目前Docker Hub国内无法访问方法汇总 docker配置 修改配置文件 vim /etc/docker/daemon.json配置内容如下: {"builder": {"gc": {"defaultKeepStorage": "20GB","enab…...
(第61天)多租户架构(CDB/PDB)
背景介绍 Oracle 的 CDB 和 PDB 是 Oracle 12C 及以上版本中引入的新概念,用于管理多租户数据库环境。 Oracle 数据库是商业数据库领域中的翘楚,其强大的功能和高可靠性备受企业用户追捧。而随着云计算和大数据时代的到来,Oracle 也不断推出新的技术以适应这些变化。CDB 技…...
【自定义Source、Sink】Flink自定义Source、Sink对ClickHouse进行读和批量写操作
ClickHouse官网文档 Flink 读取 ClickHouse 数据两种驱动 ClickHouse 官方提供Clickhouse JDBC.【建议使用】第3方提供的Clickhouse JDBC. ru.yandex.clickhouse.ClickHouseDriver ru.yandex.clickhouse.ClickHouseDriver.现在是没有维护 ClickHouse 官方提供Clickhouse JDBC…...
linux 查看服务启动时间
文章目录 linux 查看服务启动时间参数解析 linux 查看服务启动时间 [root104 ~]# ps -o lstart -p ps -ef |grep -v grep |grep "zookeeper"|awk {print$2}STARTED Fri Dec 15 16:54:10 2023参数解析 linux 命令中 ps -ef 详解 ps -ef表示查看全格式的进程。 ps …...
[RK-Linux] 移植Linux-5.10到RK3399(六)| 检查GMAC(RTL8211F)配置使能千兆以太网
ROC-RK3399-PC Pro 使用 RTL8211F PHY 芯片作为以太网收发器。 RTL8211F是一种高性能的千兆以太网物理层收发器(PHY),广泛用于台式机、笔记本电脑、网络交换机等设备中。主要特点: 采用低功耗28nm CMOS技术,功耗低。支持千兆速率(10/100/1000Mbps)。支持全双工和半双工…...
博途WinCC专业版C/S架构入门指南
WinCC Professional V16 支持客户机/服务器架构,但目前只支持单个服务器或单对冗余服务器/多个客户机的模式,还不能支持像WinCC V7.5 SP1中的多个服务器/多个客户机的分布式架构。 博途工控人平时在哪里技术交流博途工控人社群 博途工控人平时在哪里技…...
大数据生态圈kafka在物联网中的应用测试
背景 由物联网项目中使用到了Tbox应用管理车辆,在上报数据的过程中,需要将终端产生的数据通过kafka的produce topic customer对数据进行处理后,放置到mysql中。完成数据二进制到json转换工作。 Kafka的使用 查看kafka的topic ./kafka-topi…...
ChatGPT使用:一个发包机器人的提示词
发包机器人: 设想:目前项目组有n条打包线会输出多个包,用户想获取最新的包是比较困难的,难点在于 1. 分支多:trunk,release,outer等,至少有3个分支; 2. 多平台&#x…...
Axure元件库的使用
1.基本元件库 1.1Axure的画布范围 Axure是一个绘制项目原型图的软件,它里面的基本原件有: 1.1元件的呈现范围 首先我们要了解基本元件的作用范围在哪里? 浏览效果: 可以看出当我们的基本元件放在画布区域内是可以完全呈现出来…...
Unity中Shader URP最简Shader框架(整理总结篇)
文章目录 前言一、精简 ShaderGraph 所有冗余代码后的最简 URP Shader二、我们来对比一下 URP Shader 与 BuildInRP Shader 的对应关系 与 区别1、"RenderPipeline""UniversalPipeline"2、面片剔除、深度测试、深度写入、颜色混合 和 BRP 下一致3、必须引入…...
AT32F435飞控之DIATONE MAMBA MK5 F435 Anti-Interference
AT32F435飞控之DIATONE MAMBA MK5 F435 Anti-Interference 1. 源由2. 规格3. 分析3.1 喜欢3.2 不便3.3 建议 4. 总结5. 参考资料 1. 源由 AT32 F435飞控在xFlight开源飞控之AT32F435计划一文中已经大体阐述了一些移植历史。 之前整体上看,就是航模飞控新MCU的移植…...
ntp时间同步配置中 server、pool和peer的区别
在 NTP(Network Time Protocol)的配置中,server、pool 和 peer 是用于指定时间同步关系的关键字,它们在角色和行为上有一些区别。 server: server 关键字用于指定一个或多个 NTP 服务器,这些服务器将提供时…...
JMeter安装RabbitMQ测试插件
整体流程如下:先下载AMQP插件源码,可以通过antivy在本地编译成jar包,再将jar包导入JMeter目录下,重启JMeter生效。 Apache Ant 是一个基于 Java 的构建工具。Ant 可用于自动化构建和部署 Java 应用程序,使开发人员更轻…...
基于ssm日用品网站设计论文
摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本日用品网站就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息&…...
coco数据集格式的RandomCrop
transforms.py文件的改进 添加 RandomCrop 函数 class RandomCrop(object):"""随机裁剪图像以及bboxes"""def __init__(self, output_size):self.output_size output_sizedef __call__(self, image, target):height, width image.shape[-2:]…...
机器学习-KL散度的直观理解+代码
KL散度 直观理解:KL散度是一种衡量两个分布之间匹配程度的方法。通常在概率和统计中,我们会用更简单的近似分布来代替观察到的数据或复杂的分布,KL散度帮我们衡量在选择近似值时损失了多少信息。 在信息论或概率论中,KL散度&#…...
【教程】制作 iOS 推送证书
目录 证书类型 MAC Key Store 消息推送控制台 制作证书 创建苹果 App ID 使用appuploder制作 .p12文件 创建证书 如需向 iOS 设备推送数据,您首先需要在消息推送控制台上配置 iOS 推送证书。iOS 推送证书用于推送通知,本文将介绍消息推送服务支…...
ToolLLM model 以及LangChain AutoGPT Xagent在调用外部工具Tools的表现对比浅析
文章主要谈及主流ToolLLM 以及高口碑Agent 在调用Tools上的一些对比,框架先上,内容会不断丰富与更新。 第一部分,ToolLLM model 先来说主打Function Call 的大模型们 OpenAI GPT 宇宙第一LLM,它的functionCall都知道࿰…...
【MySQL学习之基础篇】约束
文章目录 1. 概述2. 基础约束3. 外键约束3.1. 介绍3.2. 外键的添加3.3. 外键删除和更新行为 1. 概述 概念: 约束是作用于表中字段上的规则,用于限制存储在表中的数据。 目的: 保证数据库中数据的正确、有效性和完整性。 分类&#x…...
【DataSophon】大数据管理平台DataSophon-1.2.1基本使用
🦄 个人主页——🎐开着拖拉机回家_Linux,大数据运维-CSDN博客 🎐✨🍁 🪁🍁🪁🍁🪁🍁🪁🍁 🪁🍁🪁&am…...
基于redisson实现发布订阅(多服务间用避坑)
前言 今天要分享的是基于Redisson实现信息发布与订阅(以前分享过直接基于redis的实现),如果你是在多服务间基于redisson做信息传递,并且有服务压根就收不到信息,那你一定要看完。 今天其实重点是避坑࿰…...
Java 源码、反码、补码 位运算
文章目录 1. 源码、反码、补码1.1 原码1.2 反码1.3 补码1.4 byte的最大值1.5 byte的最小值 2. 位运算2.1 & 与2.2 | 或2.3 ~ 非2.4 ^ 异或2.5 << 左移 (没有无符号左移)2.6 >> 右移 (有符号右移)2.7 >>>…...
时序分解 | Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICEEMDAN时间序列信号分解
时序分解 | Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICEEMDAN时间序列信号分解 目录 时序分解 | Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICEEMDAN时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICE…...
Linux Conda 安装 Jupyter
在Linux服务器Conda环境上安装Jupyter过程中遇到了无数的报错,特此记录。 目录 步骤一:安装Anaconda3 步骤二:配置Conda源 步骤三:安装Jupyter 安装报错:simplejson.errors.JSONDecodeError 安装报错:…...
金融众筹系统源码:适合创业孵化机构 附带完整的搭建教程
互联网技术的发展,金融众筹作为一种新型的融资方式,逐渐成为创业孵化机构的重要手段。为了满足这一需求,金融众筹系统源码就由此而生,并附带了完整的搭建教程。 以下是部分代码示例: 系统特色功能一览: 1.…...
OpenCV imencode 函数详解与应用示例
OpenCV imencode 函数详解与应用示例 介绍imencode 函数的基本信息示例代码应用场景 介绍 OpenCV是一个强大的计算机视觉库,提供了许多图像处理和分析的工具。imencode函数是其中之一,用于将图像编码为指定格式的字节流。这个函数对于图像的存储、传输和…...
持续集成交付CICD:Jenkins使用CD流水线下载Nexus制品
目录 一、实验 1.Jenkins使用CD流水线下载Nexus制品 一、实验 1.Jenkins使用CD流水线下载Nexus制品 (1)Jenkins新建CD流水线 (2)新建视图 (3)查看视图 (4)添加字符参数 …...
【C++】输入输出流 ⑩ ( 文件流 | 文件流打开方式参数 | 文件指针 | 组合打开方式 | 文件打开失败 )
文章目录 一、文件流打开方式参数1、文件流打开方式参数2、文件指针3、组合打开方式4、文件打开失败 一、文件流打开方式参数 1、文件流打开方式参数 文件流打开方式参数 : ios::in : 以只读方式打开文件 ;ios::out : 以只写方式打开文件 , 默认打开方式 , 如果文件已存在则清…...
紧急通知界面访问升级中狼人/seo流量优化
描述 给一组整数,问能找出多少对整数,他们的和大于一个给定的目标值。 使用 O(1) 的额外空间和 O(nlogn) 的时间。 您在真实的面试中是否遇到过这个题? 是 样例 对于 numbers [2, 7, 11, 15], target 24 的情况,返回 1。因…...
简单网站建设/网站查询工具
管道的半双工 管道在双方在内核中共用同一内存区,为了保证数据信息的准确性,所以双方进程读写互斥,且一方写时另一方不可读。 由于双方共享一块缓冲,所以半双工的限制就产生了。 Socket的双工 所谓双工就是两个进程拥有两块缓存…...
港口建设申报网站/网络营销seo优化
做好笔记,打好基础,往高处走。供自己参考,同时欢迎大家指正。1、在官网下载好新版的免安装文件,我的是5.7.20。解压到自定义目录。2、配置环境变量:右键计算机-->属性-->高级系统设置-->环境变量 在path里最…...
长沙工程有限公司/企业seo顾问
“互联网这个行业,最早由老虎和狐狸把事业开创起来,后来逐渐进入两条狗,然后是熊——百度,还有猫——猫扑。当然,南极的动物企鹅也不甘寂寞,没有想到 在这么多野兽横行的市场上竟然长出土豆,最后…...
廊坊哪里有做网站的/网络推广是干嘛的
一个快捷方式后面跟着快捷方式几个字感觉不太舒服,解决办法很简单。不用任何工具,直接修改注册表中资源管理器(explorer)中选项link的键值为零即可。具体操作如下: 1、按win键R打开运行窗口,在里面输入regedit进入注册…...
苹果地图可以看街景吗/广东网站营销seo费用
var scrollTop; var strTop window.location.href;//多个页面的记忆功能,需要通过页面地址来分辨cookie名称 $(document).scroll(function () { scrollTop $(document).scrollTop(); //获取滚动条位置 $.cookie(strTop,scrollTop,{ expires: 1 });生成coo…...