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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章