ボイスチャットシステム

Multisoft-labのホーム    ご意見・ご感想等

ソケットの使用法

ソケットの概要

 ソケットとはアプリケーションをインターネットに接続するための機構です。 インターネット通信をサポートするサーバーやクライアントには必ずソケットが必要になります。 以下では、ソケットの実際的な使い方を、 サーバーを作成する場合とクライアントを作成する場合とに分けて解説します。

 なお、ここで説明する内容は、開発環境がWindowsであることを想定しています。 Unixでソケットを使いたい場合は別の文献を参照してください。 また、ソケットを使う際に注意するべきことがあるので、「注意!」にまとめました。 プログラミングの際には必ず一読してください。


サーバーの作成法(TCP)

 ここではTCP通信をベースとしたサーバーの構成法について解説します。 図1にサーバー作成での基本的な操作の流れを示しました。 この章では初期設定に必要なsocket, bind, listen, accept関数、及び終了処理のclosesocket関数について説明します。

サーバー作成の流れ

(図1)サーバー作成の流れ

 基本的な流れは次のようになっています。

1.socket関数
 リクエストを受け付けるためのソケットを作成します。

2.bind関数
 サーバーのアドレス情報(IPアドレス及びポート番号)をソケットに結び付けます。

3.listen関数
 リクエストの接続待ちキューを設定し、接続要求の準備をします。

4.accept関数
 接続待ちのリクエストを受付けて接続を確立し、新しいソケットを作成します。

 accept関数により新しく生成されたソケットは、 通信専用のソケットで、send関数及びrecv関数によりデータの送受信ができます。 これらの関数は、クライアントアプリケーションでも使われるので、後程まとめて解説します。


ソケットの作成(socket)

 TCPサーバーを作成するには、 まず初めにリクエスト受付用のソケットを作成する必要があります。 このソケットを作成するのがsocket関数です。

ソケット関数の定義

socket関数は次のように定義されています。

   SOCKET s=socket(int af,int type,int protocol);
af アドレスファミリ。通常はAF_INET。
type 通信プロトコル。TCP通信かUDP通信かの設定を行う。以下のパラメータがある。
SOCK_STREAM

通信はTCPで行われる

SOCK_DGRAM

通信はUDPで行われる

protocol アドレス・ファミリに応じた固有のプロトコル。通常は0でよい
戻り値 成功の場合はソケットが返る。失敗の場合はINVALID_SOCKET。

socket関数の使い方

TCPのソケットを生成するときは、通常次のような記述をします。

   SOCKET s=socket(AF_INET,SOCK_STREAM,0);
   if(s==INVALID_SOCKET){
       printf("ソケットを生成できません");
       exit(1);
   }
   /* 以下、bindへ続く */

ソケットの生成は以上で完了です。


アドレス情報を結び付ける(bind)

 socketで作成されたソケットに、 サーバーのIPアドレス及びポート番号を結び付けるのが、bind関数です。 bind関数はSOCKADDR_IN構造体でこれらの情報を受け取るので、まずSOCKADDR_IN構造体を示します。

SOCKADDR_IN構造体

struct sockaddr_in{
    short           sin_family;
    unsigned short  sin_port;
    struct in_addr  sin_addr;
    char            sin_zero[8];
};
typedef sockaddr_in SOCKADDR_IN;
sin_family アドレスファミリ。AF_INET。
sin_port ポート番号
sin_addr IPアドレス
sin_zero パディング

bind関数の定義

   bind関数は次のように定義されています。
    int bind(SOCKET s,const struct sockaddr FAR* name,int namelen);
s ソケット
name アドレス情報の入ったSOCKADDR_IN構造体をSOCKADDR構造体にキャストしたもの
namelen SOCKADDR_IN構造体の大きさ。sizeof(SOCKADDR_IN)。
戻り値 成功の場合は0。失敗の場合はSOCKET_ERROR。

