中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

做網(wǎng)站編程用什么語言好seo怎么做最佳

做網(wǎng)站編程用什么語言好,seo怎么做最佳,網(wǎng)站建設(shè)選擇題,淄博做網(wǎng)站哪家好文章目錄 Linux文件(系統(tǒng))IO(含動(dòng)靜態(tài)庫的鏈接操作)1、C語言文件IO操作2、三個(gè)數(shù)據(jù)流stdin、stdout、stderr3、系統(tǒng)文件IO3.1、相關(guān)系統(tǒng)調(diào)用接口的使用3.2、文件描述符fd3.3、文件描述符的分配規(guī)則3.3、重定向3.4、自制shell加入重…

文章目錄

  • Linux文件(系統(tǒng))IO(含動(dòng)靜態(tài)庫的鏈接操作)
    • 1、C語言文件IO操作
    • 2、三個(gè)數(shù)據(jù)流stdin、stdout、stderr
    • 3、系統(tǒng)文件IO
      • 3.1、相關(guān)系統(tǒng)調(diào)用接口的使用
      • 3.2、文件描述符fd
      • 3.3、文件描述符的分配規(guī)則
      • 3.3、重定向
      • 3.4、自制shell加入重定向功能
    • 4、FILE
    • 5、文件系統(tǒng)
      • 5.1、inode
      • 5.2、硬鏈接(Hard Link)
      • 5.3、軟鏈接(Symbolic Link)
    • 6、動(dòng)態(tài)庫和靜態(tài)庫
      • 6.1、靜態(tài)庫
      • 6.2、動(dòng)態(tài)庫

img

Linux文件(系統(tǒng))IO(含動(dòng)靜態(tài)庫的鏈接操作)

1、C語言文件IO操作

  • fopen打開文件,fclose關(guān)閉文件

  • fwrite操作(寫文件)

fwrite函數(shù)從ptr里將nitems個(gè)大小為size字節(jié)的數(shù)據(jù)寫進(jìn)定stream

  • hello_w.c文件

    #include <stdio.h>                                                              
    #include <string.h>    int main(){    FILE* pf = fopen("myfile.txt","w");    if(!pf){    perror("fopen");    return 1;    }    const char* msg = "hello fopen\n";    int count = 5;    while(count--){    fwrite(msg,strlen(msg),1,pf);    }    fclose(pf);    return 0;    
    }
    

    這里我們將會(huì)在此路徑得到一個(gè)寫有5hello fopenmyfile.txt文件,如下gif:

  • fread操作(寫文件)

fread 不會(huì)在字符串末尾添加 null 終止符(\0),fgets會(huì)。

fread函數(shù)從定stream里讀取nitems個(gè)size字節(jié)大小的數(shù)據(jù)存到ptr

  • hello_r.c文件

    #include <stdio.h>
    #include <string.h>int main(){FILE* pf = fopen("myfile.txt","r");if(!pf){perror("fopen");return 1;}char buffer[1024];while(1){//fgets// 每次讀1個(gè)字節(jié),讀1024次(文件結(jié)束就不讀)size_t s = fread(buffer,1,sizeof(buffer),pf);if(s > 0){buffer[s] = 0;printf("%s",buffer);}if(feof(pf)){break;}}fclose(pf);return 0;
    }
    

    這里我們將會(huì)在此路徑讀到myfile.txt文件,看到打印出來的數(shù)據(jù),如下gif:

  • fgets操作(每次讀文件的一行))

fgets函數(shù)從給定stream中讀取size個(gè)字節(jié)放到str中,當(dāng)發(fā)現(xiàn)換行符、文件結(jié)束符或發(fā)生錯(cuò)誤時(shí),讀取將停止。

#include <stdio.h>
#include <string.h>int main(){FILE* pf = fopen("myfile.txt","r");if(!pf){perror("fopen");return 1;}char buffer[1024];const char* msg = "hello xxxxx\n";    while(1){    char* r = fgets(buffer,sizeof(msg),pf);                                                  if(!r)    break;    printf("%s",buffer);    }fclose(pf);return 0;
}

  • 打開文件的方式:
			 r      打開文本文件,用于讀。流被定位于文件的開始。r+     打開文本文件,用于讀寫。流被定位于文件的開始。w      將文件長度截?cái)酁榱?#xff0c;或者創(chuàng)建文本文件,用于寫。流被定位于文件的開始。w+     打開文件,用于讀寫。如果文件不存在就創(chuàng)建它,否則將截?cái)嗨?。流被定位于文件的開始。a      打開文件,用于追加(在文件尾寫)。如果文件不存在就創(chuàng)建它。流被定位于文件的末尾。a+     打開文件,用于追加(在文件尾寫)。如果文件不存在就創(chuàng)建它。讀文件的初始位置是文件的開始,但是輸出總是被追加到文件的末尾。

2、三個(gè)數(shù)據(jù)流stdin、stdout、stderr

  • stdin(標(biāo)準(zhǔn)輸入流):我們?cè)贑語言常用的scanf函數(shù),其默認(rèn)就是指定了標(biāo)準(zhǔn)輸入流stdin
#include <stdio.h>int main(){int x = 0;scanf("%d",&x);return 0;
}

fread使用標(biāo)準(zhǔn)輸入流:

#include <stdio.h>    
#include <string.h>    int main(){ char buffer[10];    // 這里要注意,這里的sizeof(buffer)-1是多少,fread就會(huì)去讀多少數(shù)據(jù),直到讀到這么多數(shù)據(jù)    size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer)-1, stdin);    buffer[bytesRead] = '\0';  // 字符串結(jié)束符    printf("buff:%s",buffer);return 0;
}

如果我們想指定輸入流,我們可以使用fscanf函數(shù):

#include <stdio.h>    int main(){    FILE* pf = fopen("myfile","r");    if(!pf){    perror("fopen error");    return 1;    }    int in = 0;    fscanf(pf,"%d",&in);    printf("%d\n",in);fclose(pf);return 0;    
}

  • stdout(標(biāo)準(zhǔn)輸出流):我們?cè)贑語言常用的printf函數(shù),其默認(rèn)就是指定了標(biāo)準(zhǔn)輸入流stdout
#include <stdio.h>int main(){printf("%d\n",2);return 0;
}

fwrite使用標(biāo)準(zhǔn)輸出流:

