サーバー


受信待機

ネットワーク上で情報を通信しあうためには
サービスを提供するサーバーと、サービスを受けるクライアントに分かれます
この関係を、クライアント/サーバーシステムと呼んでいます

通信においては、サーバーがなくては何も始まりません
そこで、まず最初にサーバーの建築方法から解説します
サーバーの建築といっても、単純にレスポンスを返すだけのプログラムです
POP3 や FTP、HTTP のようなプログラムを作るには、ソケットの知識だけではなく
プロトコルの仕様も知る必要がありますが、この講座はプロトコルの解説は対象外です

WinSock では、クライアントとの情報交換はソケットで行います
本来、ソケットの思想から考えればファイルの入出力関数を使って行えるべきですが
WinSock は、ソケット入出力のために専用の関数を用意しています

ソケットは socket() 関数を用いて作成します
ファイルハンドルのように SOCKET 型としてソケットが識別されます
この SOCKET を、一般にソケット記述子と呼びます

SOCKET socket (int af , int type , int protocol);

af にはアドレスの種類を表すアドレスファミリを
type にはソケットの種類を表すソケットタイプを定数で指定します
protocol にはソケットプロトコルを決定しますが、これは af と type から決定できます
0 を指定すれば WinSock によってデフォルトのプロトコルが使われます
プロトコルは通常 TCP か UDP が使われます

関数が成功すればソケットが、失敗すれば INVALID_SOCKET が返されます
socket() が返した SOCKET は、ファイルハンドルのような存在で
今後、ソケットの通信はこの値を使って行うことができます

type に指定するソケットタイプは、ストリームタイプとデータグラムタイプです
バイトストリームの場合は SOCK_STREAM 定数を指定します
この場合、信頼性の高い TCP 通信を行うことができます
今回は、AF_INET アドレスファミリの SOCK_STREAM タイプで通信を行いましょう

このほかにはデータグラムを使う、信頼性の低い通信も可能です
これは SOCK_DGRAM 定数を指定し、UDP による通信を行うことができます
UDP は、データの到着順などをチェックしないため、破損の可能性がありますが
破損が気にならないデータタイプであれば、より高速な通信を行うことができます
これは、圧縮されたメディア情報(MPEG や Windows Media)に適していると考えられます

サーバーは、次に bind() 関数を呼び出さなければなりません
この関数は、ソケットに名前をつけるという役割を果たします
bind() によって、サーバーは受信に使うポートを設定することができます
int bind (
	SOCKET s ,
	const struct sockaddr FAR*  name , int namelen
);
s にはソケット記述子を、name にはソケットアドレスの情報を
namelen には name で指定した構造体のサイズをバイト単位で指定します
関数は、成功すれば 0 を、失敗すれば SOCKET_ERROR を返します

name に指定する sockaddr 構造体は次のように定義されています
因みに、やはり SOCKADDR という別名が Windows の命名規則から与えられています
struct sockaddr {
        u_short    sa_family;
        char       sa_data[14];
};
sa_family にはアドレスファミリを表す定数を指定します
sa_data は sa_family によってプロトコル固有の情報を与えるための空き領域です
TCP/IP を利用する場合、sa_data の上位6バイトを利用します
そのために sockaddr_in 構造体も用意されています
struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};
sin_family には、やはりアドレスファミリを指定します
sin_port にはポート番号、sin_addr には IP アドレスを指定します
sin_zero メンバは、利用しない 0 の値で初期化される領域です
この構造体は、sockaddr とサイズが同じなので、論理的互換性があります

bind() 関数の name には、アドレスファミリとポート番号を知らせるために使います
サーバーのアドレスは常にローカルホストであることが分かっているため必要ありません
それ以外の値は 0 で初期化しておきましょう

次に listen() 関数を使って受け入れキューの初期化を行います
サーバーは、同時に複数の要求が行われることを想定しなければなりません
そこで、接続要求を入れるキューを作成し、要求を待機させる必要があります

int listen (SOCKET s , int backlog);

s にはソケット記述子を、backlog にはキューの最大サイズを指定します
関数が成功すれば 0 、失敗すれば SOCKET_ERROR を返します
キューのサイズが無効だった場合、WinSock は最も近い有効な値を使います

最後に accept() 関数を使ってサーバーは要求を待機します
サーバーは、お客さんがいなければ仕事がありません
accept() 関数は、スレッドを要求があるまで待機させてしまいます
GUI アプリケーションの場合は、プロセスの主スレッドで呼び出さないようにしてください
SOCKET accept (
	SOCKET s,
	struct sockaddr FAR* addr , int FAR* addrlen
);
s にはサーバーのソケット記述子を指定します
addr には要求してきたクライアントの情報を格納するための
sockaddr 構造体へのポインタを指定します

addrlen には addr のサイズを格納する数値へのポインタを指定します
関数が返れば addr にクライアントの情報が、addrlen に addr のサイズが格納されます
関数が成功すれば要求者のソケットが、失敗すれば INVALID_SOCKET が返ります
accept が返したソケット記述子を使って、クライアントと通信を行うことができます

さて、これでサーバーの準備は終了です
ポートやアドレスを指定するときには、バイトオーダーに注意してください
#include <stdio.h>
#include <winsock2.h>

