標準I/O機能のメリット
標準I/O機能の2つのメリット
データ通信に標準の I/O 機能を使用することは難しくありません。ただし、機能の使い方を知っているだけではあまり意味がありません。少なくとも
これらの機能の利点を理解する必要があります。標準 I/O 機能の主な利点は以下の 2 つです。
□ 標準 I/O 機能は移植性が良い (Portability)
□ 標準 I/O 機能はバッファリングを使用してパフォーマンスを向上させることができます。
携帯性については説明の必要はありません。IO機能だけでなく、すべての標準機能は移植性に優れています。すべてのオペレーティング システム (コンパイラ) をサポートするために、これらの関数は ANSI C 標準に従って定義されているためです。もちろん、これはネットワークプログラミングに限らず、あらゆるプログラミング分野に当てはまります。
標準 I/O 機能の 2 番目の利点については、次に説明します。標準 IO 関数を使用すると、追加のバッファリング サポートが得られます。前述したように、ソケットの作成時にオペレーティング システムが I/O バッファを準備するため、この式は混乱を引き起こす可能性があります。さらに混乱を招く前に、これら 2 つのバッファーの関係について説明しましょう。ソケットが作成されると、オペレーティング システムは I/O 用のバッファを生成します。このバッファは、TCP プロトコルの実装において非常に重要な役割を果たします。このとき、標準の IO 機能を使用すると、別の追加バッファのサポートが得られます。おおよその意味は次の図に示されています。
図からわかるように、標準 I/O 関数を使用してデータを転送する場合、2 つのバッファが渡されます。たとえば、文字列「Hello」が fputs 関数を通じて送信されると、データはまず標準 IO 関数のバッファに渡されます。その後、データはソケットの出力バッファに移動され、最後に文字列が他のホストに送信されます。
2 つのバッファーの関係がわかったので、それぞれの用途を説明しましょう。バッファを設定する主な目的はパフォーマンスを向上させることですが、ソケット内のバッファは主に TCP プロトコルを実装するために設定されます。たとえば、TCP 送信でデータが失われた場合、データは再配信されます
。データを再送信するということは、データがどこかに保存されることを意味します。どこに存在するのでしょうか?ソケットの出力バッファリング。対照的に
、標準 IO 関数のバッファリングを使用する主な目的はパフォーマンスを向上させることですが、実際には、バッファリングがすべての場合に優れたパフォーマンスをもたらすわけではありません。ただし、転送する必要があるデータが増えるほど、バッファリングと非バッファリングのパフォーマンスの差は大きくなります。性能の向上は次の 2 つの観点から説明できます。
□転送データ量
□出力バッファへのデータの移動回数
1バイトのデータを10回(10パケット)送信した場合と、合計10バイト送信した場合を比較してください。データを送信する際に使用されるパケットにはヘッダ情報(ヘッダ)が含まれています。ヘッダ情報はデータサイズとは関係なく、一定のフォーマットに従って埋め込まれます。ヘッダーが 40 バイト (実際にはさらに大きい) を占めると仮定しても、渡す必要があるデータの量には大きな違いがあります。
□1バイト 10回 40×10=400バイト
□10バイト 1回 40×1=40バイト
また、データを送信するには、ソケットの出力バッファにデータを移動するのに時間がかかります。しかし、それは移動の数にも関係します
。1 バイトのデータを 10 回移動するのにかかる時間は、10 バイトのデータを 1 回移動するのにかかる時間のほぼ 10 倍です。
標準 I/O 関数のいくつかの欠点
ここで説明を終えると、標準の I/O 機能には利点しかないと思われるかもしれません。実は、以下のような欠点もあります。
□双方向のコミュニケーションを行うのは容易ではない。
□ fflush 関数が頻繁に呼び出される場合があります。(バッファをリフレッシュするために使用されます)
□ファイル記述子を FILE 構造ポインタの形式で返す必要があります。
C 言語のファイル IO 関連の知識のほとんどを習得していることを前提としています。ファイルを開くときに、読み取りと書き込みの操作を同時に実行したい場合は、r+、w+、a+ モードでファイルを開く必要があります。ただし、バッファリングのため、読み取りと書き込みの動作状態が切り替わるたびに fflush 関数を呼び出す必要があります。これは、バッファベースのパフォーマンスの向上にも影響します。また、標準IO機能を使用するには、FILE構造体ポインタ(以下、FILEポインタと呼びます)が必要です。ソケットを作成すると、デフォルトでファイル記述子が返されるため、ファイル記述子を FILE ポインタに変換する必要があります。
標準のI/O関数を使用する
前述したように、ソケットを作成するとファイル記述子が返されますが、標準の IO 関数を使用するには、FILE 構造体ポインタに変換するしかありません
。まずはその変換方法を紹介します。
fdopen関数を使用してFILE構造体ポインタに変換します。
ソケットの作成時に返されたファイル記述子は、fdopen 関数を通じて標準 IO 関数で使用されるFILE
構造ポインタに変換できます。
#include<stdio.h>
FILE * fdopen(int fildes, const char * mode);
//成功时返回转换的FILE结构体指针,失败时返回NULL。
fildes //需要转换的文件描述符。
mode //将要创建的FILE结构体指针的模式(mode)信息。
上記関数の第 2 パラメータは、fopen 関数のオープン モードと同じです。一般的に使用されるパラメータは、読み取りモード「r」と書き込みモード「w」です。
fileno 関数を使用してファイル記述子に変換します
次に、fdopen 関数の逆の機能を提供する、場合によっては非常に便利な関数を紹介します。
#include<stdio.h>
int fileno(FILE * stream);//成功时返回转化后的文件描述符,失败时返回-1
この関数の使用方法も非常に簡単で、FILE ポインタ パラメータをこの関数に渡すと、対応するファイル記述子が返されます。
ソケットベースの標準I/O機能の使用方法
標準 IO 関数の長所と短所を上で紹介し、ファイル記述子を FILE ポインタに変換する方法も紹介します。以下はこれをソケットに適用します。ソケット操作ですが、内容を説明する必要はなく、単純にこれらの関数を適用するだけです。次に、これまでのエコーサーバーとクライアントを、標準のIO機能に基づいたデータ交換形式に変更します。
サーバー側でもクライアント側でも、変更方法に違いはありません。fdopen 関数を呼び出して標準の IO 関数を使用するだけで、
自分で変更できると思います。変更されたサーバー側のコードが最初に示されます。
#include<"头文件声明和之前章节中的回声服务端相同,故省略">
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc,char *argv[]){
int serv_sock,clnt_sock;
char message[BUF_SIZE];
int str_len,i;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_sz;
FILE * readfp;
FILE * writefp;
if(argc!=2){
printf("Usage : %s <port>\n",argv[0]);
exit(1);
}
serv_sock=socket(PF_INET,SOCK_STREAM,0);
if(serv_sock==-1)error_handling("socket() error");
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
error_handling("bind() error");
if(listen(serv_sock,5)==-1)error_handling("listen() error");
clnt_addr_sz=sizeof(clnt_sock);
for(int i=0;i<5;++i){
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_sock,&clnt_addr_sz);
if(clnt_sock==-1)error_handling("accept() error");
else printf("Connected client %d \n",i+1);
readfp=fdopen(clnt_sock,"r");
writefp=fdopen(clnt_sock,"w");
while(!feof(readfp)){
fgets(message,BUF_SIZE,readfp);
fputs(message,writefp);
fflush(writefp);
}
fclose(readfp);
fclose(writefp);
}
close(serv_sock);
return 0;
}
void error_handling(char*message){
//与之前章节的错误处理函数相同,故省略
}
エコークライアントのコードを次に示します。
#include<"与之前章节的头文件声明相同,故省略">
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc,char *argv[]){
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_int serv_addr;
FILE * readfp;
FILE * writefp;
if(argc!=3){
printf("Usage : %s <IP> <port>\n",argv[0]);
exit(1);
}
sock=socket(PF_INET,SOCK_STREAM,0);
if(sock==-1)error_handling("socket() error");
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
error_handling("connect() error");
else
puts("Connected.......");
readfp=fdopen(sock,"r");
writefp=fdopen(sock,"w");
while(1){
fputs("Input message(Q to quite):",stdout);
fgets(message,BUF_SIZE,stdin);
if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))break;
fputs(message,writefp);
fflush(writefp);
fgets(message,BUF_SIZE,readfp);
printf("Message from server: %s",message);
}
fclose(writefp);
fclose(readfp);
return 0;
}
void error_handling(char *message){
//和前面章节的错误处理函数相同,故省略。
}
上記はソケットプログラミングにおける標準IO関数の応用方法ですが、追加のコードを記述する必要があるため、想像されているほど一般的には使用されていません。しかし、場合によっては非常に役立つこともあり、標準の IO 機能をもう一度見直すことは、誰にとっても非常に有益です。