■ WinSockとは
ウィンドウズでソケットを使ってプログラムを書くための API
TCP/IPなどインターネットを使って通信を行うプログラムが書ける
NetworkService( API )
最初 WinSock は、TCP/IP プロトコルのために開発されましたがその後も
バージョンアップを重ね、
現在ではプロトコルに依存しないWinSock 2 が主流として使われてます
SocketAPI を利用すると TCP/IP の詳細な処理は気にしなくてもいい
Socket という相手への
プラグを経由して Message のやりとりをすればいいから ラクチン !
■ NetworkProgram の骨格
POINT
ネットワーク上で情報を通信しあうためには相手と自分という最低 2つのProgramが必要になる。
先に起動して相手の接続を待っているプログラムと
後からサーバに接続を試みるプログラムの2つ
ネットワークアプリケーションというのは、クライアントソフトとサーバーソフト
これら二つがそろって、完成されたサービスとなる
TCP を使った通信プログラムの書き方 は サーバ と クライアントで異なる。
POINT
一度接続できれば、サーバとクライアントで通信方法に同じ
POINT
POP3 や FTP、HTTP のようなプログラムを作るには、ソケットの知識だけではなく
プロトコルの仕様も知る必要がある
DESC
NetworkProgram で用意するのは [ ServerProgram ] と [ ClientProgram ]
次のように 2 つの Program をつくる
サーバー クライアント
↓ ↓
WSAStartup WSAStartup
↓ ↓
socket 接続待ちをする socket
IP, ポートを設定
↓ |
bind |
↓ |
listen ↓
↓ ←――――― connect // 相手( 接続待ちのServer )を IP, Port で指定
accept ―――――→ |
↓ ↓
send/recv ←――――→ send/recv // 通信する
↓ ↓
closesocket closesocket
↓ ↓
WSACleanup WSACleanup
サーバーはクライアントからの接続を待ちうけ
通信用にソケットを生成して通信する
Server と Client がやりとりするためのおおまかな流れ
1. まず Server を起動してたちあげておく
Server が自分で決めた Port(入り口)をあけてリクエスト待ちをする
2. サービスをうけたいときに Client を実行する
Client をたちあげて マシン名と Port を指定して通信を開始する
POINT
ClientProgram は ServerProgram の [ HostName ] , [ IP ]を知っている必要がある
■ winsock.の初期化
はじめての Winsock プログラム。
DLL を初期化して終了するだけ。
#include < winsock2.h>
int main()
{
WSADATA wsaData;
// Major , Minor -> WORD 型を pack するマクロ.
// typedef unsigned short WORD;
// wsaData に格納される.
WSAStartup( MAKEWORD(2,0), &wsaData );
// 終了処理.
WSACleanup();
return 0;
}
エラー処理を追加してみる。
#include < stdio.h>
#include < winsock2.h>
int main()
{
WSADATA wsaData;
int err;
err = WSAStartup( MAKEWORD(2,0), &wsaData );
if (err != 0) {
switch (err) {
// ネットワークサブシステムがネットワークへの接続を準備できていない
case WSASYSNOTREADY:
printf("WSASYSNOTREADY\n");
break;
// 要求されたwinsockのバージョンがサポートされていない
case WSAVERNOTSUPPORTED:
printf("WSAVERNOTSUPPORTED\n");
break;
// winsockが処理できる最大プロセス数に達した
case WSAEINPROGRESS:
printf("WSAEINPROGRESS\n");
break;
// 第二引数であるlpWSAData は有効なポインタではない
case WSAEPROCLIM:
printf("WSAEPROCLIM\n");
break;
case WSAEFAULT:
printf("WSAEFAULT\n");
break;
}
}
WSACleanup();
return 0;
}
■ シンプルサーバー(SimpleServer)
SAMPLE
シンプルサーバー ポート7777 を利用
シンプルサーバー ポート7777 を利用 GUI
DESC
一般的な TCP を使ったサーバは、複数回クライアントからの接続を受ける
( 接続が複数である点に注意. while(1){} の部分が複数する部分. )
POINT
接続要求をうける Code を while (1) で繰りかえすこと
#include < stdio.h>
#include < winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(7777);
adr.sin_addr.S_un.S_addr = INADDR_ANY; // サーバ側で割り当てられている IP を自動で設定
// Socket へアドレスを設定
bind(sock, (struct sockaddr *)&adr, sizeof(adr));
listen( sock, 5 );
// ここで 受付を繰り返す
while (1) {
struct sockaddr_in client;
int len = sizeof(client);
// 接続要求を受け付ける。
// スレッドはブロックされる( GUI では MainThread で呼ばないこと )
printf( "クライアントからの接続をまってます\n" );
SOCKET sockCli = accept(sock, (struct sockaddr *)&client, &len);
// クライアントへ送信する
printf( "クライアントからの接続を受け付けました。メッセージを送信します\n" );
const char *msg = "HELLO\n";
send(sockCli, msg, strlen(msg), 0);
// 接続解除
// クライアントとのセッションを終了する
// こちら側の通信を切る
// [ Client ] < ---------- [ Server ]
closesocket( sockCli );
}
WSACleanup();
return 0;
}
POINT
先にサーバープログラムを起動して待機させておく。
次に好きなタイミングでクライアントを起動する。
# Server を起動しておく
shell> ./simplesrv.exe
# telnet で接続する
shell> telnet localhost 7777
■ シンプルクライアント(SimpleClient)
SAMPLE
シンプル クライアント port 7777 へ接続
シンプル クライアント port 7777 へ接続 GUI
クライアントプログラムはサーバがリスンしているポートへコネクトする。
接続後のやりとりの手順( プロトコル )はネットワークアプリケーションの次第。
単にサーバから "HEELO" の文字だけもらって終了する。
int main(int argc, char *argv[])
{
WSADATA wsaData;
char buf[32];
WSAStartup(MAKEWORD(2,0), &wsaData);
// TCP ソケットの作成
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
// 送信先のサーバのアドレスを設定
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(7777);
// POINT
// dot 表記 | DNS の名前がくる可能性がある.
// IPv4 ( dot 表記 )から取得
char *ip = "localhost";
adr.sin_addr.S_un.S_addr = inet_addr( ip );
// 無効だったので, DomainName から検索する
if (adr.sin_addr.S_un.S_addr == 0xffffffff) {
struct hostent *host;
// 名前( DNS がつけた名前 )から IP をひく
host = gethostbyname( ip );
if (host == NULL) {
return 1;
}
adr.sin_addr.S_un.S_addr =
// host から IP( 32 bit )を取得可能.
*(unsigned int *)host->h_addr_list[0];
}
// 接続. -> server::accept() へ
connect( sock, (struct sockaddr *)&adr, sizeof(adr) );
memset(buf, 0, sizeof(buf));
int n = recv(sock, buf, sizeof(buf), 0);
printf("%d, %s\n", n, buf);
closesocket(sock);
WSACleanup();
return 0;
}
■ accept.した相手を表示するサンプル
DESC
accept の第二引数は、接続した相手に関する情報( IP, port... )を含む
IPアドレスの表示には inet_ntoa を利用する.
inet_ntoa は、引数として渡した struct in_addr を表現する文字列を返す.
printf(
"accepted connection from %s, port=%d\n",
// 32 bit Addr を 文字列に変換.
inet_ntoa( client.sin_addr ),
// unsigned short の値を host の ByteOrder に変換.
ntohs( client.sin_port )
);
■ エコーサーバー
SAMPLE
シンプル エコークライアント port 7777 へ接続 GUI
シンプル エコーサーバ port 7777 でリスン GUI
■ バイナリファイルの取得
SAMPLE
ファイルをダウンロードして %HOMEPATH%test.bmp として保存
HTTP プロトコルを利用して、バイナリファイルを取得する。
テキストファイルを取得する場合と処理はほとんど同じだが、
バイナリデータは \0 を含むケースがあるためその点だけ注意する。
// 同じように接続する。
int ret = connect( sock, (struct sockaddr *)&adr, sizeof(adr) );
// バイナリファイルを取得するHTTPメッセージを送信する。
const char *msg = "GET /bin/test.bmp HTTP/1.0\r\nHost: ooo.iiyudana.net\r\n\r\n";
send( sock, msg, strlen(msg), 0 );
// 受信バッファ
char *p = new char[1024*1024*10];
int len = 0;
while (1) {
// すべてのデータをネットワーク先から受信するまで recv を続ける。
ret = ::recv(sock, buf, sizeof(buf) - 1, 0 );
buf[ret] = '\0';
len += ret;
// 今回の受信分をまとめる
if ( ret ) {
memcpy( p, buf, ret );
p += ret;
}
// すべてを受信したら完了。
if ( ret == SOCKET_ERROR || ret == 0 ) {
break;
}
}
// 受信したデータから HTTP レスポンスのヘッダ部分を除去してからファイルに書き込む。
{
int idx = 0;
while (1) {
if ( memcmp( ( p + idx ), "\r\n\r\n", 4 ) == 0 ) {
p[idx] = '\0';
printf( "HTTP response header\n\n%s\n\n\n", p );
break;
}
idx ++;
}
// バイナリファイルとして書く。
{
const char *d = getenv( "HOMEPATH" );
std::string path = "c:\\";
path += d;
path += "\\test.bmp";
FILE *fp = fopen( path.c_str(), "wb" );
fwrite( p + (idx+4), len - (idx + 4), 1, fp );
fclose( fp );
}
}
■ Block.処理を回避する
DESC
TCP/IP を利用した 通信プログラムでは相手からの返答のまちがはいる
問題は accept() send() recv() が処理まちになること
そのため
スレッド立てて、バッファ用意して何とかすることにする
スレッド立てると、送信受信はいいが接続、切断がが非同期に来てメンドイ
さらにサーバーで1コネクション1スレッド立ててた スレッド数
WSAEventSelect を利用
// 何かしらの NetworkEvent が起こると Event をシグナル状態にして知らせてくれる
WSAWaitForMultipleEvents
を使用すれば WSA_MAXIMUM_WAIT_EVENTS
まで同時に待ち受けることができる
// winnt.h
#define MAXIMUM_WAIT_OBJECTS 64
// winsock2.h
#define WSA_MAXIMUM_WAIT_EVENTS MAXIMUM_WAIT_OBJECTS
■ クライアントごとにスレッドを作成
SAMPLE
チャットクライアント
チャットサーバー port 7777
次のよう流れで処理をする。
while(1) {
// クライアントのソケットを受けとる。
SOCKET sock = accept();
// この後に次のクライアントの相手をする必要がある。
// そこでスレッドに accept() した相手と対話をしてもらう。
CreateThread( ClientHandler, arg );
// すぐに次のクライアントの待ちに入る。
}
// 各クライアントとの対話処理の本体
void ClientHandler( void *arg ) {
// クライアントのソケットを受け取る
SOCKET sock = *((SOCKET *)arg);
free( arg );
// エコーサーバーの処理。
char buf[256];
int len = recv(sock, buf, sizeof(buf), 0);
buf[ len ] = '\0';
// スレッドの効果を確認するため、少し寝てみる。
Sleep( 5 * 1000 );
send( buf, strlen(buf), 0 );
closesocket( sock );
}
// サーバーのメインではアクセプトのみを繰り返す。
// 受付と案内だけをする。
void main() {
listen();
while (1) {
// クライアントの接続を受け付ける
SOCK sock = accept();
SOCKET *p = malloc ( sizeof(SOCKET) );
*p = sock;
// 後のことはスレッドにまかせて
CreateThread( 0, 0,
(LPTHREAD_START_ROUTINE)ClientHandler, p,
0, 0 );
// すぐに次の客をまつ。
}
}
■ select.を利用する
// サーバーソケットを作成する
SOCKET createServerSocket( unsigned short port )
{
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.S_un.S_addr = INADDR_ANY; // サーバ側で割り当てられている IP を自動で設定
bind(sock, (struct sockaddr *)&adr, sizeof(adr));
listen( sock, 5 );
return sock;
}
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
fd_set sets;
int i;
SOCKET aSock[2];
struct timeval time = {
3, // 3 秒まつ
0 // usec 単位では指定しない
};
// 2つのポートでリスンする。
for( i=0; i< 2; i++ ){
aSock[i] = createServerSocket( 1001 + i );
}
while(1) {
// リストをクリア
// WARNING
// select の実行毎にクリアする必要がある。しないと -1 が返る
FD_ZERO( &sets );
// 監視したいソケットをリストに追加
for( i=0; i< 2; i++ ){
FD_SET( aSock[i], &sets );
}
int ret = select( 10000, &sets, NULL, NULL, &time );
if ( ret == 0 ) {
printf( "select timeout\n" );
}
else {
printf( "select ret %d \n", ret );
for( i=0; i< 2; i++ ){
if ( FD_ISSET( aSock[i], &sets ) ) {
printf( "request\n" );
struct sockaddr_in adr;
int len;
// これをしないと, request 待ちが常にON になるため Loop が繰り返される
SOCKET sockCli = accept( aSock[i], (struct sockaddr *)&adr, &len );
// クライアントとの対話を普通にすると recv などでブロックされる可能性はある。
char buf[256];
int n = recv(sockCli, buf, sizeof(buf), 0);
buf[ n ] = '\0';
printf( "client mgg %s\n", buf );
send( sockCli, buf, strlen(buf), 0 );
closesocket( sockCli );
}
}
}
}
}
■ select
SYNTAX
int select (
int nr, // 検索するポートの値の上限
fd_set *readfds, // 入力( connect, recv )
fd_set *writefds, // 出力( send )
fd_set *exceptfds,
const struct timeval *timeout // timeout 時間( NULL で永久にまつ )
);
RET
N : I/O が可能になったソケットの数
0 : タイムアウトをした。
-1 : エラー
DESC
指定したソケットリストのイベントをチェックして、fd_set にI/Oが可能になった
ソケット情報をセットして返す。
これによってプログラム側は I/O でブロックされないと仮定して処理を続行できる。
NULL が渡された ソケットリストはチェックされない。
POINT
接続要求( connect )も入力としてみなす。
// ソケットをリストからすべて削除する
FD_ZERO( reads )
// 指定したソケットをリストから削除する
FD_CLR( reads )
// 指定したソケットをリストへ追加する
FD_SET( reads )
// 指定したソケットがリストにあるかチェック
FD_ISSET( sock, reads )
WARNING
select を実行毎にリセットが必要。
しないとエラー( -1 )が返る
// connect, recv のみをチェックする
select( 1000, reads, NULL, NULL, NULL );
■ Win32のEventObjectを使う
■ connectのタイムアウト
POINT
WSA で始まる関数は WinSock 固有の関数。
その時のタイムアウトはWinSockのとき20秒ほどになっています
タイムアウト秒数を指定する関数はないので, イベントを使う。
ブロック型の Winsock 関数 ( connect()) のいくつかは
タイムアウト時間は中に埋め込まれいる
理由は
正しいタイムアウト時間を設定するために必要な情報を全て もつ のは
プロトコルスタックだけだから
setsockopt()において SO_SNDTIMEO または SO_RCVTIMEO オプションを設定すれば
send() と recv() のタイムアウトを変更できる。
しかし connect() にはない。
これ以外の解決方法は ブロック型ソケットのAPIを使用しないこと。
非ブロック型ソケットの関数は全て
タイムアウトをすることが できるようになっている。
bool connectWithTimeout( SOCKET soc, DWORD ip, unsigned short port, int time )
{
// nTimeout : タイムアウト時間(ms単位。1秒=1000ms)
// 戻り値:TRUEで成功、FALSEで失敗
WSAEVENT hEvent;
int ret,err,retflag = FALSE;
DWORD ret;
WSANETWORKEVENTS events;
sockaddr_in adr;
// イベント作成
h = WSACreateEvent();
if(h == WSA_INVALID_EVENT) return false;
// イベント型に(自動的にノンブロッキングになる)
ret = WSAEventSelect(soc, h, FD_CONNECT);
if(ret == SOCKET_ERROR)
{
WSACloseEvent(h);
return false;
}
//接続
memset(&adr, 0, sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.s_addr = ip;
if(connect(soc, (sockaddr *)&adr, sizeof(adr)) == SOCKET_ERROR)
{
err = WSAGetLastError();
//WSAEWOULDBLOCKの時はまだ接続されていないので続ける
if(err != WSAEWOULDBLOCK) goto END;
}
// 指定時間だけイベント発生待機
// 時間が立っても何もなければ WSA_WAIT_TIMEOUT
//
// ret が WSA_WAIT_EVENT_0以外でエラー
ret = WSAWaitForMultipleEvents(1, &h, FALSE, time, FALSE);
if( ret != WSA_WAIT_EVENT_0 ) goto END;
// 発生したイベントの種類を調べる
ret = WSAEnumNetworkEvents(soc, h, &events);
if(ret == SOCKET_ERROR) goto END;
// 今回はコネクト待ちをしていたので, コネクト関連のイベントがあるか調べて、
if( (events.lNetworkEvents & FD_CONNECT)
&&
// エラーがないこともチェックする。
events.iErrorCode[FD_CONNECT_BIT] == 0) {
ret = TRUE;
}
END:
//イベント型を終了
WSAEventSelect(soc, NULL, 0);
WSACloseEvent(h);
//ブロッキングに戻す(必要なら)
ret = 0;
ioctlsocket(soc, FIONBIO, &dwret);
return ret;
}
■ recv.timeout
int recvWithTimeout( SOCKET sock, int time )
{
char buf[1024];
HANDLE h = WSACreateEvent();
WSANETWORKEVENTS events;
// recv, close のイベントを通知してもらうように設定。
WSAEventSelect( sock, h, FD_READ|FD_CLOSE );
while(1)
{
// 時間を指定してまつ
int ret = WSAWaitForMultipleEvents(1, &h, FALSE, time ,FALSE);
// 失敗
if(ret == WSA_WAIT_FAILED)
{
printf("error WSAWaitForMultipleEvents\n");
break;
}
// どのイベントがあったか調べる
if(WSAEnumNetworkEvents(fd,hEvent,&events) == SOCKET_ERROR)
{
printf("error WSAEnumNetworkEvents\n");
}
else
{
// 相手が接続を切ったら
if(events.lNetworkEvents & FD_CLOSE)
{
printf("close\n");
// 自分も切って終了
closesocket(fd);
break;
}
// 相手からのメッセージが届いたら
if(events.lNetworkEvents & FD_READ)
{
// 受け取る
int len = recv(sock, buf, 1024, 0);
printf("Received %d bytes\n",len);
}
}
}
// イベントオブジェクト破棄
WSACloseEvent( h );
return 0;
}
■ WSAWaitForMultipleEvents
DWORD WSAWaitForMultipleEvents(
DWORD num, // イベントオブジェクト数。1 〜 WSA_MAXIMUM_WAIT_EVENTS で指定
const WSAEVENT FAR *lphEvents, // イベントオブジェクトハンドルの配列のポインタ
BOOL fWaitAll, // TRUE : 全てのイベントがシグナル状態になるまで待つ。FALSE : どれか一つがシグナルのときに戻る。
DWORD time, // タイムアウト時間( msec )
BOOL fAlertable // TRUE : 関数から戻るときに I/O完了ルーチンが実行される。FALSE : 実行されない
);
Return
WSA_WAIT_FAILED : 失敗
// 成功した場合は次のどれかが返る
// また event オブジェクトにもその内容が設定される
WSA_WAIT_TIMEOUT : タイムアウトになった。
WAIT_IO_COMPLETION : 一つ以上のI/O完了ルーチンが実行のためにキューに詰まれた
WSA_WAIT_EVENT_0 〜
(WSA_WAIT_EVENT_0 + cEvents - 1)
fWaitAllがTRUE : 全てのイベントがシグナルになったことを表す。
FALSE : WSA_WAIT_EVENT_0を引いた戻り値はシグナル状態になった
イベントハンドルのインデックスを表す
■ WSAResetEvent
DESC
イベントのシグナル状態をリセットする。
WSAResetEvent( events[idx - WSA_WAIT_EVENT_0]);
■ WSARecv
SYNTAX
int WSARecv(
SOCKET s,
LPWSABUF lpBuffers, // Buffer への pointer
DWORD dwBufferCount, // buffer 数
LPDWORD lpNumberOfBytesRecvd, // pointer to the number, in bytes,
of data received by this call if the receive operation completes immediately.
If the lpOverlapped parameter is non-NULL,
this parameter is optional and can be set to NULL.
LPDWORD lpFlags, // pointer to flags used to modify
the behavior of the WSARecv function call
LPWSAOVERLAPPED lpOverlapped, // pointer to a WSAOVERLAPPED structure
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 完了が終了した時点での
callback
);
DESC
接続ソケット(connect)からデータを受けとる
重複 Socket のみに利用する
■ ByteOrder
DESC
Network Program でぶつかる問題 ( どんな Computer があるかわからない. )
Server は同じようにサービスをする必要がある
問題1 Endian
Intel Processor が支配しているため, LE が一般的
下位 Byte からアドレスに格納.
0xABCD というワード長のデータを 0xCDAB という順でメモリに保存します
Network では, BE が標準的.
WinSock がこの問題を解決( 抽象化する. )
( ie. ByteOrder を気にする必要がない. )
// とりあえず以下の 関数を使えば, ByteOrder を気にする必要ない
htons : host to network short
// ホストバイトオーダーをネットワークバイトオーダーに変換
// 32 ビットホストバイトオーダーを
// hostshort には 16 ビットホストバイトオーダーを指定します
htons()
htonl()
// 逆.
ntohs()
ntohl()
*
#include < stdio.h>
#include < winsock2.h>
X86 系の結果.
DD : CC : BB : AA
AA : BB : CC : DD
int main() {
unsigned int iValue = 0xAABBCCDD;
unsigned char *pc = (char *)&iValue;
// LE なので下位 word から順番に格納される.
// Byte Level でメモリを若い順番からアクセスするとわかる.
printf("%X : %X : %X : %X\n" , pc[0] , pc[1] , pc[2] , pc[3]);
// [ host ] -> [ network ] order へ変換.
// Hostが BE ならば, 何もしない.
iValue = htonl(iValue);
printf("%X : %X : %X : %X\n" , pc[0] , pc[1] , pc[2] , pc[3]);
return 0;
}
■ HttpClient
SAMPLE
HTTP クライアント
HTTP クライアント GUI
DESC
Browser の簡易版
Browser とは HTTPServer に HTTP Request をなげている Program のこと
TCPClient の一例として HTTP Client を作成
HTTPサーバーとのやり取りは非常に簡単
クライアントがファイルのリクエストを送信すると
HTTPサーバーがヘッダーとデータを送信してきて
HTTPクライアントがデータを受信し終わると
切断して
セッションを終了するという流れになる
メールを送信するSMTPというプロトコル( 通信方法 )との違いは
サーバーとあまり対話を繰り返さないこと
WARNING
HTTPサーバーに接続できたらすぐリクエストを送信してあとは受信するだけ
リクエストを送信する際に[ リクエストの終わりを示す改行のみの行 ]を送信しないと
HTTPサーバーはそのまま待機した状態になり
データを受信できなくなってしまう
SYNTAX
GET file PROTOCOL名/VERSION
// 一番簡単な HTTP GET メッセージ
GET /index.html HTTP/1.1/\r\n\r\n
GET path HTTP/1.0
NULL
POINT
HTTP Server が相手だと, Request を投げないと何もかえってこない
相手によっては Client から声をかけないとだめ
返ってくるデータは,
HTTPサーバーから処理の結果
データのサイズ
データの更新日などのヘッダー情報と、実際のデータが送信される
HTTP/1.1 200 OK
Server: Apache-Coyote/
ETag: W/"116-124713615
Last-Modified: Thu, 09
Content-Type: text/htm
Content-Length: 116
Date: Thu, 09 Jul 2009
Connection: close
// 接続先のホストとポートを指定する
// ウェブサーバなので ポートは 80
adr.sin_addr.S_un.S_addr = inet_addr("74.125.31.106");
adr.sin_port = htons( 80 );
// main page の HTML をリクエストする HTML メッセージ。
const char *msg = "GET / HTTP/1.0\r\n\r\n";
send( sock, msg, strlen(msg), 0 );
// Web Server からの返答をもらう
std::string s;
while (1) {
ret = ::recv(sockSrv, buf, sizeof(buf) - 1, 0 );
buf[ret] = '\0';
s += buf;
if ( ret == SOCKET_ERROR || ret == 0 ) {
break;
}
}
printf( "%s\n", s.c_str() );
■ ホスト名とアドレス
DESC
接続先の Computer 識別するには名前が必要
IP がそれ. ***.***.***.*** という形式の 32 ビット( 4 byte )
で表現される識別子
でも数値ではわかりずらいので名前をつける. google.com
-> google.com -> DNS -> IP
[ 住所 ] と [ 郵便番号 ]の関係
1. ホスト名を使ってコンピューターの情報を取得する
( Winsock がすべて解決してくれる. )
struct hostent FAR * gethostbyname (const char FAR * name);
POINT
hostent 構造体はホストの情報を格納する
struct hostent {
char FAR * h_name; // ホスト名
char FAR * FAR * h_aliases; // 別名
short h_addrtype; // Address の種類 AF_INET
short h_length; // adr size IP であれば 4 バイト
// IPv6 6 バイト
char FAR * FAR * h_addr_list;
};
C:\...>test www.age-soft.co.jp
公式名 = ns.age-soft.co.jp
別名 = www.age-soft.co.jp
IP = 210.143.102.133
int main(int argc , char *argv[]) {
LPHOSTENT host;
WSADATA wsaData;
int i;
if (argc == 1) return 0;
WSAStartup( 2 , &wsaData );
host = gethostbyname(argv[1]);
if (host == NULL) {
fprintf(stderr , "ホスト名の取得に失敗しました : %s" , argv[1]);
return 0;
}
printf("公式名 = %s\n" , host->h_name);
for(i = 0 ; host->h_aliases[i] ; i++) {
printf("別名 = %s\n" , host->h_aliases[i]);
}
for(i = 0 ; host->h_addr_list[i] ; i++) {
printf("IP = %d.%d.%d.%d\n" ,
(BYTE)*((host->h_addr_list[i])) ,
(BYTE)*((host->h_addr_list[i]) + 1) ,
(BYTE)*((host->h_addr_list[i]) + 2) ,
(BYTE)*((host->h_addr_list[i]) + 3)
);
}
WSACleanup();
return 0;
}
■ WinSock.固有の型
■ SOCKET
ソケットのインスタンスをあらわす整数の識別子
winsock.h 内で
■ UDP
POINT
UDP Client は UDP サーバとのみ接続ができる
■ UDPClient
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
// UDP 用のソケット作成
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
// ローカルサーバのアドレスを指定
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(7777);
adr.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1" );
// POINT
// TCP とは異なり, 接続処理が不要のため connect なしですぐに send() する
char buf[] = "test";
int len = sizeof(adr);
// 他のソケットが使用していないポート番号が自動で設定される。
// サーバはこのポートを見て返事を返すので特に知る必要はない
sendto( sock, buf, strlen(buf), 0, (struct sockaddr *)&adr, len );
{
int len = sizeof(adr);
char buf[256];
printf( "recvfrom にはいります\n" );
int n = recvfrom( sock, buf, sizeof(buf), 0, (struct sockaddr *)&adr, &len );
printf( "echo ret = %s\n", buf );
gets( buf );
}
}
■ UdpServer
// サーバーソケットを作成する
SOCKET createServerSocket( unsigned short port )
{
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.S_un.S_addr = INADDR_ANY; // サーバ側で割り当てられている IP を自動で設定
bind(sock, (struct sockaddr *)&adr, sizeof(adr));
// 接続をする必要がないため listen() は不要
return sock;
}
// UDP を使ったエコーサーバ
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
SOCKET sock = createServerSocket( 7777 );
// クライアントのメッセージを返すことを繰り返す
while(1) {
struct sockaddr_in adr;
// WARNING
// この処理は必須
int len = sizeof(adr);
// 移行のクライアントとの対話を普通にすると当然 recv などでブロックされる。
char buf[256];
// POINT
// UDP では接続を確立しないため accept は不要
// 1 つの通信をすべての処理に使いまわす
//
printf( "recvfrom にはいります\n" );
int n = recvfrom( sock, buf, sizeof(buf), 0, (struct sockaddr *)&adr, &len );
buf[ n ] = '\0';
printf( "client msg %s\n", buf );
sendto( sock, buf, strlen(buf), 0, (struct sockaddr *)&adr, len );
}
}
■ recvfrom
SYNTAX
int recvfrom( SOCKET sock,
void *msg,
unsigned int length,
int flags
struct sockaddr *adr,
unsigned int & adrlength
)
RET
N : 受信したメッセージのバイト数
-1 : エラー
DESC
UDP ソケットでメッセージを受信する
TCP の recv() と同じようにブロックされる。
最後に2つの引数でデータグラムの送り主の情報がわかる。
POINT
TCP のように接続処理をしないため 特定の相手以外からのデータも受信される。
// ip の値を比較して判断する。
if ( dst.sin_addr.s_addr != src.sin_addr.s_addr ) {
printf( "unknown server" );
}
■ broadcast
DESC
broadcast とは特定のネットワークに向けて無差別に送信する方法のこと。
multicast は条件にあった相手にだけ送信される。
IP では broadcast, multicast が利用できるのは UDP だけ。
POINT
broadcast, multicast の使い分けはメッセージを要求するホストの割合や,
通信相手の情報の有無で考える。
インターネット規模でのブロードキャストは規模が大きすぎるため
ブロードキャスト用のパケットを転送しないルータが多い。
broadcast は LAN 内で使用することが一般的。
逆にホストを見つけるには broadcast が適しているため
プリンタはどこか? といった質問は向いている。
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
// UDP 用のソケット作成
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
// 送信先をネットワーク内すべてに指定する
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(7777);
adr.sin_addr.S_un.S_addr = inet_addr( "169.254.255.255" );
// ブロードキャスト用のソケットに変更
int param = 1;
if ( setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char *)¶m, sizeof(param) ) < 0 ){
printf( "fail setsockopt\n" );
return;
}
// POINT
// 3 秒ごとに指定した特定のネットワークにブロードキャストをする
//
while(1) {
printf( "broadcast message 送信\n" );
char buf[] = "test broadcast";
int len = sizeof(adr);
// 他のソケットが使用していないポート番号が自動で設定される。
// サーバはこのポートを見て返事を返すので特に知る必要はない
sendto( sock, buf, strlen(buf), 0, (struct sockaddr *)&adr, len );
Sleep(1000 * 3);
}
}
ブロードキャストを待ち続けるレシーバ側の処理
// サーバーソケットを作成する
SOCKET createSocket( unsigned short port )
{
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.S_un.S_addr = INADDR_ANY; // サーバ側で割り当てられている IP を自動で設定
bind(sock, (struct sockaddr *)&adr, sizeof(adr));
return sock;
}
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
SOCKET sock = createSocket( 7777 );
int cnt = 0;
while(1) {
struct sockaddr_in adr;
int len = sizeof(adr);
// 移行のクライアントとの対話を普通にすると当然 recv などでブロックされる。
char buf[256];
printf( "recvfrom にはいります\n" );
int n = recvfrom( sock, buf, sizeof(buf), 0, (struct sockaddr *)&adr, &len );
buf[ n ] = '\0';
printf( "broadcast msg %s\n", buf );
}
}
■ multicast
POINT
マルチキャストは特定のグループのメンバーにだけ送信する。
この集合をマルチキャストグループといい、
受信をしたい人はこのグループに登録する必要がある。
IP にはマルチキャスト用に用意されたアドレスの範囲があるため
その中から任意のものを宛先に指定する。
224.0.0.0 - 239.255.255.255
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
// UDP 用のソケット作成
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
// マルチキャストグループを宛先に指定する
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(7777);
adr.sin_addr.S_un.S_addr = inet_addr( "224.1.2.3" );
int param = 1;
if ( setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)¶m, sizeof(param) ) < 0 ){
printf( "setsockopt fail\n" );
}
else {
printf( "setsockopt success\n" );
}
// POINT
// 3 秒ごとに指定した特定のネットワークにマルチキャストする
while(1) {
printf( "multicast message 送信\n" );
char buf[] = "test multicast";
int len = sizeof(adr);
// 他のソケットが使用していないポート番号が自動で設定される。
// サーバはこのポートを見て返事を返すので特に知る必要はない
sendto( sock, buf, strlen(buf), 0, (struct sockaddr *)&adr, len );
Sleep(1000 * 3);
}
}
マルチキャストレシーバの処理
マルチキャストグループに登録して受信する。
// WARNING
// setsockopt( IP_ADD_MEMBERSHIP ) で失敗するため
// winsock.h ではなく winsock2.h をインクルードすること
#include < winsock2.h>
#include < ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
// サーバーソケットを作成する
SOCKET createServerSocket( unsigned short port )
{
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in adr;
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.S_un.S_addr = INADDR_ANY; // サーバ側で割り当てられている IP を自動で設定
bind(sock, (struct sockaddr *)&adr, sizeof(adr));
return sock;
}
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0), &wsaData);
SOCKET sock = createServerSocket( 7777 );
// マルチキャストグループへ登録( join )する
struct ip_mreq mr;
mr.imr_multiaddr.s_addr = inet_addr( "224.1.2.3" );
mr.imr_interface.s_addr = INADDR_ANY;
if ( setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mr, sizeof(mr) ) < 0 ){
printf( "setsockopt fail\n" );
}
else {
printf( "setsockopt success\n" );
}
while(1) {
struct sockaddr_in adr;
int len = sizeof(adr);
// 移行のクライアントとの対話を普通にすると当然 recv などでブロックされる。
char buf[256];
printf( "recvfrom にはいります\n" );
int n = recvfrom( sock, buf, sizeof(buf), 0, (struct sockaddr *)&adr, &len );
buf[ n ] = '\0';
printf( "multicast msg %s\n", buf );
}
}
■ setsockopt
SYNTAX
int setsockopt(
SOCKET sock,
int level, // 指定したいオプションのプロトコル
int name, // 設定したいオプション
const char *val, // オプションの値へのポインタ
unsigned length, // オプションのバイトサイズ
)
DESC
ソケットのオプションの値を変更する。
RET
0 : 成功
-1 : 失敗
POINT
設定できる項目はプロトコルスタックのレイヤーごとに異なる
SOL_SOCKET
IPPROTO_TCP // TCP プロトコルへの制御オプション
IPPROTO_IP
// recv() のタイムアウト時間を 3000 msec にする
unsigned int t = 3 * 1000;
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&t, sizeof(t) );
// タイムアウトを1秒に設定
unsigned int t = 1 * 1000;
getsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&t, sizeof(t) );
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&t, sizeof(t) );
■ getsockopt
SYNTAX
int getsockopt(
SOCKET sock,
int level,
int name, // 設定したいオプション
char *val, // オプションの値をもらうポインタ
unsigned *length, // オプションの値のバイトサイズ
)
DESC
ソケットのオプションの値を取得する。
RET
0 : 成功
-1 : 失敗
recv() の受信バッファサイズを2倍にする
int sz;
int len = sizeof( sz );
getsockopt( sock, SOL_SOCKET, SO_RCVBUF, &sz, &len );
sz *= 2;
setsockopt( sock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz) );