之前我們一直使用的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函數,這裏就不詳細介紹了。