int main() {
	WSADATA wsaData;
	LPHOSTENT host;
	SOCKET s;
	SOCKADDR_IN name = { AF_INET } , client;
	int client_addr = sizeof(SOCKADDR);

	WSAStartup(2 , &wsaData);
	name.sin_port = htons(2048);

	s = socket(AF_INET , SOCK_STREAM , 0);
	bind(s , (SOCKADDR *)&name , sizeof(SOCKADDR));
	listen(s , 1);
	accept(s , (SOCKADDR *)&client , &client_addr);

	printf("アクセスがありました\nポート %d : アドレス = %s\n" ,
		client.sin_port , inet_ntoa(client.sin_addr)
	);
	WSACleanup();
	return 0;
}
このプログラムを実行すると、ポート番号 2048 を使って要求を待機します
Telnet を使ってサーバーにアクセスすると、accept() が制御を返します
Telnet を起動し open localhost 2048 と入力して実験してみるとよいでしょう
プログラムは、要求者の情報を表示して処理を終了させます

ただし、このプログラムは正しい終了処理を行っていないので、行儀が悪いと言えます
より正しいプログラムは、通信者に切断を通達するなどの処理を行うべきです
何も言わず、一方的に電話を切るのはマナー違反であることと同じです

UDP 通信であれば、一方的に切断してもかまわないのですが
TCP のように対話型の通信を行っている場合は、最初に相手に切断を通達します
切断は shutdown() 関数を用います

int shutdown (SOCKET s , int how);

s には切断するソケットを、how には切断する種類を指定します
関数が成功すれば 0、失敗すれば SOCKET_ERROR が返ります
how で指定できる値は、次の定数のいずれかです

定数解説
SD_RECEIVE 受信を無効にする
SD_SEND 送信を無効にする
SD_BOTH 受信、送信を無効にする

この場でのプログラムは、まだクライアントとの通信を行っていませんが
クライアントと通信中の場合は、まず最初に送信を無効にします
その後、クライアントが送ってきた情報を全て受信し、その後に受信も無効化します

切断が終了すれば、ソケット記述子を解放します
ソケット記述子の解放は closesocket() 関数を使います

int closesocket (SOCKET s);

s には解放するソケットを指定します
関数が成功すれば 0、失敗すれば SOCKET_ERROR を返します
#include <stdio.h>
#include <winsock2.h>

int main() {
	WSADATA wsaData;
	LPHOSTENT host;
	SOCKET s;
	SOCKADDR_IN name = { AF_INET } , client;
	int client_addr = sizeof(SOCKADDR);

	WSAStartup(2 , &wsaData);
	name.sin_port = htons(2048);

	s = socket(AF_INET , SOCK_STREAM , 0);
	bind(s , (SOCKADDR *)&name , sizeof(SOCKADDR));
	listen(s , 1);
	accept(s , (SOCKADDR *)&client , &client_addr);

	printf("アクセスがありました\nポート %d : アドレス = %s\n" ,
		client.sin_port , inet_ntoa(client.sin_addr)
	);

	shutdown(s , SD_BOTH);
	closesocket(s);
	WSACleanup();
	return 0;
}
このプログラムは、先ほどのプログラムに終了処理を追加したものです
最後に、shutdown() 関数と closesocket() を使ってソケットを切断しています。
UDP 通信であれば closesocket() を呼び出すだけでかまいません


socket

SOCKET socket (int af , int type , int protocol);
ソケットを作成します
af
アドレスファミリを指定します
type
ソケットタイプを指定します
protocol
ソケットのプロトコルを指定します。0 を指定すれば af と type から WinSock が判断します
戻り値
成功すればソケットが、失敗すれば INVALID_SOCKET
ソケットタイプには次の値のいずれかを指定できます

定数解説
SOCK_STREAM バイトストリームを使用し、信頼性の高い TCP 通信を行う
SOCK_DGRAM データグラムを使用し、信頼性の低い UDP 通信を行う

bind()

int bind (
	SOCKET s ,
	const struct sockaddr FAR*  name , int namelen
);
サーバーソケットに名前をつけます
s
ソケットを指定します
name
ソケットアドレス SOCKADDR 構造体へのポインタを指定します
namelen
name のサイズをバイト単位で指定します
戻り値
成功すれば 0 、失敗すれば SOCKET_ERROR

listen()

int listen (SOCKET s , int backlog);
ソケットを受信待機モードにし、保留接続キューのサイズを確保します
s
ソケットを指定します
backlog
キューに入れる接続要求の最大数を指定します
戻り値
成功すれば 0 、失敗すれば SOCKET_ERROR

accept()

SOCKET accept (
	SOCKET s,
	struct sockaddr FAR* addr , int FAR* addrlen
);
サーバーソケットを接続要求待ち状態にします
s
ソケットを指定します
addr
クライアントのアドレスが格納されるバッファへのポインタを指定します
addrlen
addr のサイズを格納する整数へのポインタを指定します
戻り値
クライアントソケット、失敗すれば INVALID_SOCKET

shutdown()

int shutdown (SOCKET s , int how);
ソケットを切断します
s
切断するソケットを指定します
how
切断する種類を指定します
戻り値
成功すれば 0、失敗すれば SOCKET_ERROR

how で指定できる値は、次の定数のいずれかです

定数解説
SD_RECEIVE 受信を無効にする
SD_SEND 送信を無効にする
SD_BOTH 受信、送信を無効にする

closesocket()

int closesocket (SOCKET s);
ソケットを閉じます
s
閉じるソケットを指定します
戻り値
成功すれば 0、失敗すれば SOCKET_ERROR



前のページへ戻る次のページへ