#include <stdio.h>    
#include <string.h>                                                                              int main(){    FILE* pf = fopen("myfile","w");    if(!pf){    perror("fopen");    return 1;    }    const char* msg = "hello fprintf\n";       fwrite(msg,strlen(msg),1,stdout);fclose(pf);	return 0;    
}

如果我們想指定輸出流,我們可以使用fprintf函數(shù):

#include <stdio.h>    int main(){    FILE* pf = fopen("myfile","w");    if(!pf){    perror("fopen error");    return 1;    }    const char* msg = "hello fprintf\n";    fprintf(pf,"%s",msg);                                                                       fclose(pf);return 0;    
}

  • stderr(標(biāo)準(zhǔn)輸入流)(后面講)

3、系統(tǒng)文件IO

3.1、相關(guān)系統(tǒng)調(diào)用接口的使用

  • 類比C標(biāo)準(zhǔn)庫中的函數(shù)fopen、fwrite、fread等,我們稱之為庫函數(shù)(權(quán)限低)(底層就是使用系統(tǒng)調(diào)用接口)。

  • 而open、write、read等屬于系統(tǒng)提供的接口,我們稱之為系統(tǒng)調(diào)用接口(權(quán)限高)。

  • 參考下圖的lib和系統(tǒng)調(diào)用接口

  • open打開文件,close關(guān)閉文件

這里注意有兩個(gè)open系統(tǒng)調(diào)用接口,第一個(gè)不帶第三個(gè)參數(shù)mode,這個(gè)mode就是給創(chuàng)建的文件設(shè)置權(quán)限的比如0666,就是讓這個(gè)文件的root、自己、其他人的權(quán)限都設(shè)置為只能讀寫,當(dāng)然,這個(gè)還要看權(quán)限掩碼umask是多少,假如umask是0002這最后文件的權(quán)限為0664(前面的博客有講權(quán)限掩碼,自行查閱)。

  • 系統(tǒng)調(diào)用接口write寫文件

write接口試圖從buf中取nbyte字節(jié)的數(shù)據(jù)寫到到文件描述符fildes指向的空間中

#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    int main(){    int fd = open("myfile",O_WRONLY|O_TRUNC|O_CREAT,0666);                      printf("%d\n",fd);// 3const char* msg = "hello write\n";    write(fd,msg,strlen(msg));    close(fd);    return 0;    
}
  • 系統(tǒng)調(diào)用接口read讀文件

read接口試圖從文件描述符fildes指向的空間中讀取nbyte字節(jié)放到buf

#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    int main(){    int fd = open("myfile",O_RDONLY);    printf("%d\n",fd);// 3char buff[64];        const char* msg = "hello write\n";    read(fd,buff,strlen(msg));    printf("%s",buff);close(fd);    return 0;    
}

3.2、文件描述符fd

  • 經(jīng)過上面對(duì)文件myfile的寫讀,我們知道這個(gè)open的返回值fd是3,那么為什么返回值是3?我們?cè)俅蜷_幾個(gè)文件試試它們的文件描述符到底是多少:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    
    #include <string.h>    int main(){int fd1 = open("myfile1",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd2 = open("myfile2",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd3 = open("myfile3",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd4 = open("myfile4",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd5 = open("myfile5",O_WRONLY|O_TRUNC|O_CREAT,0666);    printf("%d\n",fd1);    printf("%d\n",fd2);    printf("%d\n",fd3);                                                         printf("%d\n",fd4);    printf("%d\n",fd5);//close省時(shí)間不寫了return 0;
    }
    

    我們得出結(jié)果是:

    這像是什么?是不是數(shù)組的下標(biāo)!那么問題又來了,前面的0,1,2去哪了?這時(shí)我們想到還有三個(gè)數(shù)據(jù)流,分別是stdin、stdout、stderr。我們繼續(xù)來驗(yàn)證一下:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    
    #include <string.h>    int main(){int fd1 = open("myfile1",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd2 = open("myfile2",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd3 = open("myfile3",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd4 = open("myfile4",O_WRONLY|O_TRUNC|O_CREAT,0666);    int fd5 = open("myfile5",O_WRONLY|O_TRUNC|O_CREAT,0666);    printf("%d\n",stdin->_fileno);    printf("%d\n",stdout->_fileno);    printf("%d\n",stderr->_fileno);    printf("%d\n",fd1);    printf("%d\n",fd2);                                                         printf("%d\n",fd3);    printf("%d\n",fd4);    printf("%d\n",fd5);//close省時(shí)間不寫了return 0;
    }
    

    這個(gè)打印出來的結(jié)果是:

    因此驗(yàn)證了我們的猜想!

  • 而現(xiàn)在知道,文件描述符就是從0開始的小整數(shù)。當(dāng)我們打開文件時(shí),操作系統(tǒng)在內(nèi)存中要?jiǎng)?chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來描述目標(biāo)文件。于是就有了file結(jié)構(gòu)體,表示一個(gè)已經(jīng)打開的文件對(duì)象。而進(jìn)程執(zhí)行open系統(tǒng)調(diào)用,所以必須讓進(jìn)程和文件關(guān)聯(lián)起來。每個(gè)進(jìn)程都有一個(gè)指針*files, 指向一張表files_struct,該表最重要的部分就是包涵一個(gè)指針數(shù)組,每個(gè)元素都是一個(gè)指向打開文件的指針!所以,本質(zhì)上,文件描述符就是該數(shù)組的下標(biāo)。所以,只要拿著文件描述符,就可以找到對(duì)應(yīng)的文件。如下圖:


3.3、文件描述符的分配規(guī)則

  • 來看下列代碼:
#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    int main(){    int fd = open("myfile",O_RDONLY);    if(fd < 0){    perror("open");    return 1;    }    printf("%d\n",fd);    close(fd);    return 0;    
}

輸出是3,關(guān)閉0或2試試(這里不能關(guān)閉1,因?yàn)?是標(biāo)準(zhǔn)輸出流,關(guān)閉了我們就看不到輸出結(jié)果,想看到輸出結(jié)果我們需要進(jìn)行輸出重定向,下面會(huì)講)

#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    int main(){    close(0);//close(2);int fd = open("myfile",O_RDONLY);    if(fd < 0){    perror("open");    return 1;    }    printf("%d\n",fd);    close(fd);    return 0;    
}

輸出是0或者2。說明了什么?說明文件描述符的分配規(guī)則是在files_struct數(shù)組當(dāng)中,找到當(dāng)前沒有被使用的最小的一個(gè)下標(biāo),作為新的文件描述符。


