Internet Programming

UDPクライアントの作成手順(同期型)

[1997/11/28]

■UDPクライアント作成の流れ

UDPクライアントの簡単な流れは、
1.ソケットの作成を行う。

2.ローカルのポート番号を指定する。

3.サーバを指定して送受信を行う。

4.ソケットを破棄する。
の4段階から成り立ちます。

TCPとは違って、サーバには接続しません。
「ローカルのポート番号を指定する。」とは、サーバからデータを受信するときに使うローカルのポート番号を指定するということです。
送信は、送信先のサーバのIPアドレスとポート番号を指定してデータを送信します。
受信は、受信するデータを送信したサーバのIPアドレスとポート番号を指定してデータを受信します。

実際にプログラムで使うWinSockのAPI(Socket Interface)を使用してもう少し詳しくあらわすと、

  1. WSAStartup関数でWinSockの初期化を行います。
    (プログラムの開始時のみ)

  2. socket関数でソケットの作成を行います。

  3. bind関数でローカルのポート番号を指定する。

  4. サーバのIPアドレスを取得します。inet_addr関数およびgethostbyname関数でサーバのIPアドレスの取得を行います。
    (送信と受信で使用します)

  5. ソケットに対して、sendto関数でサーバを指定して送信、recvfrom関数でサーバを指定して受信を行います。

  6. closesocket関数でソケットを破棄します。

  7. WSACleanup関数でWinSockのリソースを解放する。
    (プログラムの終了時のみ)
となります。

■APIの使用方法

WinSockのAPIを使用した例を示します。

WinSockを使用する際は、
wsock32.libをリンク
winsock.hをインクルード
する必要があります。

ソース中は、
: ソケット
: コメント
太字 : メインのAPI
となっています。

●WinSockの初期化の例

	WORD wVersionRequested;
	int  nErrorStatus;
	WSADATA wsaData;

	/* WinSockの初期化を行う */
	wVersionRequested = MAKEWORD(1, 1);			/* バージョン 1.1 を要求する */
	nErrorStatus = WSAStartup(wVersionRequested, &wsaData);
	if (atexit((void (*)(void))(WSACleanup))) {		/* 終了時にWinSockのリソースを解放するようにしておく */
		fprintf(stderr,"atexit(WSACleanup)失敗\n");
		exit(-1);
	}
	if ( nErrorStatus != 0 ) {
		fprintf(stderr,"WinSockの初期化失敗\n");
		exit(-1);
	}

WSAStartup関数の第一引数は、要求するWinSockのバージョンを入れます。
もし、バージョン 2.0 を要求する場合は、
MAKEWORD(2, 0)
と指定します。

atexit((void (*)(void))(WSACleanup))は、終了時にWSACleanup関数を実行するようにしています。

●ソケットの作成例

	int soc;		/* ソケット(Soket Descriptor) */

	/* socにソケットを作成します */
	soc = socket(PF_INET, SOCK_DGRAM, 0);
	if(soc == INVALID_SOCKET){
		fprintf(stderr,"ソケット作成失敗\n");
	}

socket関数の第1引数には、インターネットを表すPF_INETを指定して、第2引数にはUDPを表すSOCK_DGRAMを指定しています。
第3引数は、プロトコルを指定します。

●ローカルのポート番号を指定する例

	struct  sockaddr_in	 sockname;		/* ソケットのアドレス */

	/* ソケットのアドレスの構造体にサーバのIPアドレスとポート番号を設定します */
	sockname.sin_family	   = AF_INET;					/* インターネットの場合 */
	sockname.sin_addr.s_addr  = INADDR_ANY;				/* 自分のIPアドレスを使うようにする */
	sockname.sin_port	   = htons((unsigned short)local_port);		/* ローカルのポート番号 */
	memset(sockname.sin_zero,(int)0,sizeof(sockname.sin_zero));

	if(bind(soc,(struct sockaddr *)&sockname,sizeof(sockname)) == SOCKET_ERROR){
		fprintf(stderr,"受信ポート番号の指定に失敗\n");
	}

サーバが送信したデータを受信するローカルのポート番号をbind関数で指定しています。

bind関数の第1引数は、socket関数で作成されたソケットです。
第2引数は、ソケットのアドレスの構造体へのポインタで、第3引数はその構造体のサイズです。

ポート番号の指定のところで、htons((unsigned short)port)とありますが、これはホストのバイト並び(16bit)ネットワークのバイト並び(16bit)に変換しているのです。
32bitのホストのバイト並びをネットワークのバイト並びに変換するには、htonl関数を使います。
逆にネットワークのバイト並びをホストのバイト並びに変換するには、ntohs関数ntohl関数があります。

