TCP/IP ネットワーク プログラミング 第 15 章: ソケットと標準 I/O

標準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 機能をもう一度見直すことは、誰にとっても非常に有益です。

おすすめ

転載: blog.csdn.net/Reol99999/article/details/131782455