3.3、重定向

  • 重定向分為輸入重定向和輸出重定向

  • 針對(duì)上面關(guān)閉1的情況,關(guān)閉1后看不到輸出結(jié)果了,那么我們可以進(jìn)行輸出重定向,看下面代碼:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    int main(){    close(1);int fd = open("myfile",O_WRONLY|O_CREAT,0666);    if(fd < 0){    perror("open");    return 1;    }    printf("%d\n",fd);    fflush(stdout);// 	刷新緩沖區(qū)close(fd);    return 0;    
    }
    

    我們發(fā)現(xiàn),本來應(yīng)該輸出到顯示器上的數(shù)據(jù),輸出到了myfile文件中!這種現(xiàn)象叫做輸出重定向。輸入重定向類似(從myfile文件中讀取數(shù)據(jù))。

    常見的重定向有:>, >>, <

  • 那么重定向的本質(zhì)是什么?看下圖:

    也就是將file*的指針重新指向新的文件。

  • 除了使用關(guān)閉標(biāo)準(zhǔn)輸入輸出流的方法實(shí)現(xiàn)重定向功能,還可以使用dup2系統(tǒng)調(diào)用。

    dup2系統(tǒng)調(diào)用,fildes2文件描述符是指定的,fildes是需要重定向的文件描述符。

    使用dup2系統(tǒng)調(diào)用代碼:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    int main(){    int fd = open("myfile",O_RDWR|O_CREAT,0666);                                   if(fd < 0){    perror("open");    return 1;    }    close(1);    dup2(fd,1);    printf("hello dup2\n");    fflush(stdout);    return 0;    
    }
    

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>int main() {int fd = open("./log", O_CREAT | O_RDWR);if (fd < 0) {perror("open");return 1;}close(1);dup2(fd, 1);for (;;) {char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);// 從標(biāo)準(zhǔn)輸入流(命令行)讀取數(shù)據(jù)if (read_size < 0) {perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
    }
    

3.4、自制shell加入重定向功能

  • myshell.c文件

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <sys/stat.h>#define SIZE 1024
    #define MAX_LEN 128
    #define SEP " "// 下面的都和重定向有關(guān)
    #define NoneRedir  -1
    #define StdinRedir  0
    #define StdoutRedir 1
    #define AppendRedir 2//#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)int redir_type = NoneRedir;
    char *filename = NULL;char* argv[MAX_LEN];//命令行字符串?dāng)?shù)組
    char pwd[SIZE];
    char envtemp[SIZE];
    int lastcode = 0;//退出碼const char* HostName(){char* hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "None";
    }const char* UserName(){char* hostname = getenv("USER");if(hostname) return hostname;else return "None";
    }const char* CurrentWorkDir(){char* hostname = getenv("PWD");if(hostname) return hostname;else return "None";
    }char* Home(){char* hostname = getenv("HOME");if(hostname) return hostname;else return "None"; 
    }int Interactive(char* commandline, int size){printf("[%s@%s %s]$ ",UserName(),HostName(),CurrentWorkDir());fgets(commandline,SIZE,stdin);commandline[strlen(commandline)-1] = '\0';return strlen(commandline);//空串返回0
    }void Check_redir(char in[])
    {// ls -a -l// ls -a -l > log.txt// ls -a -l >> log.txt// cat < log.txtredir_type = NoneRedir;filename = NULL;int pos = strlen(in) - 1;while( pos >= 0 ){if(in[pos] == '>'){if(in[pos-1] == '>'){redir_type = AppendRedir;in[pos-1] = 0;pos++;//IgnSpace(in, pos);while(in[pos] == ' '){pos++;}filename = in+pos;break;}else{redir_type = StdoutRedir;in[pos++] = 0;//IgnSpace(in, pos);while(in[pos] == ' '){pos++;}filename = in+pos;//printf("debug: %s, %d\n", filename, redir_type);break;}}else if(in[pos] == '<'){redir_type = StdinRedir;in[pos++] = 0;//IgnSpace(in, pos);while(in[pos] == ' '){pos++;}filename = in+pos;//printf("debug: %s, %d\n", filename, redir_type);break;}else{pos--;}}
    }void Split(char* commandline){Check_redir(commandline);int i = 0;argv[i++] = strtok(commandline,SEP);while(argv[i++] = strtok(NULL,SEP));//解決ls無彩色問題if(strcmp(argv[0],"ls") == 0){argv[i-1] = (char*)"--color";argv[i] = NULL;}
    }int BuildingCmd(){int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* target = argv[1];//cd XXX 和cdif(!target) target = Home();//第二個(gè)參數(shù)為NULL//改變當(dāng)前工作目錄chdir(target);//處理target為..的情況//重新獲取當(dāng)前路徑char temp[1024];getcwd(temp,1024);//更新當(dāng)前環(huán)境變量PWDsnprintf(pwd,SIZE,"PWD=%s",temp);//導(dǎo)出環(huán)境變量putenv(pwd);}else if(strcmp(argv[0],"export") == 0){ret = 1;if(argv[1]){strcpy(envtemp,argv[1]);putenv(envtemp);}}else if(strcmp(argv[0],"echo") == 0){ret = 1;if(argv[1] == NULL){printf("\n");}else{if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n",lastcode);lastcode = 0;}else{char* e = getenv(argv[1]+1);if(e) printf("%s\n",e);}}else{printf("%s\n",argv[1]);}}}return ret;
    }void Execute(){//只能交給子進(jìn)程,如果用父進(jìn)程執(zhí)行命令行,執(zhí)行一次就結(jié)束了pid_t id = fork();if(id < 0) perror("fork\n");else if(id == 0){int fd = -1;if(redir_type == StdinRedir){fd = open(filename, O_RDONLY);dup2(fd, 0);}else if(redir_type == StdoutRedir){umask(0);fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC,0666);dup2(fd, 1);}else if(redir_type == AppendRedir){fd = open(filename, O_CREAT | O_WRONLY | O_APPEND);dup2(fd, 1);}else{// do nothing}execvp(argv[0],argv);exit(1);//執(zhí)行完退出}//父進(jìn)程等待子進(jìn)程int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id) lastcode = WEXITSTATUS(status);//等待成功
    }int main(){while(1){char commandline[SIZE];//1. 打印命令行提示符,獲取用戶的命令字符串int n = Interactive(commandline,SIZE);if(!n) continue;//返回值為0就是空串,下面代碼不再執(zhí)行//2. 對(duì)命令行字符串進(jìn)行切割Split(commandline);//3. 處理內(nèi)建命令n = BuildingCmd();if(n) continue;//4. 執(zhí)行這個(gè)命令Execute();}//int i;//for(i=0;argv[i];++i){//    printf("argv[%d]:%s\n",i,argv[i]);//}return 0;
    }
    

4、FILE

  • 因?yàn)镮O相關(guān)函數(shù)與系統(tǒng)調(diào)用接口對(duì)應(yīng),并且?guī)旌瘮?shù)封裝系統(tǒng)調(diào)用,所以本質(zhì)上,訪問文件都是通過fd訪問的。所以C庫當(dāng)中的FILE結(jié)構(gòu)體內(nèi)部,必定封裝了fd。

  • 研究以下代碼看輸出結(jié)果

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <string.h>    int main(){    //int fd  = open("myfile",O_RDWR|O_CREAT|O_TRUNC,0666);    const char* msg1 = "hello printf\n"; // \n自帶刷新    const char* msg2 = "hello fwrite\n";    const char* msg3 = "hello write\n";    write(1,msg3,strlen(msg3));    printf("%s",msg1);    fwrite(msg2,strlen(msg2),1,stdout);    fork();    return 0;    
}

