19-高階I/O函式——套接字和標準I/O
之前我們一直使用的read,write函式以及它們的變體recv, send等函式執行I/O,這些函式都是要使用描述符的,通常這些函式都作為unix核心中的系統呼叫實現。
除了以上說的系統呼叫,我們也可以使用標準I/O函式庫(standard I/O libary),這個函式庫由 ANSI C 標準進行規範,不過使用標準I/O函式需要建立一個標準 I/O 流,我們可以使用fdopen函式來完成,與 fdopen 函式功能相反的函式是 fileno,它從標準 I/O 流創建出一個檔案描述符。
這兩個函式原型如下:
#include <stdio.h> FILE *fdopen(int fd, const char *mode); int fileno(FILE *stream);
fdopen函式的fd引數表示檔案描述符,mode則是檔案的讀寫許可權,例如:”w”表示寫許可權,“r”表示讀許可權。
fileno函式的stream引數表示需要傳入一個檔案流形式的指標。
使用標準I/O函式庫需要考慮以下幾點:
1. 當我們想要再標準I/O呼叫select時,因為select只能用於描述符,因此我們需要獲取標準I/O流的描述符,可以使用fileno函式來完成。
2. tcp套接字和udp套接字是全雙工的,標準I/O流也可以是全雙工的,但是為了避免標準I/O緩衝區的問題,解決辦法是給套接字建立兩個標誌I/O流,一個用於讀,另一個用於寫。
使用標準I/O改寫TCP伺服器,程式碼如下:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <ctype.h> #include <arpa/inet.h> #include <stdlib.h> #define SERV_PORT 10001 #define SERV_IP "127.0.0.1" int main(void) { int sfd, cfd; int len, i; //BUFSIZ是系統內嵌的一個巨集,用來指定buf大小 char buf[BUFSIZ], clie_IP[BUFSIZ]; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; sfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; inet_pton(AF_INET , SERV_IP , &serv_addr.sin_addr.s_addr); serv_addr.sin_port = htons(SERV_PORT); //繫結套接字 bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); //設定連線上限,此處不阻塞 listen(sfd, 64); clie_addr_len = sizeof(clie_addr); //阻塞,等待客戶端發起連線 cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len); //根據描述符建立兩個標準I/O流緩衝,一個讀,一個寫 FILE *fpin = NULL; FILE *fpout = NULL; fpin = fdopen(cfd , "r"); fpout = fdopen(cfd , "w"); while (fgets(buf , sizeof(buf) , fpin)!=NULL) { printf("%s" , buf); //處理客戶端資料,小寫轉大寫 for (i = 0; i < strlen(buf); i++){ buf[i] = toupper(buf[i]); } //處理完資料,回寫給客戶端 if(fputs(buf , fpout) == EOF){ puts("fputs error"); break; } //重新整理標準I/O fflush(fpout); } //關閉連線 close(sfd); close(cfd); return 0; }
此時服務端並沒有重新整理標準I/O,在客戶端輸入一些資料,執行結果如下:
可以看到,在客戶端處連續輸入幾次資料後,卻沒有收到任何服務端的迴應。
此時開啟服務端重新整理標準I/O,然後在客戶端輸入資料:
從上面的結果我們知道,開啟服務端重新整理標準I/O後,客戶端才會收到服務端的迴應,原因在於標準I/O的緩衝問題,也就是說服務端呼叫fputs寫入的回射實際上是寫入到了標準I/O的緩衝區,而不是套接字的緩衝區,因為標準I/O類呼叫都有一個使用者緩衝區,當呼叫標準I/O函式時還要把資料從使用者緩衝區拷貝到核心緩衝區(即套接字的緩衝區),但問題在於此時標準I/O的緩衝區沒有滿。
只有當標準I/O的緩衝區滿了之後,才會把資料拷貝到套接字描述符的緩衝區,最終回射給客戶端,而fflush函式則正好是幹這件事情的。通常標準I/O有三大類緩衝區,關於標準I/O的快取具體可參考:2-C標準的I/O快取和FILE結構體 , 5-檔案I/O—read/write函式,這裡就不詳細介紹了。