使い方





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.の初期化


[.] ws2_32.lib [.] #include <winsock2.h> // version ごとの対応はこれ // version DLL ヘッダ ライブラリ Winsock1.1 (16bit) WINSOCK.DLL winsock.h winsock.lib Winsock1.1 (32bit) WSOCK32.DLL winsock.h wsock32.lib Winsock2.0 (32bit) WS2_32.DLL winsock2.h ws2_32.lib
はじめての 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 http://ooo.iiyudana.net/bin/net/simple_server.exe( シンプルサーバー ポート7777 を利用 ) http://ooo.iiyudana.net/bin/net/simple_server_gui.exe( シンプルサーバー ポート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 http://ooo.iiyudana.net/bin/net/simple_client.exe( シンプル クライアント port 7777 へ接続 ) http://ooo.iiyudana.net/bin/net/simple_client_gui.exe( シンプル クライアント 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 ), // u16 の値を host の ByteOrder に変換. ntohs( client.sin_port ) );



エコーサーバー


SAMPLE http://ooo.iiyudana.net/bin/net/simple_echo_client_gui.exe( シンプル エコークライアント port 7777 へ接続 GUI ) http://ooo.iiyudana.net/bin/net/simple_echo_server_gui.exe( シンプル エコーサーバ port 7777 でリスン GUI )


バイナリファイルの取得


SAMPLE http://ooo.iiyudana.net/bin/net/filedl.exe( ファイルをダウンロードして %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 http://ooo.iiyudana.net/bin/net/chat_client_gui.exe( チャットクライアント ) http://ooo.iiyudana.net/bin/net/chat_server_gui.exe( チャットサーバー 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://ooo.iiyudana.net/bin/net/httpclient.exe( HTTP クライアント ) http://ooo.iiyudana.net/bin/net/httpclient_gui.exe( 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 内で
typedef u_int SOCKET;



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) );