結(jié)果如下:

但如果對(duì)進(jìn)程實(shí)現(xiàn)輸出重定向呢? ./buffer > buffile , 我們發(fā)現(xiàn)結(jié)果變成了:

我們發(fā)現(xiàn) printf 和 fwrite (庫函數(shù))都輸出了2次,而 write 只輸出了一次(系統(tǒng)調(diào)用)。為什么呢?肯定和fork有關(guān)!

  • 一般C庫函數(shù)寫入文件時(shí)是全緩沖的,而寫入顯示器是行緩沖

  • printf和``fwrite `庫函數(shù)會(huì)自帶緩沖區(qū)(之前的進(jìn)度條例子就可以說明),當(dāng)發(fā)生重定向到普通文件時(shí),數(shù)據(jù)的緩沖方式由行緩沖變成了全緩沖。而我們放在緩沖區(qū)中的數(shù)據(jù),就不會(huì)被立即刷新,甚至fork之后。但是進(jìn)程退出之后,會(huì)統(tǒng)一刷新,寫入文件當(dāng)中。但是fork的時(shí)候,父子數(shù)據(jù)會(huì)發(fā)生寫時(shí)拷貝,所以當(dāng)你父進(jìn)程準(zhǔn)備刷新(刷新完相當(dāng)于清空數(shù)據(jù),就會(huì)發(fā)生寫時(shí)拷貝)的時(shí)候,子進(jìn)程也就有了同樣的一份數(shù)據(jù),隨即產(chǎn)生兩份數(shù)據(jù)。

  • write 沒有變化,說明沒有所謂的緩沖。

  • 圖示:

  • 那么為什么會(huì)有緩沖區(qū)這個(gè)概念呢?
  • 可以提高使用者的效率
  • **減少系統(tǒng)調(diào)用次數(shù):**調(diào)用系統(tǒng)調(diào)用是有時(shí)間和空間上的消耗的,增加緩沖區(qū)可以在數(shù)據(jù)填滿過后再調(diào)用系統(tǒng)調(diào)用,減少調(diào)用系統(tǒng)調(diào)用的次數(shù)
  • **減少磁盤訪問次數(shù):**直接將數(shù)據(jù)寫入磁盤會(huì)導(dǎo)致頻繁的磁盤訪問,而磁盤訪問是一種相對(duì)較慢的操作。通過使用緩沖區(qū),可以將多個(gè)寫入操作合并到一起,減少磁盤訪問次數(shù),提高寫入性能。
  • 模擬實(shí)現(xiàn)FILE結(jié)構(gòu)體
  • mystdio.h文件

    #include<stdio.h>                                                                                                                                                     #define NONE_FLUSH (1<<1)
    #define LINE_FLUSH (1<<2)
    #define FULL_FLUSH (1<<3)
    #define SIZE 1024typedef struct _myFILE{int fileno;int pos;// 當(dāng)前寫入位置int cap;// 文件容量int flush_mode; // 刷新模式char buff[SIZE];
    }myFILE;myFILE* my_fopen(const char *path, const char *mode);int my_fwrite(const char *ptr, int size,myFILE *stream);//int my_fread(void *ptr, int size, myFILE *stream);void my_fclose(myFILE* stream);const char *toString(int flag);void DebugPrint(myFILE *fp);
    
  • mystdio.c文件

    #include "mystdio.h"                                                                                                                                                                                                                                                                                                                                                                   
    #include <fcntl.h>           
    #include <string.h>          
    #include <sys/types.h>       
    #include <sys/stat.h>        
    #include <stdlib.h>      
    #include <unistd.h>      myFILE* my_fopen(const char *path, const char *mode){      int flag = 0;      if(strcmp(mode,"r") == 0){      flag |= O_RDONLY;      }else if(strcmp(mode,"w") == 0){      flag |= (O_WRONLY|O_CREAT|O_TRUNC);      }else if(strcmp(mode,"a") == 0){      flag |= (O_WRONLY|O_CREAT|O_APPEND);      }else{      return NULL;      }      int fd = 0;      if(flag & O_WRONLY){      umask(0);      fd = open(path,flag,0666);      }else{      fd = open(path,flag);      }      if(fd < 0) return NULL;      myFILE* fp = (myFILE*)malloc(sizeof(myFILE));      if(!fp){      perror("malloc");      return NULL;      }      fp->fileno = fd;      fp->pos = 0;      fp->cap = SIZE;      fp->flush_mode = LINE_FLUSH;      return fp;      
    }      void my_fflush(myFILE* stream){      if(stream->pos == 0) return;      write(stream->fileno,stream->buff,stream->pos);      stream->pos = 0;// 刷新后pos到最初位置      
    }      int my_fwrite(const char *ptr, int size,myFILE *stream){      memcpy(stream->buff+stream->pos,ptr,size);// buff從pos開始       stream->pos += size;      if((stream->flush_mode & LINE_FLUSH) && (stream->buff[stream->pos-1] == '\n')){      my_fflush(stream);      }else if((stream->flush_mode & FULL_FLUSH) && (stream->pos == stream->cap)){      my_fflush(stream);      }      return size;      
    }      void my_fclose(myFILE* stream){      my_fflush(stream);      close(stream->fileno);      free(stream);      
    }      const char *toString(int flag)      
    {      if(flag & NONE_FLUSH) return "None";      else if(flag & LINE_FLUSH) return "Line";      else if(flag & FULL_FLUSH) return "FULL";      return "Unknow";      
    }      void DebugPrint(myFILE *fp)      
    {      printf("outbufer: %s\n", fp->buff);      printf("fd: %d\n", fp->fileno);      printf("pos: %d\n", fp->pos);      printf("cap: %d\n", fp->cap);      printf("flush_mode: %s\n", toString(fp->flush_mode));      
    } 
    
  • main.c文件

    #include "mystdio.h"    
    #include <string.h>    
    #include <unistd.h>    int main(){    myFILE* fp = my_fopen("myfile","w");    if(!fp) return 1;    const char* msg = "hello my_fwrite\n";    my_fwrite(msg,strlen(msg),fp);                                                                                                                                    //int cnt = 5;    //char buffer[64];    //while(cnt)    //{    //    snprintf(buffer, sizeof(buffer), "helloworld,hellobit,%d  ", cnt--);    //    my_fwrite( buffer, strlen(buffer),fp);    //    DebugPrint(fp);    //    sleep(2);    //    //my_fflush(fp);    //}    //    //fork();    my_fclose(fp);    return 0;    
    }
    

5、文件系統(tǒng)

我們?cè)诿钚休斎?code>ls -l ,除了看見文件名,還看見了一些文件的其他屬性

其中還包含:

  • 模式
  • 硬鏈接數(shù)
  • 文件所有者
  • 文件所屬組
  • 大小
  • 最后修改時(shí)間
  • 文件名

還可以通過stat命令來獲取文件的更多信息

上面的執(zhí)行結(jié)果有幾個(gè)信息我們可以通過inode來解釋一下


5.1、inode

為了能解釋清楚inode,我們需要來理解一下文件系統(tǒng),我們先看以下這張磁盤文件系統(tǒng)圖:

磁盤是一個(gè)典型的塊設(shè)備,磁盤分區(qū)被劃分為一個(gè)個(gè)的block(塊)。一個(gè)block的大小是在格式化的時(shí)候確定的,后面不可更改。

解釋一下上述圖片各名詞的含義:

  • 塊組(Block Group):文件系統(tǒng)會(huì)根據(jù)分區(qū)的大小劃分為數(shù)個(gè)塊組,一個(gè)塊組就是一個(gè)分區(qū),并且每個(gè)塊組都會(huì)有相同的結(jié)構(gòu)組成。

  • 超級(jí)塊(Super Block):存放文件系統(tǒng)本身的信息。記錄的信息主要有:bolck 和 inode的總量,未使用的block和inode的數(shù)量,一個(gè)block和inode的大小,最近一次掛載的時(shí)間,最近一次寫入數(shù)據(jù)的時(shí)間,最近一次檢驗(yàn)磁盤的時(shí)間等其他文件系統(tǒng)的相關(guān)信息。Super Block的信息被破壞,可以說整個(gè)文件系統(tǒng)結(jié)構(gòu)就被破壞了,不過一般磁盤不止一個(gè)扇區(qū)存放有超級(jí)塊,可能會(huì)有三四個(gè)扇區(qū)存放有超級(jí)塊,以便于第一個(gè)扇區(qū)的超級(jí)塊損壞可以通過其他扇區(qū)的超級(jí)塊來恢復(fù)。

  • 塊組描述(Group Discriptor Table):描述塊組屬性信息,也就是管理磁盤塊位圖、inode為徒、inode表、數(shù)據(jù)塊的使用情況。

  • 塊位圖(Block Bitmap):塊位圖中記錄著數(shù)據(jù)塊中哪個(gè)數(shù)據(jù)塊已經(jīng)被占用,哪個(gè)數(shù)據(jù)塊沒有被占用。

  • inode位圖(inode Bitmap):每個(gè)bite表示該inode編號(hào)是否已經(jīng)被使用,比如0表示沒有被使用,1表示已經(jīng)被使用。

  • inode表(inode Table):存放文件屬性。如文件大小,所有者,最近修改時(shí)間,使用的塊情況等

  • 數(shù)據(jù)塊(Data Block):存放文件內(nèi)容。

將屬性和數(shù)據(jù)分開存放的想法看起來很簡單,但實(shí)際上是如何工作的呢?我們通過touch一個(gè)新文件來看看如何工作的。

查看inode編號(hào)ls -li。

從整體上來看,創(chuàng)建一個(gè)文件所經(jīng)歷的步驟如下:

為了說明問題,我們將上圖簡化:

創(chuàng)建文件一般需要4個(gè)操作:

  1. 存儲(chǔ)屬性

    內(nèi)核先找到一個(gè)空閑的i節(jié)點(diǎn)(這里是263466)。內(nèi)核把文件信息記錄到其中。

  2. 存儲(chǔ)數(shù)據(jù)

    該文件需要存儲(chǔ)在三個(gè)磁盤塊,內(nèi)核找到了三個(gè)空閑塊:300,500,800。將內(nèi)核緩沖區(qū)的第一塊數(shù)據(jù)復(fù)制到300,下一塊復(fù)制到500,以此類推。

  3. 記錄分配情況

    文件內(nèi)容按順序300,500,800存放。內(nèi)核在inode上的磁盤分布區(qū)記錄了上述塊列表。

  4. 添加文件名到目錄

    新的文件名abc。linux如何在當(dāng)前的目錄中記錄這個(gè)文件?內(nèi)核將入口(263466,abc)(映射關(guān)系)添加到目錄文件。文件名和inode之間的對(duì)應(yīng)關(guān)系將文件名和文件的內(nèi)容及屬性連接起來。


插寫一個(gè)文件系統(tǒng)操作:

使用dd命令創(chuàng)建大文件并將其寫入文件系統(tǒng),然后將文件系統(tǒng)掛載到系統(tǒng)中,可以按照以下步驟進(jìn)行:

  1. 創(chuàng)建目錄 /path/to

    mkdir -p /path/to
    
  2. 使用 dd 命令創(chuàng)建大文件

    以下命令創(chuàng)建一個(gè)大小為1GB的空文件:

    dd if=/dev/zero of=/path/to/largefile bs=1M count=1024
    

    這將在指定路徑創(chuàng)建名為 largefile 的大小為1GB的空文件。

  3. 使用 mkfs 創(chuàng)建文件系統(tǒng)

    在上面創(chuàng)建的文件中,可以使用適當(dāng)?shù)奈募到y(tǒng)類型(例如ext4)來創(chuàng)建文件系統(tǒng)。假設(shè)要?jiǎng)?chuàng)建ext4文件系統(tǒng):

    mkfs.ext4 /path/to/largefile
    

    這將在 /path/to/largefile 中創(chuàng)建一個(gè)ext4文件系統(tǒng)。

  4. 掛載文件系統(tǒng)

    創(chuàng)建文件系統(tǒng)后,您可以將其掛載到系統(tǒng)中的某個(gè)目錄。首先,創(chuàng)建一個(gè)用于掛載的目錄:

    mkdir /mnt/largefile
    

    然后將文件系統(tǒng)掛載到此目錄:

    mount -o loop /path/to/largefile /mnt/largefile
    

    這將將 /path/to/largefile 中的文件系統(tǒng)掛載到 /mnt/largefile 目錄中。


5.2、硬鏈接(Hard Link)

  • 硬鏈接是指在文件系統(tǒng)中,多個(gè)文件可以指向同一個(gè)inode,這樣多個(gè)文件實(shí)際上指向了相同的數(shù)據(jù)塊。
  • 硬鏈接是文件系統(tǒng)級(jí)別的鏈接,它們只是指向了相同的inode,不保留路徑信息,因此即使原文件移動(dòng)或重命名,硬鏈接仍然有效。
  • 刪除原文件并不會(huì)影響硬鏈接的可用性,只有當(dāng)所有鏈接都被刪除后,文件的inode和數(shù)據(jù)塊才會(huì)被釋放。

使用硬鏈接:ln 原文件名 硬鏈接文件名,如下

ln file.txt file.hard

我們前面有講到,文件的一行屬性信息,有一個(gè)是硬鏈接數(shù),我們能看到,一開始沒給file.txt建立硬鏈接的時(shí)候,它的硬鏈接數(shù)1,就是只有自己,當(dāng)給它建立硬鏈接后,硬鏈接的文件和原文件的硬鏈接數(shù)都變?yōu)?。

因此我們得出結(jié)論:普通文件的硬鏈接數(shù)等于該文件的建立的硬鏈接文件個(gè)數(shù)+1(可以自行再給file.txt文件再建立一個(gè)硬鏈接文件,看是不是硬鏈接數(shù)都變?yōu)?)。

我們?cè)倏茨夸浀挠叉溄訑?shù):

我們看到,新創(chuàng)建的目錄就有2個(gè)硬鏈接數(shù)!為什么是兩個(gè)?我們看圖可以知道,一個(gè)是自己的目錄文件的,還有一個(gè)是該目錄下的.目錄的,我們知道目錄.就是指當(dāng)前目錄。我們?cè)賮頊y(cè)試,在該目錄下再創(chuàng)建5個(gè)目錄,再看看硬鏈接數(shù)是多少?

能看到硬鏈接數(shù)變?yōu)?了!為什么呢?我們發(fā)現(xiàn)其中2個(gè)是自己和.目錄,還有5個(gè)是該目錄下的目錄(a、b、c、d、e)下的..目錄,顯而易見..目錄就是上級(jí)目錄。因此我們得出結(jié)論:目錄文件的硬鏈接數(shù)等于2+該目錄下的目錄個(gè)數(shù)。

注意:用戶不能自己給目錄建立硬鏈接,只能由系統(tǒng)創(chuàng)建!因?yàn)樽约簞?chuàng)建目錄的硬鏈接會(huì)導(dǎo)致路徑回路。

假如當(dāng)前目錄是/user/home,你給user目錄創(chuàng)建一個(gè)硬鏈接/,哪當(dāng)前創(chuàng)建的硬鏈接目錄變?yōu)榱?code>/user/home/user,這就導(dǎo)致了路徑回路。


5.3、軟鏈接(Symbolic Link)

軟鏈接也叫符號(hào)鏈接。

  • 軟鏈接是一個(gè)特殊的文件,它包含了指向另一個(gè)文件的路徑,相當(dāng)于一個(gè)快捷方式。
  • 軟鏈接可以跨越文件系統(tǒng),因?yàn)樗鼈儽4娴氖锹窂叫畔⒍皇莍node號(hào)。
  • 軟鏈接不像硬鏈接那樣指向相同的數(shù)據(jù)塊,而是指向目標(biāo)文件的路徑,因此即使原文件移動(dòng)或重命名,軟鏈接也可能失效。
  • 刪除原文件后,軟鏈接將成為死鏈接(即指向一個(gè)不存在的目標(biāo)),如果不再需要,可以手動(dòng)刪除。

使用軟鏈接:ln -s 原文件 軟鏈接文件。如:

ln -s file.txt file.symb


6、動(dòng)態(tài)庫和靜態(tài)庫

下面講解動(dòng)靜態(tài)庫可能會(huì)用到的文件

  • mystdio.h文件

    #include<stdio.h>#define NONE_FLUSH (1<<1)
    #define LINE_FLUSH (1<<2)
    #define FULL_FLUSH (1<<3)
    #define SIZE 1024typedef struct _myFILE{int fileno;int pos;// 當(dāng)前寫入位置int cap;// 文件容量int flush_mode; // 刷新模式char buff[SIZE];
    }myFILE;myFILE* my_fopen(const char *path, const char *mode);int my_fwrite(const char *ptr, int size,myFILE *stream);//int my_fread(void *ptr, int size, myFILE *stream);void my_fclose(myFILE* stream);const char *toString(int flag);void DebugPrint(myFILE *fp);
    
  • mystdio.c文件

    #include "mystdio.h"
    #include <fcntl.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdlib.h>
    #include <unistd.h>myFILE* my_fopen(const char *path, const char *mode){int flag = 0;if(strcmp(mode,"r") == 0){flag |= O_RDONLY;}else if(strcmp(mode,"w") == 0){flag |= (O_WRONLY|O_CREAT|O_TRUNC);}else if(strcmp(mode,"a") == 0){flag |= (O_WRONLY|O_CREAT|O_APPEND);}else{return NULL;}int fd = 0;if(flag & O_WRONLY){umask(0);fd = open(path,flag,0666);}else{fd = open(path,flag);}if(fd < 0) return NULL;myFILE* fp = (myFILE*)malloc(sizeof(myFILE));if(!fp){perror("malloc");return NULL;}fp->fileno = fd;fp->pos = 0;fp->cap = SIZE;fp->flush_mode = LINE_FLUSH;return fp;
    }void my_fflush(myFILE* stream){if(stream->pos == 0) return;write(stream->fileno,stream->buff,stream->pos);stream->pos = 0;// 刷新后pos到最初位置
    }int my_fwrite(const char *ptr, int size,myFILE *stream){memcpy(stream->buff+stream->pos,ptr,size);// buff從pos開始 stream->pos += size;if((stream->flush_mode & LINE_FLUSH) && (stream->buff[stream->pos-1] == '\n')){my_fflush(stream);}else if((stream->flush_mode & FULL_FLUSH) && (stream->pos == stream->cap)){my_fflush(stream);}return size;
    }void my_fclose(myFILE* stream){my_fflush(stream);close(stream->fileno);free(stream);
    }const char *toString(int flag)
    {if(flag & NONE_FLUSH) return "None";else if(flag & LINE_FLUSH) return "Line";else if(flag & FULL_FLUSH) return "FULL";return "Unknow";
    }void DebugPrint(myFILE *fp)
    {printf("outbufer: %s\n", fp->buff);printf("fd: %d\n", fp->fileno);printf("pos: %d\n", fp->pos);printf("cap: %d\n", fp->cap);printf("flush_mode: %s\n", toString(fp->flush_mode));
    }
    
  • cal.h文件

    #pragma once                                                                       int Add(int a, int b);
    
  • cal.c文件

    #include "cal.h"    int Add(int a ,int b){    return a + b;                                                                  
    }
    
  • main.c文件

    #include <stdio.h>    
    #include "cal.h"    
    #include "mystdio.h"    int main(){    int res = Add(10,20);    printf("%d+%d=%d\n",10,20,res);    myFILE* fp = my_fopen("log1.txt","w");    if(fp) return 1;                                                               return 0;    
    }
    

6.1、靜態(tài)庫

靜態(tài)庫(.a文件):程序編譯的時(shí)候把哭的代碼鏈接到可執(zhí)行文件中,程序運(yùn)行時(shí)不再需要靜態(tài)庫。

  • 生成靜態(tài)庫:將.o文件打包生成靜態(tài)庫

  • 使用歸檔工具:ar -rc 靜態(tài)庫名 .o文件

  • ar是gnu歸檔工具,rc表示(replace and create)

  • 這里的.o文件是用命令gcc -o 生成的(和動(dòng)態(tài)庫不一樣,下面動(dòng)態(tài)庫會(huì)講)

  • 需要注意的是:靜態(tài)庫名是有規(guī)范的,不能寫成XXX.a,必須寫成libXXX.a

  • 如:ar -rc libmylib.a *.o

  • 查看靜態(tài)庫列表ar -tv 靜態(tài)庫名

  • -t:列出靜態(tài)庫中的文件

  • -v:verbose 詳細(xì)信息

  • 如:ar -tv libmylib.a

  • 使用靜態(tài)庫

  • 先將main.c生成main.o文件

    • 這里我們使用一個(gè)新命令 gcc -c .c文件,可以生成同名.o文件。如gcc -c main.c
  • 再使用命令:gcc - o 可執(zhí)行文件 帶main函數(shù)的目標(biāo)文件(如main.o) -L 靜態(tài)庫路徑 -l靜態(tài)庫名(去除前綴lib和后綴.a,如mylib) -static

  • gcc -o myexe main.c -L. -lmylib -static

  • 解釋一下選項(xiàng):

    • -L:指定靜態(tài)庫路徑(當(dāng)前路徑可以使用.,也可以使用絕對(duì)路徑)
    • -l:指定靜態(tài)庫
    • -static:指定是靜態(tài)鏈接


6.2、動(dòng)態(tài)庫

動(dòng)態(tài)庫相對(duì)靜態(tài)庫的使用更復(fù)雜,原理也更復(fù)雜。

動(dòng)態(tài)庫(.so文件):程序在運(yùn)行的時(shí)候才去鏈接動(dòng)態(tài)庫的代碼,多個(gè)程序共享使用庫的代碼。

一個(gè)與動(dòng)態(tài)庫鏈接的可執(zhí)行文件僅僅包含它用到的函數(shù)入口地址的一個(gè)表,而不是外部函數(shù)所在目標(biāo)文件的整個(gè)機(jī)器碼(不像靜態(tài)庫那樣直接在代碼里面展開)。

在可執(zhí)行文件開始運(yùn)行以前,外部函數(shù)的機(jī)器碼由操作系統(tǒng)從磁盤上的該動(dòng)態(tài)庫中復(fù)制到內(nèi)存中,這個(gè)過程稱為動(dòng)態(tài)鏈接(dynamic linking)

動(dòng)態(tài)庫可以在多個(gè)程序間共享,所以動(dòng)態(tài)鏈接使得可執(zhí)行文件更小,節(jié)省了磁盤空間。操作系統(tǒng)采用虛擬內(nèi)存機(jī)制允許物理內(nèi)存中的一份動(dòng)態(tài)庫被要用到該庫的所有進(jìn)程共用,節(jié)省了內(nèi)存和磁盤空間。

  • 生成動(dòng)態(tài)庫:動(dòng)態(tài)庫的生成也需要目標(biāo)文件,但是動(dòng)態(tài)庫的目標(biāo)文件和靜態(tài)庫的目標(biāo)文件的生成方式不一樣,動(dòng)態(tài)庫目標(biāo)文件必須使用選項(xiàng)fPIC(產(chǎn)生位置無關(guān)碼)。

    • 先將.c文件生成.o文件:gcc -c -fPIC *.c,將.c文件生成同名.o文件

    • 再使用選項(xiàng)shared.o文件打包成動(dòng)態(tài)庫:gcc -shared -o 動(dòng)態(tài)庫名 .o文件

    • gcc -shared -o libmylib.so *.o

    • 需要注意的是:這個(gè)動(dòng)態(tài)庫名也有規(guī)則:不能是XXX.so,必須是libXXX.so。

  • 使用動(dòng)態(tài)庫

    • 先用main.c文件生成main.o文件

    • 再使用命令gcc -o 可執(zhí)行文件 帶main函數(shù)的目標(biāo)文件 -L動(dòng)態(tài)庫路徑 -l(動(dòng)態(tài)庫名去掉前綴lib,去掉后綴.so)

    • gcc -o myexe main.o -L. -lmylib

  • 現(xiàn)在有一個(gè)問題:如果動(dòng)態(tài)庫不在當(dāng)前目錄下呢?

    我們來測(cè)試一下,將動(dòng)態(tài)庫和頭文件放到一個(gè)目錄下

    假設(shè)我們得到了一個(gè)壓縮文件,文件解壓后得到一個(gè)目錄文件,目錄文件里面包含所需所有的頭文件和靜態(tài)庫

    這時(shí)候我們?cè)趺催\(yùn)行這個(gè)main函數(shù)呢?

    這里我們又會(huì)使用到一個(gè)選項(xiàng)I,這個(gè)選項(xiàng)是用來指明頭文件的路徑。

    現(xiàn)在出現(xiàn)一個(gè)新的問題!直接運(yùn)行myexe會(huì)報(bào)錯(cuò)!

    報(bào)錯(cuò)的原因是找不到動(dòng)態(tài)庫libmyib.so。我們來看看該可執(zhí)行文件所依賴的動(dòng)態(tài)庫,使用命令:ldd 可執(zhí)行文件名。ldd即list dynamic dependence。

    ldd myexe

    也就是沒找到該動(dòng)態(tài)庫。有下面四種解決辦法:

    1、將該文件所需的動(dòng)態(tài)庫放到系統(tǒng)里的動(dòng)態(tài)庫中(不建議使用,風(fēng)險(xiǎn)高)。

    如:sudo cp mylib/lib/* /lib64

    2、使用環(huán)境變量

    使用命令:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:動(dòng)態(tài)庫文件路徑。這樣導(dǎo)入環(huán)境變量的話,重啟shell又需要重新配置。

    如:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xp2/Linux_Primary/day_32/demo_02/dynamiclib/user/mylib/lib

    還可以去系統(tǒng)文件添加環(huán)境變量

    在這兩個(gè)文件中:.bash_profile.bashrc文件。查找到這兩個(gè)文件的方式就是先cd到用戶路徑cd ~,再ls -al查看這個(gè)路徑下所有文件就可以看到這兩個(gè)文件。

    3、使用軟鏈接的方式(推薦,簡單好用)

    在lib64目錄下新建一個(gè)同動(dòng)態(tài)庫名的動(dòng)態(tài)庫文件的軟鏈接。

    如:sudo ln -s /home/xp2/Linux_Primary/day_32/demo_02/dynamiclib/user/mylib/lib/libmylib.so /lib64/libmylib.so

    4、在系統(tǒng)目錄下(/etc/ld.so.conf.d/)新建一個(gè)(文件名.conf)配置文件(推薦)


那么好,Linux基礎(chǔ)IO就到這里,如果你對(duì)Linux和C++也感興趣的話,可以看看我的主頁哦。下面是我的github主頁,里面記錄了我的學(xué)習(xí)代碼和leetcode的一些題的題解,有興趣的可以看看。

Xpccccc的github主頁

http://www.risenshineclean.com/news/40059.html

相關(guān)文章:

  • 外國人學(xué)做中國菜的網(wǎng)站營銷網(wǎng)站做的好的公司
  • 網(wǎng)上做家教兼職哪個(gè)網(wǎng)站南昌百度快速排名提升
  • 網(wǎng)站開發(fā)經(jīng)常遇到的問題大一html網(wǎng)頁制作作業(yè)簡單
  • 南京模板建網(wǎng)站哪家好百度一下官網(wǎng)
  • 聊大 網(wǎng)站設(shè)計(jì)seo優(yōu)化主要做什么
  • 太倉建設(shè)銀行網(wǎng)站搜索指數(shù)查詢
  • 劉琪 找誰做網(wǎng)站靠譜東莞網(wǎng)絡(luò)營銷全網(wǎng)推廣
  • 做網(wǎng)站怎么做鼠標(biāo)跟隨2023很有可能再次封城嗎
  • 網(wǎng)站經(jīng)營與建設(shè)優(yōu)化大師軟件下載
  • 網(wǎng)上有做口譯的網(wǎng)站么官方進(jìn)一步優(yōu)化
  • 彩票代購網(wǎng)站建設(shè)百度怎么注冊(cè)自己的店鋪
  • 做電影平臺(tái)網(wǎng)站怎么賺錢的什么是網(wǎng)絡(luò)營銷平臺(tái)
  • wordpress主頁居中重慶seo俱樂部
  • 焦作專業(yè)做網(wǎng)站公司中國新聞發(fā)布
  • wordpress 個(gè)性化韶山百度seo
  • 桐鄉(xiāng)網(wǎng)站建設(shè)世界十大網(wǎng)站排名出爐
  • 高明專業(yè)網(wǎng)站建設(shè)報(bào)價(jià)青海百度關(guān)鍵詞seo
  • 網(wǎng)站制作需要平臺(tái)培訓(xùn)心得體會(huì)總結(jié)
  • 網(wǎng)站建設(shè)專企業(yè)站seo價(jià)格
  • 網(wǎng)站建設(shè)種類 優(yōu)幫云海外網(wǎng)站推廣優(yōu)化專員
  • 可信賴的南昌網(wǎng)站制作seo是付費(fèi)還是免費(fèi)推廣
  • 商城網(wǎng)站合同網(wǎng)站的優(yōu)化
  • wordpress 側(cè)邊懸浮塊鄭州谷歌優(yōu)化外包
  • web網(wǎng)站開發(fā)全過程網(wǎng)站內(nèi)鏈優(yōu)化
  • 做網(wǎng)站的流程方法上海排名優(yōu)化推廣工具
  • 網(wǎng)站的設(shè)計(jì)與維護(hù)摘要搜易網(wǎng)優(yōu)化的效果如何
  • 國家和省對(duì)政府網(wǎng)站建設(shè)要求做網(wǎng)絡(luò)推廣要學(xué)些什么
  • 關(guān)于門戶網(wǎng)站建設(shè)通報(bào)google chrome谷歌瀏覽器
  • 成都it培訓(xùn)機(jī)構(gòu)優(yōu)化網(wǎng)絡(luò)搜索引擎
  • 門戶網(wǎng)站快速制作佛山網(wǎng)站建設(shè)維護(hù)