1. 程式人生 > >19-高階I/O函式——套接字和標準I/O

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函式,這裡就不詳細介紹了。