TCPIP網絡編程_1.2 基於Linux的文件操作
討論套接字的過程中突然談及文件也許有些奇怪. 但對Linux而言, socket 操作與文件操作沒有區別, 因此有必要詳細瞭解文件. 在Linux 世界裏, socket 也被認爲是文件的一種, 因此在網絡數據傳輸中自然可以使用文件I/O 的相關的函數, Windows 則與 Linux 不同, 是要區分 socket 和文件的. 因此在 Windows中需要調用特殊的數據傳輸相關函數.
底層文件訪問(Low-Level File Access) 和 文件標識符 (File Descriptor)
即使看到"底層"二字, 也會有讀者一測其難以理解. 實際上, "底層"這個表達可以理解爲 "與標準無關的操作系統獨立提供的. ". 稍後講解的函數是由 Linux 提供的, 而非 ANSI 標準定義的函數. 如果想要使用 Linux 提供的文件 I/O 函數, 首先應該理解好文件描述符的概念.
此處的文件描述符是系統分配給文件或套接字的整數. 實際上, 學習c語言過程中用過的標準輸入輸出及標準錯誤在 Linux 中也被分配表 1-1 中的文件描述符.
文件和套接字一般經過創建過程纔會被分配文件描述符. 而表 1-1 中的3種輸入輸出對象即使未經過特殊的創建處理, 程序開始運行後也會被自動分配文件描述符. 稍後將詳細講解其使用方法及含義.
學校附近有個服務站, 只需打個電話就能複印所需論文. 服務站有一位常客加英秀, 他每次都要求複印同一篇論文的部分內容.
"大叔你好! 請幫我複印一下<<關於隨着高度信息化社會而逐步提升地位的觸覺, 知覺, 思維, 性格, 智力等人類生活質量相關問題特性的人類學研究>>“這篇論文第 26頁到 30頁”.
這位同學每天這樣打好幾次電話, 更是雪上加霜的是語速還特別慢. 終於有一天大叔說話:“從現在開始, 那篇論文編爲第18號! 你就說幫我複印18號論文26頁到30頁!”
之後英秀也是隻複印超過50字標題的論文, 大叔也會給每篇論文分配無重複的新號(數字).這纔不會頭痛與英秀的對話, 且不影響業務.
該案例中, 大叔相當於操作系統, 英秀相當於程序員, 論文號相當於文件描述符, 論文相當於文件或套接字. 也就是說, 每當生成文件或套接字, 操作系統將返回分配給它們的整數. 這個整數將成爲程序員與操作系統之間良好的溝通的渠道. 實際上, 文件描述符只不過是爲了方便稱呼操作系統創建的文件或套接字而賦予的數而已.
文件描述符有時也稱爲文件句柄, 但"句柄"主要是 Windows 中的術語. 因此, 本書中如果涉及 Windows 平臺將使用 “句柄”, 如果是 Linux 平臺則用"描述符".
打開文件
首先介紹打開文件以讀寫數據的函數. 調用此函數時需要傳遞兩個參數: 第一個參數是打開的目標文件名及路徑信息, 第二個參數是文件打開模式(文件特性信息)
表 1-2 是此函數第二個參數flag 可能的常量值及含義. 如需傳遞多個參數, 則應通過位或運算(OR)符組合並傳遞.
稍後將給出次函數的使用示例. 接下來先介紹文件和寫文件時調用的函數.
關閉文件
各位學習C語言時學過, 使用文件後必須關閉. 下面介紹關閉文件時調用的函數.
若調用此函數的同時傳遞文件描述符參數, 則關閉(終止)相應文件. 另外需要注意的是, 此函數不僅僅可以關閉文件, 還可以關閉套接字. 這再次證明了 "Linux操作系統不區分文件與套接字"的特點.
將數據寫入文件
接下來介紹write函數用於向文件輸出(傳輸)數據. 當然, Linux 中不區分文件與套接字, 因此, 通過套接字向其他計算機傳輸數據時也會用到該函數. 之前的示例也調用它傳遞字符串"Hello word! ".
此函數定義中, size_t 是通過typedef 聲明的unsigned int 類型. 對ssize_t 來說, size_t 前面多加的 s 代表signed, 即ssize_t 是通過typedef 聲明的signed int 類型.
我們已經接觸到 ssize_t, size_t 等陌生的數據類型. 這些都是元數據類型(primitive), 在sys/types.h 頭文件中一般由typedef 聲明定義, 算是給大家熟悉的基本數據起來別名. 既然已經有了基本數據類型, 爲何還要聲明並使用這些新的呢?
人們目前普遍認爲int 是32 位的, 因爲主流操作系統和計算機仍採用32位. 而在過去16 位操作系統時代, int 是16位. 根據系統的不同, 時代的變化, 數據類型的表現形式也隨之改變, 需要修改程序中使用的數據類型. 如果之前已在需要聲明4字節數據類型之處使用了size_t或 ssize_t ,則將大大減小代碼變動, 因爲只需要修改並編譯size_t和ssize_t的typedeef聲明即可. 在項目中, 爲了給基本數據類型賦予別名, 一般會添加大量typedef聲明. 而爲了與程序員定義的新數據類型加以區別,操作系統定義的數據類型會添加後綴_t.
下面通過實例幫助大家更好地理解前面討論過的函數. 此程序將創建新文件並保存數據.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
void error_handling(char *message);
int main()
{
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)
{
error_handling("write() error!");
}
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
運行結果:
讀取文件中的數據
與之前的write函數相對應, read 函數用來輸入(接收)數據
下列實例將通過read函數讀取data.txt 中保存的數據.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
void error_handling(char *message);
int main()
{
int fd;
char buf[BUF_SIZE];
fd = open("data.txt", O_RDONLY);
if (fd == -1)
{
error_handling("open() error!");
}
printf("file descriptor: %d \n", fd);
if (read(fd, buf, sizeof(buf)) ==-1)
{
error_handling("read() error!");
}
printf("file data: %s", buf);
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
運行結果:
基於文件描述符的 I/O 操作相關介紹到此結束. 希望各位記住, 該內容同樣適用於套接字.
文件描述符與套接字
下面將同時創建文件和套接字, 並用整數型態比較返回的文件描述值.
#include <stdio.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd1, fd2, fd3;
/* 創建1個文件和兩個套接字 */
fd1 = socket(PF_INET, SOCK_STREAM, 0);
fd2 = open("test.dat", O_CREAT|O_WRONLY|O_TRUNC);
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開始以由小到大的順序編號(numbering), 因爲0 , 1, 2 是分配給標準I/O 的描述符(如圖表 1- 1所示)
時間: 2020_05_21