●サーバのIPアドレス取得の例

	unsigned long serveraddr;		/* サーバのIPアドレス */
	struct hostent *serverhostent;	/* サーバのホスト情報を指すポインタ */

	/* svNameにドットで区切った10進数のIPアドレスが入っている場合、serveraddrに32bit整数のIPアドレスが返ります */
	serveraddr = inet_addr((char*)svName);
	if (serveraddr == -1) {
		/* サーバ名(svName)からサーバのホスト情報を取得します */
		serverhostent = gethostbyname(svName);
		if (serverhostent == NULL) {
			fprintf(stderr,"ホストアドレス取得失敗\n");
		}else{
			/* サーバのホスト情報からIPアドレスをserveraddrにコピーします */
			serveraddr = *((unsigned long *)((serverhostent->h_addr_list)[0]));
		}
	}

svNameにドットで区切った10進数のIPアドレスが入っている場合は、inet_addr関数で32bitの整数値に変換されます。
例)
210.146.62.2 -(inet_addr)→ 23E92D2

svNameにサーバのホスト名が入っている場合は、gethostbyname関数でサーバのホスト情報を取得します。
そして、サーバのホスト情報の中からIPアドレスを取り出します。
例)
www.kinet.or.jp -(gethostbyname)→ サーバのホスト情報(この中のIPアドレスを取り出します)

●サーバに文字列を送信する例

	struct  sockaddr_in	 serversockaddr;		/* サーバのアドレス */

	/* サーバのアドレスの構造体にサーバのIPアドレスとポート番号を設定します */
	serversockaddr.sin_family	 = AF_INET;				/* インターネットの場合 */
	serversockaddr.sin_addr.s_addr  = serveraddr;				/* サーバのIPアドレス */
	serversockaddr.sin_port	 = htons((unsigned short)port);		/* ポート番号 */
	memset(serversockaddr.sin_zero,(int)0,sizeof(serversockaddr.sin_zero));

	/* サーバを指定して文字列(buf)を送信します */
	/* 送信した文字列は指定したサーバに届きます */
	if(sendto(soc, buf, lstrlen(buf), 0,(struct sockaddr *)&serversockaddr,sizeof(serversockaddr)) == SOCKET_ERROR){
		fprintf(stderr,"サーバへの送信失敗\n");
	}

sendto関数の第1引数は、socket関数で作成されたソケットです。
第2引数は送信する文字列のバッファで、第3引数は文字列の長さです。
第5引数は、送信先サーバのアドレスの構造体へのポインタで、第6引数はその構造体のサイズです。

●サーバから文字列を受信する例

	#define RECVSIZE 256	/* 受信サイズ */

	char buf[256];		/* 受信するバッファ */
	int buf_len;		/* 受信したバイト数 */

	struct  sockaddr_in	 serversockaddr;		/* サーバのアドレス */
	int sockaddr_size;		/* サーバのアドレスのサイズ */

	/* サーバのアドレスの構造体にサーバのIPアドレスとポート番号を設定します */
	serversockaddr.sin_family	 = AF_INET;				/* インターネットの場合 */
	serversockaddr.sin_addr.s_addr  = serveraddr;				/* サーバのIPアドレス */
	serversockaddr.sin_port	 = htons((unsigned short)port);		/* ポート番号 */
	memset(serversockaddr.sin_zero,(int)0,sizeof(serversockaddr.sin_zero));

	/* サーバを指定して文字列を受信します */
	/* 受信した文字列は buf に入ります */
	sockaddr_size = sizeof(serversockaddr);
	buf_len = recvfrom(soc, buf, RECVSIZE - 1, 0,(struct sockaddr *)&serversockaddr,&sockaddr_size);
	if (buf_len == SOCKET_ERROR ){
		fprintf(stderr,"サーバからの受信失敗\n");
	}
	buf[buf_len] = '\0';	/* 受信したバッファの後ろにヌル文字を付加する */

recvfrom関数の第1引数は、socket関数で作成されたソケットです。
第2引数は受信するバッファで、第3引数は受信バッファのサイズです。
第5引数は、受信するデータを送信したサーバのアドレスの構造体へのポインタで、第6引数はその構造体のサイズの入った変数へのポインタです。

●ソケットを破棄する例

	/* ソケットを破棄する */
	closesocket(soc);

closesocket関数の第1引数は、socket関数で作成されたソケットです。

●WinSockのリソースを解放する例

	/* WinSockのリソースを解放します */
	WSACleanup();

WinSockのリソースを解放する関数ですが、プログラムの初めにatexit()を使って終了時に呼び出されるようにしておけばいいと思います。

●エラーコードを取得する場合

WinSockのAPIを使用してエラーになった場合、WSAGetLastError関数でエラーコードを取得することができます。


mail
メールアドレス
<nakka@nakka.com>


Internet Programming
Internet Programming