株式会社アイズ・ソフトウェア

/~eyes-software-co-jp/A1A2
お問い合わせ |  情報セキュリティへの取組について情報セキュリティ方針個人情報保護方針) |  ブログ

BLOG 社員ブログ

2016年11月

複数通信先のソケット通信サンプル

C言語で、複数の通信先とソケット通信を行う手順についてサンプルプログラムにて説明します。

本サンプルプログラムはソケット通信のサーバー側で、1つのポートで最大10個の通信先プロセスからデータを受信するものです。
クライアント側のサンプルプログラムについては以前のソケット通信サンプルのを参照すること。

  1. socket()にてソケットを生成し、ディスクリプタを得る。
  2. bind()にてソケットにポート番号及びIPアドレスを設定する。
  3. listen()にて接続を待ち状態とする。
  4. 接続待ちのディスクリプタをディスクリプタ集合に設定する。
    ディスクリプタ集合はFD_ZERO()にて初期化し、FD_SETにてディスクリプタを設定する。
  5. 2回目以降で受信ディスクリプタがある場合、受信ディスクリプタをディスクリプタ集合に設定する。
  6. struct timeval 構造体の変数tvにタイムアウト値を設定する。
    変数tvはselect()にて値が変更される場合がある。その為、タイムアウト値は毎回設定すること。
  7. select()にて接続&受信を待ち受ける。
      select()の第一引数へはディスクリプタ集合にセットしたディスクリプタの最大値+1を指定する。
      第二引数へは接続及び受信待ちのディスクリプタ集合を指定する。
      第三引数へは送信完了待ちのディスクリプタを指定する。
      第四引数へは例外を監視するディスクリプタを指定する。
      本サンプルでは第三、第四引数は使用しない為、NULLを指定している。
      第五引数へはタイムアウト値を指定する。タイムアウト時間を指定しない場合はNULLを指定する。
      select()はシグナル受信した場合、タイムアウトした場合、ディスクリプタ集合で指定したディスクリプタにデータ受信などの変化があった場合にリターンする。
  8. select()の戻り値が負の値の場合、select()がエラー終了している。ただし、errnoがEINTRの場合はシグナル受信での終了なので、リトライで継続できる。
    本サンプルではシグナル受信の場合は4.からの処理を繰り返すようにしている。
  9. select()の戻り値が0の場合、タイムアウトしている。
    本サンプルでは何もしないため、4.からの処理を繰り返すようにしている。
  10. select()の戻り値が正の値の場合、ディスクリプタ集合で指定したディスクリプタのどれかに変化が有ったことを示している。
    どのディスクリプタに変化があったかはFD_ISSET()にて調べることが可能である。
  11. 接続待ちディスクリプタに接続があったかをFD_ISSET()にて調べ、接続があればaccept()にて接続を確立する。
    本サンプルではaccept()にて取得したディスクリプタをfd2配列にセットし、受信ディスクリプタとして使用する。
  12. 受信ディスクリプタに受信データがあるかをFD_ISSET()にて調べ、受信データがrecv()にてデータを受信する。
    recv()の戻り値が0であった場合、そのディスクリプタはクライアント側から切断された事を示している。
  13. 本サンプルではfd2配列にて10個の受信ディスクリプタを管理している。接続されたらディスクリプタをセットし、切断されたら-1へリセットすることで同時に最大10個の通信先からのデータを受信できる。
  14. なお、ソケット通信とは無関係であるが、何箇所か出てくる sizeof(fd2)/sizeof(fd2[0]) はfd2配列の要素数を取得するものである。

========== サーバー側 ============


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>

int main(int argc, char** argv)
{
    int fd;             // 接続待ち用ディスクリプタ
    int fd2[10];        // 通信用ディスクリプタの配列
    struct servent *serv;
    struct sockaddr_in addr;
    socklen_t len = sizeof(struct sockaddr_in);
    struct sockaddr_in from_addr;
    char    buf[2048];
    int     cnt;

    // 受信バッファを初期化する
    memset(buf, 0, sizeof(buf));
    // 通信用ディスクリプタの配列を初期化する
    for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
        fd2[i] = -1;
    }
    // ソケットを作成する
    if (( fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) {
        fprintf( stdout, "socket error : fd = %d\n", fd );
        return -1;
    }

    // IPアドレス、ポート番号を設定
    addr.sin_family = AF_INET;
    addr.sin_port = ntohs(50000);
    addr.sin_addr.s_addr = INADDR_ANY;
    // バインドする
    if ( bind( fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 ) {
        fprintf( stdout, "bind error\n" );
        return -1;
    }
    // 接続待ち状態とする。待ちうけるコネクト要求は1個
    if ( listen( fd, 1 ) < 0 ) {
        fprintf( stdout, "listen error\n" );
        return -1;
    }

    int     maxfd;          // ディスクリプタの最大値
    fd_set  rfds;           // 接続待ち、受信待ちをするディスクリプタの集合
    struct timeval  tv;     // タイムアウト時間

    while ( 1 ){
        // 接続待ちのディスクリプタをディスクリプタ集合に設定する
        FD_ZERO( &rfds );
        FD_SET( fd, &rfds );
        maxfd = fd;
        // 受信待ちのディスクリプタをディスクリプタ集合に設定する
        for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
            if ( fd2[i] != -1 ){
                FD_SET( fd2[i], &rfds );
                if ( fd2[i] > maxfd ) maxfd = fd2[i];
            }
        }
        // タイムアウト時間を10sec+500000μsec に指定する。
        tv.tv_sec = 10;
        tv.tv_usec = 500000;

        // 接続&受信を待ち受ける
        cnt = select( maxfd+1, &rfds, NULL, NULL, &tv );
        if ( cnt < 0 ){
            // シグナル受信によるselect終了の場合、再度待ち受けに戻る
            if ( errno == EINTR ) continue;
            // その他のエラーの場合、終了する。
            goto end;
        } else if ( cnt == 0 ) {
            // タイムアウトした場合、再度待ち受けに戻る
            continue;
        } else {
            // 接続待ちディスクリプタに接続があったかを調べる
            if ( FD_ISSET( fd, &rfds )){
                // 接続されたならクライアントからの接続を確立する
                for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
                    if ( fd2[i] == -1 ){
                        if (( fd2[i] = accept(fd, (struct sockaddr *)&from_addr, &len )) < 0 ) {
                            goto end;
                        }
                        fprintf( stdout, "socket:%d  connected. \n", fd2[i] );
                        break;
                    }
                }
            }
            for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
                // 受信待ちディスクリプタにデータがあるかを調べる
                if ( FD_ISSET( fd2[i], &rfds )){
                    // データがあるならパケット受信する
                    cnt = recv( fd2[i], buf, sizeof(buf), 0 );
                    if ( cnt > 0 ) {
                        // パケット受信成功の場合
                        fprintf( stdout, "recv:\"%s\"\n", buf );
                    } else if ( cnt == 0 ) {
                        // 切断された場合、クローズする
                        fprintf( stdout, "socket:%d  disconnected. \n", fd2[i] );
                        close( fd2[i] );
                        fd2[i] = -1;
                    } else {
                        goto end;
                    }
                }
            }
        }
    }
end:
    // パケット送受信用ソケットクローズ
    for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
        close(fd2[i]);
    }
    // 接続要求待ち受け用ソケットクローズ
    close(fd);
    return 0;
}
============ ここまで ============

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。