bind関数の使い方

 bindを使ってソケットにアドレス情報を結び付けるには次のような記述をします。

   SOCKADDR_IN sain;
   sain.sin_family=AF_INET;
   sain.sin_addr.s_addr=INADDR_ANY;
   sain.sin_port=htons(port_number); 

   if(!bind(s,(SOCKADDR*)&sain,sizeof(sain)){
       printf("バインドに失敗しました");
       exit(1);
   }
   /* 以下、listenへ続く */

 ソケットへアドレス情報を結び付ける作業は以上で終了です。 なお、上で出てきたhtons関数はコンピュータの数値表現をネットワークの数値表現に変更するものです。 リトルエンディアンをビッグデンディアンに変更します。


リクエストの受信待機(listen)

 listen関数はリクエストの受信待機を開始する関数です。 接続要求のあったリクエストは、サーバーによって接続が確立されるまで待機状態になりますが、 listen関数はこの待機状態になっているリクエストを管理します。

listen関数の定義

 listen関数の定義は次のようになっています。
    int listen(SOCKET s,int backlog);
s ソケット
backlog 待機状態のリクエストが格納されるキューの大きさ
戻り値 成功の場合は0。失敗の場合はSOCKET_ERROR。

listen関数の使い方

   if(!listen(s,10)){
       printf("listenに失敗しました。");
       exit(1);
   }
   /* 以下、accept へ続く */

接続を確立する(accept)

 accept関数は接続待ちのリクエストを一つ取り出し、接続を確立する関数です。 接続を確立すると、新しいソケットが作成されます。 クライアントと通信は、その新しいソケットを使って行います。

accept関数の定義

accept関数は次のように定義されています。

    SOCKET t=accept(SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen);
s ソケット
addr 接続先のアドレス情報が格納される
addrlen addrの大きさが格納される。(addrの大きさを格納しておく必要がある。)
戻り値 成功の場合は新しいソケット。失敗の場合はINVALID_SOCKET。

accept関数の使い方

   SOCKET t;
   SOCKADDR_IN ta;
   int ta_len;

   ta_len=sizeof(ta);
   t=accept(s,&ta,&ta_len);
   if(t==INVALID_SOCKET){
       printf("接続が確立できませんでした。");
       exit(1);
   }
   /* 以下、send関数及びrecv関数を使って送受信操作ができる */

 接続待ちキューにリクエストがなかった場合、 accept関数を実行するとプログラムはここでブロックします。 そして接続要求があると、ここから動作を再開します。


ソケットを閉じる(closesocket)

closesocket関数は通信を切断してソケットを閉じる関数です。この関数は、

   int closesocket(SOCKET s);

のように定義されていて、成功すれば0を返します。 失敗すると、SOCKET_ERRORが返ります。


クライアントの作成法(TCP)

 この章ではTCPクライアントの構成法について解説します。 図2にクライアント作成での基本的な操作の流れを示しました。 socket関数はすでに解説したので、ここでは主にconnect関数について解説します。 なお、サーバー作成法では解説しなかったsend関数やrecv関数についても説明します。

クライアント作成の流れ

(図2)クライアント作成の流れ


接続要求を出して接続する(connect)

 connect関数はサーバーに接続を要求を出して、接続を確立します。

connect関数の定義

   int connect(SOCKET s,const struct sockaddr FAR* name,int namelen);
s ソケット
name 接続先のアドレス情報の入ったSOCKADDR_IN構造体をSOCKADDR構造体にキャストしたもの
namelen SOCKADDR_IN構造体の大きさ。sizeof(SOCKADDR_IN)。
戻り値 成功の場合は0。失敗の場合はSOCKET_ERROR。

connect関数の使い方

   struct hostent* h;
   SOCKADDR_IN sa;

   h=gethostbyname("hostname");
   if(!h){
       printf("ホストが見つかりませんでした。");
       exit(1);
   }

   sa.sin_family=AF_INET;
   sa.sin_addr.s_addr=h->h_addr;
   sa.sin_port=htons(port_number); 

   if(!connect(s,(SOCKADDR*)&sa,sizeof(sa)){
       printf("接続を確立できませんでした。");
       exit(1);
   }
   /* 以下、send関数及びrecv関数を使って送受信操作ができる */

 ここで、gethostbyname関数はDNSサーバーに問い合わせて、ドメインからIPアドレスを取得する操作を行います。 gethostbyname関数を実行するとhostent構造体へのポインタが返ります。 この構造体の中に接続先のIPアドレスがあるので、これを利用してアドレス情報を作成します。


データを送信する(send)

 サーバーではaccept関数により接続されたソケットができ、 クライアントではconnect関数によって接続されたソケットができます。 send関数及び次に解説するrecv関数は、 このようにして接続が確立されたソケットを通してデータを送受信するための関数です。 ここではまず、send関数について解説します。

send関数の定義

    int send(SOCKET s,const char FAR* buf,int len,int flags);
s ソケット
buf 送信するデータへのポインタ
len 送信するデータの長さ
flags パラメータ 0で良い
戻り値 成功の場合は送信したデータ長(バイト)。失敗の場合はSOCKET_ERROR。

send関数の使い方

 文字列"Hello\r\n"を送信する場合は次のようになります。

   int ok;
   const char* st="Hello\r\n";

   ok=send(s,st,7,0);
   if(ok==SOCKET_ERROR){
       printf("送信できませんでした。");
       exit(1);
   }

データを受信する(recv)

 データを受信操作を行うのがrecv関数です。 この関数を通して、送られてきたデータを受信することができます。

recv関数の定義

    int recv(SOCKET s,char FAR* buf,int len,int flags);
s ソケット
buf 受信バッファへのポインタ
len 受信バッファの大きさ
flags パラメータ 0で良い
戻り値
成功の場合
 受信したデータ長を返す。(1〜len)
ソケットが閉じられた場合
 0を返す。
失敗の場合
 SOCKET_ERRORを返す。

recv関数の使い方

 recv関数は受信長が必ずしもlenまで達しているとは限りません。 そこで、recv関数を使って、lenまで受信する関数receiveを紹介します。


int receive(SOCKET s,char* buf,int* len){
    int revd_size;
    int tmp;
    revd_size=0;
    while(revd_size<*len){                       
        tmp=recv(s,buf+revd_size,*len-revd_size,0);
        if(tmp==SOCKET_ERROR){     /* エラーが発生 */
            *len=received;         
            return SOCKET_ERROR;
        }
        if(tmp==0){         /* ソケットが切断された */
            *len=received;         
            return 0;
        }
        received+=tmp;
    }
    *len=received;      
    return received;
}

 このreceive関数は、lenまで受信するかソケットが閉じられるまでrecv関数を呼び続けます。 返り値はrecv関数と同じです。しかし、読み込みの途中でソケットが閉じられた場合を想定して、 引き数lenはポインタとし、ここに受信したデータ長を返すようにしてあります。


ソケットを利用するにあたっての注意事項

 ソケットを利用するにあたって、いくつか注意すべき点があります。 それは、インクルードファイルやライブラリの指定と、ソケットライブラリの初期化です。

インクルードファイルとライブラリ

 インクルードファイルは<winsock2.h>、 ライブラリはws2_32.libです。

ソケットライブラリの初期化

 ソケット関連の関数を使う前に、次の初期化操作を行う必要があります。
    WORD wVersionRequested = MAKEWORD(2,2);
    WSADATA wsaData[1];
    if(WSAStartup(wVersionRequested,wsaData)){
        printf("ソケットライブラリの初期化に失敗しました。");
        exit(1);
    }
Multisoft-labのホーム    ご意見・ご感想等の受付


Copyright © 2004 Multisoft-lab   All rights reserved.