\n
[ トップページ ]
[ ___ _CommandPrompt ]
[ ___ _JScript ]
[ ___ _MySQL ]
[ ___ _Cygwin ]
[ ___ _Java ]
[ ___ _Emacs ]
[ ___ _Make ]
[ ___ _Perl ]
[ ___ _Python ]
[ ___ _OpenGL ]
[ ___ _C# ]
[ ___ _StyleSheet ]
[ ___ _C++ ]
[ ___ _Winsock ]
[ ___ _Thread ]
[ ___ _VisualStudio ]
[ ___ _C ]
[ ___ _Win32API ]
[ ___ _Lua ]
[ ___ _PhotoShop ]
ヘッダ検索
GREP 暗黙 オブジェクト
■ 使い方
■ 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) );
■ リファレンス(Reference)
■ 共通
■ socket
SYNTAX
SOCKET socket (
int af, // ソケットタイプ( アドレスの種類を表すアドレスファミリ )
int type , // ソケットの種類( SOCK_STREAM )
int protocol // (TCP | UDP)
);
RET
0: OK
SOCKET_ERROR: FAIL
DESC
指定したタイプの Socket を作成して SOCKET HANDLE( 整数値 ) を返す。
// TCP ソケットを作成
SOCKET sock = socket( AF_INET, SOCK_STREAM, 0 );
// UDP ソケットを作成
SOCKET sock = socket( AF_INET, SOCK_DGRAM, 0 );
■ bind
SYNTAX
int bind (
SOCKET s ,
const struct sockaddr FAR* name ,
int namelen
);
RET
0: 成功
SOCKET_ERROR: 失敗
DESC
ソケットにローカルIPアドレスとローカルポートを設定する。
Server 側は指定した port で listen するため bind して明示する
Client 側は自動設定にまかせる。
ERROR
EADDRINUSE : ポートが他のソケットで利用されている。
{
SOCKADDR_IN adr = { AF_INET };
adr.sin_port = htons(80);
adr.sin_addr = INADDR_ANY; // ローカルアドレスにワイルドカードを指定
// サーバー側のすべてのIPアドレスでデータを受け取ることができる
SOCKET sock = socket(AF_INET , SOCK_STREAM , 0);
bind( sock , (SOCKADDR *)&adr , sizeof(SOCKADDR) );
}
WARNING
// 同じローカルポートに他のソケットがバインドしている場合はエラーになる
SOCKET sock1 = socket(AF_INET , SOCK_STREAM , 0);
bind( sock1 , (SOCKADDR *)&adr , sizeof(SOCKADDR) );
SOCKET sock2 = socket(AF_INET , SOCK_STREAM , 0);
bind( sock2 , (SOCKADDR *)&adr , sizeof(SOCKADDR) );
■ SOCKADDR_IN
インターネットアドレス形式を表す構造体
union で定義されているが, ほとんどは S_addr に IP Address を表す 32bit の値をいれる
typedef struct sockaddr_in {
short sin_family;
u_short sin_port; // ポート
struct in_addr sin_addr; //
char sin_zero[8]; //
} SOCKADDR_IN;
POINT
sin_port にはサーバがサービスを提供するポート番号を指定する
sin_addr には INADDR_ANY を指定することで固定アドレスでない場合に自動で割り振られる
■ send
SYNTAX
int send(
SOCKET sock,
char *msg, // メッセージのポインタ
int length, // メッセージの長さ ( byte 数 )
int flags // 通常 0 ( | MSG_DONTROUTE,MSG_OOB )
);
RET
N : 実際に送信されたバイト数
SOCKET_ERROR : エラー
DESC
指定したソケット経由でデータを送信する(主に TCP で使用)
flags で 0 を指定するとデフォルトの振る舞いをする。
正確にはソケットの送信バッファへデータがコピーされてすぐに制御を返す。
STATE
ソケットは接続状態( ESTABLISHED )である必要がある。
// HTTP Client が HTTP Server に HTTP Message ( GET / )
{
SOCKADDR_IN adr = { AF_INET };
name.sin_port = htons(80);
SOCKET sock = socket(AF_INET , SOCK_STREAM , 0);
bind( sock , (SOCKADDR *)&adr , sizeof(SOCKADDR) );
const char *msg = "test" ;
// WARNING
// メッセージは長さを送り, 末尾の '\0' は不要
send( sock, msg, strlen(msg), 0 ) ;
}
■ recv
SYNTAX
int recv(
SOCKET sock,
char* buf,
int length,
int flags // 通常 0 ( MSG_PEEK MSG_OOB )
);
RET
N : 読み込まれたバイト数
SOCKET_ERROR : 失敗
0 : 通信相手がコネクションを閉じた
DESC
指定したソケットで相手からデータを受信をする。( 主にTCPで使用 )
正確にはソケットの受信バッファにあるデータを buf で指定した領域へコピーする。
受信バッファにデータがなければブロックされるため
メインスレッドから実行する場合は何らかの対策が必要。
STATE
ESTABLISHED
接続状態でない時に利用すると SOCKET_ERROR が返る。
WARNING
HTTP Server が相手だとリクエストを投げない( send() をしない )と何もかえってこない
このとき recv() で Block される。
ブロックを回避するには, recv() 用のスレッドを作成したり、非同期ソケットを利用する。
// シンプルに受信する
char buf[1024];
int len = recv(sock, buf, sizeof(buf), 0);
buf[len] = '\0';
printf( "recv size %d\n%s" , len, buf );
if ( len == SOCKET_ERROR ) return 1;
WARNING
十分なバッファを WinSock に渡しても
ソケット上のキューにあるデータを全て返してくれない
全てとるには呼出しを繰り返す
char buf[1024 * 4];
memset(buf, 0, sizeof(buf));
int sum = 0;
while ( 1 ) {
int len = recv(sock, buf + sum, sizeof(buf) - sum, 0);
sum += len;
// 0 がきたら相手が CLOSE したことを意味する。
if ( len == 0 ) {
printf( "server closed\n" );
break;
}
// 相手が close した状態で recv() をしたり, サーバが落ちていると -1 が返る。
if ( len == SOCKET_ERROR ) {
break;
}
}
FD_CLOSE がうけれるなら, len == 0 の処理は不要
逆に FD_CLOSE Event がくるときに , recv をすると, len = 0 がかえってくる
0 == recv() は FD_CLOSE をチェックしているに等しい
// Thread にわたす 受信処理
void* waitReceiveThread(void* pParam)
{
char buf[1024];
int len;
while(1)
{
// データ受信待ち
len = recv(sock,buf,1024,0);
printf("Received %d bytes\n" ,len);
// 相手が閉じたら
if(len == 0)
{
printf("check\n" );
// こっちも切る
close(sock);
break;
}
}
return NULL;
}
connect が失敗した状態のソケットで recv( 受信 ) をすると WSAENOTCONN (10057) が返る。
接続されていないソケットを使用したということ。
同じく send() でも発生する。
// no check
connect( sock, (struct sockaddr *)&adr, sizeof(adr) );
int n = recv(m_sock, buf, sz, 0);
if (n < 0) {
printf("ERR: recv : %d\n" , WSAGetLastError() );
}
■ closesocket
SYNTAX
int closesocket (
SOCKET sock
);
RET
OK : 0
FAIL: SOCKET_ERROR
DESC
コネクションを切る。
ソケットは通信できない状態へ移行する
使うと Client 側には HOST との接続が切られました という message が届く
■ server.固有
■ listen
SYNTAX
int listen (
SOCKET s , // 受信に使う Socket
int queue // 接続に使うキューの数 ( 最大接続数 )
);
RET
0: 成功
SOCKET_ERROR: 失敗
DESC
サーバのソケットが接続待ち状態( LISTENING )に移行して、
クライアントからの接続要求がキューへ格納できる状態へする。
queue で指定した数だけ最大でリクエストを保存できて、
アクセプト( accept )をするとキューから取り出す。
queue 以上のクライアントからの接続要求は失敗する。
POINT
サーバが listen() しないとクライアント側のコネクト要求( connect() )は失敗する。
{
SOCKADDR_IN adr = { AF_INET };
adr.sin_port = htons(2048);
adr.sin_addr = INADDR_ANY;
SOCKET sock = socket(AF_INET , SOCK_STREAM , 0);
bind( sock , (SOCKADDR *)&adr , sizeof(SOCKADDR) );
// キューの数を5にしてリスン状態にする。
listen( sock, 5 );
}
■ accept
SYNTAX
SOCKET accept (
SOCKET s,
struct sockaddr FAR* addr, // クライアント側のアドレス情報
int FAR* addrlen
);
RET
ClientSocket : 成功
INVALID_SOCKET : 失敗
DESC
クライアントからの接続を受け付ける。
正確には
リスン中のソケットの接続すみリストからひとつを取り出してディスクリプタを割り当てて返す。
リストが空ならばブロックされため、
GUI の MainThread では利用しないこと。
{
SOCKET sockCli = accept( sock, );
// お客さんへメッセージをなげる。
const char buf[] = "いらっしゃい" ;
send( sockCli, buf, length(buf), 0 );
}
■ client.固有
■ connect
SYNTAX
int connect(
SOCKET,
struct sockaddr *adr,
int size
);
RET
SOCKET_ERROR : 接続に失敗
0 : 成功
DESC
指定したサーバに接続する。
不正なアドレスを指定すると失敗する。
ERROR
ETIMEOUT : 指定した時間にハンドシェイクが完了しなかった。
WARNING
Server でリスン( listen() )してない場合はすべての接続要求は失敗する。
逆に listen() していれば accept() をしなくても接続要求は成功する。
ただしリストが埋まっていれば失敗する。
struct sockaddr_in adr = { AF_INET };
adr.sin_port = htons( 80 );
adr.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1" );
int ret = connect( sock, (struct sockaddr *)&adr, sizeof(adr) );
if ( ret == SOCKET_ERROR ) {
printf( "fail connect\n" );
}
host が存在して ping などで接続できるが, 一定時間がたっても応答がない場合は
20秒ほど経過してから 10060 ( WSAETIMEDOUT ) が返る。
ケースとしてクライアントが指定したポートでリスンしてないなどがある
adr.sin_port = htons( 8888 );
int ret = connect( sock, (struct sockaddr *)&adr, sizeof(adr) );
if ( ret != 0 ) {
printf("FAIL connect : WSAGetLastError %d\n" , WSAGetLastError());
}
■ 変換
■ htons
SYNTAX
u_short htons(
u_short
);
DESC
short int をネットワークバイトオーダーに変換
SYNTAX
u_short htons (
u_short hostshort // 変換する 16 ビットホストバイトオーダ
);
);
RET
指定した値のネットワークバイトオーダー
DESC
16 ビットホストバイトオーダーをネットワークバイトオーダーに変換する
{
struct sockaddr_in srcAddr;
// short 型の Data のときに使う
srcAddr.sin_port = htons( port );
}
■ htonl
SYNTAX
u_short htonl (
u_long hostshort // 変換する 32 bit HostByteOrder
);
RET
指定した値の NetworkByteOrder
DESC
htons の long (32bit) Version
{
struct sockaddr_in srcAddr;
srcAddr.sin_addr.s_addr = htonl( INADDR_ANY );
}
■ ntohs
SYNTAX
u_short ntohs(
u_short netshort
);
DESC
short の整数を ホストバイトオーダー に変換する
Client の port 情報を変換する際に便利
■ getHostByName
SYNTAX
struct hostent * getHostByName( char *name );
DESC
ホスト名( 又は dot表記のIPアドレス )から 32bit IPAddress をえる
逆に IP からホスト名とりたい場合は getHostByAddr() を使う
DNS にサーバー名とアドレスを登録しておく必要がある。
RET
!NULL : 変換されたホスト情報へのポインタ
NULL : 不正なホスト名( dot表記IPアドレス )を指定した。
POINT
IPアドレスでもあて先を指定できるが、ISPの変更があった場合に影響を受けないように
ホスト名を指定しておくと良い。
LPHOSTENT host = getHostByName( "google.co.jp" );
POINT
// ホスト情報を格納した構造体 hostent を返す。
typedef struct hostent {
char *h_name; // ホスト名
char **h_aliases; // ホストの別名
char **h_addr_list; // 32bit IPアドレスのポインタ。プログラムはこの値を指定すること。
};
■ getHostByAddr
SYNTAX
struct hostent * getHostByAddr( char *addr, int len, int type );
DESC
IP Address から Host情報をえる
■ inet_addr
SYNTAX
int inet_addr(
const char FAR * // 文字列表現アドレスへのポインタ ( "xxx.xxx.x.x" )
);
RET
アドレスの数値表現: 成功
INADDR_NONE : 失敗
DESC
ドット表記のIPv4アドレス文字列を 4byte の整数(ネットワークバイトオーダー)へ変換
BAD
// ドメイン名はダメ
inet_addr( "library.city.chuo.tokyo.jp" )
OK
// dot 表記を渡すこと
inet_addr( "210.233.111.187" );
■ inet_ntoa
SYNTAX
char * inet_ntoa (
struct in_addr in // アドレスが格納されている in_addr 構造体
);
RET
文字列へのポインタ : 成功
NULL : 失敗
DESC
バイナリ表現の IPアドレスを 10進数のドット表記文字列( "127.0.0.1" など )に変換。
■ WinSock.固有
■ WSAStartup
SYNTAX
int WSAStartup (
WORD wVersionRequested, // 使用したい version
LPWSADATA lpWSAData); // WinSock の実装情報を取得する.
RET
0: OK
!0: FAIL
DESC
DLL を初期化する
■ WSACleanup
SYNTAX
int WSACleanup ();
RET
0: OK
!0: FAIL
DESC
WinSock の Resource を解放する
■ WSAGetLastError
SYNTAX
int WSAGetLastError()
DESC
最後( 直近 )に発生したエラーコードを返す。
■ 非同期.API
■ WSAWaitForMultipleEvents
SYNTAX
DWORD WSAWaitForMultipleEvents(
1, // Event数
&hEvent, // EventObject
FALSE,
5*1000, // 待ち時間 msec
FALSE
);
DESC
Socket に関連つけられたネットワークイベントを指定時間まつ
(connect | recv | cloes )をまつ
// Accept を指定する
//
WSAWaitForMultipleEvents()
// Client が接続してきた !
if ( FD_ACCEPT ) {
}
■ WSAEnumNetworkEvents
SYNTAX
nRet = WSAEnumNetworkEvents(
Socket,
hEvent,
&events
);
DESC
発生したNetworkイベントの種類を調べる
■ WSAEventSelect
SYNTAX
nRet = WSAEventSelect(
Socket, // 関連づける Socket
hEvent, // Event
FD_READ|FD_CONNECT|FD_CLOSE // 通知をうける Event の種類を設定
);
RET
0 : 成功
!0 : ERRORCODE
DESC
ソケットを[ 非ブロッキング ]にし[ ネットワークイベント ]を関連付ける
イベントがおきると イベントオブジェクトが Signal 状態になる
yfWaitAll [in]
A value that specifies the wait type
wait の type を指定する
If TRUE
the function returns when the state of all objects in the lphEvents array is signaled
If FALSE
the function returns when any of the event objects is signaled
( すべての Event に対してかえす )
In the latter case
the return value minus WSA_WAIT_EVENT_0 indicates the index of the event object
whose state caused the function to return
If more than one event object became signaled during the call
this is the array index to the signaled event object
with the smallest index value of all the signaled event objects
なんか false でよさげ
fAlertable [in]
〈物・事が〉変えられる, 改められる.
A value that specifies whether the thread is placed in an alertable wait state
so the system can execute I/O completion routines
If TRUE
the thread is placed in an altertable wait state
and
WSAWaitForMultipleEvents can return when the system executes
an I/O completion routine
In this case
WSA_WAIT_IO_COMPLETION is returned and the
event that was being waited on is not signaled yet
The application must call the WSAWaitForMultipleEvents function again
If FALSE
the thread is not placed in an altertable wait state and I/O completion routines
are not executed
■ データ型
■ sockaddr
DESC
汎用のアドレス構造体
特定のアドレスファミリ固有ではない。
struct sockaddr {
unsigned short sa_family;
char sa_data[14]
};
■ sockaddr_in
DESC
インターネットアドレスファミリの構造体
POINT
sockaddr 構造体をインターネットファミリ向けに定義しなおしただけのため、
sockaddr を受け取る関数にキャストして渡すことができる。
設定する値はすべてネットワークバイトオーダーであること。
struct sockaddr {
unsigned short sin_family;
unsigned short sin_port; // ポート
struct in_addr sin_addr; // ip
char sin_zero[8]; // 未使用領域
};
■ ReferenceCommand
Network Program に便利なコマンド
■ nslookup
SYNTAX
nslookup domanin
DESC
DomainName から .表記 IP を返す
// この値を inet_addr() に渡して接続先を指定する
nslookup google.co.jp
■ tracert
SYNTAX
tracert domain
■ netstat
POINT
接続状況とソケットの状態を確認できる。
ローカルでテストをする場合はサーバで設定したポートは
ローカルアドレス側にくる。
クライアント側は外部アドレスに表示される。
プロトコル ローカルアドレス 外部アドレス 状態
TCP xxx.xxx.xxx.xx:yy xxx.xxx.xxx.xx.yyy ESTABLISHED
■ URLEncode
DESC
URI( Address )に含むことができない文字コードを使用できる文字に変換する
RFC では Percent-Encoding という名称
文字列データをURLに埋め込むようなときは
Webサーバとの間で正しく情報をやりとりするために
別の表記形式に変換する(エンコードする)必要がある
URI の規則を定める RFC 3986 では URI に
ASCII の非予約文字以外の文字データを用いる場合には
「%xx」( xxは16進数 )という形でコードを表記することが定められている
こんな問題がある
問題は.
日本語( 2 byte 文字 ) ServletEngine( Tomcat )で正しく変換されないこと
Client からの 日本語をうけつける際に, 文字化けをおこすこと
要は,
Gateway が 解釈できるように, 1 byte 文字に変化してしまう
データをURLに埋め込んでも問題が生じないように
英数字以外の文字や/などの記号を安全な形に変換することが目的
( さらに SPC, 制御文字も可視化する )
その際にどの文字コードを用いるかは実装によって異なる
Rule からはみ出る文字は, URLEncode( 16 進 ASCII 表記 ) される
SPACE < ->
Opera( Yahoo::jisyo ) で試してみた.
[+] ---> [%2B] # ASCII ですね.
[ ] ---> [+]
[&] ---> [%26]
[?] ---> [%3F] # ASCII ですね.
変換方法
1. Byte 単位でされる.
1. 第1 Byte -> 16 進.
1. 第2 Byte -> ASCII の文字範囲に収まる場合は, そのままの文字.
1. 各項目は [&] を接続文字になる
http://house.o-uccino.jp/detail_0006131464_h/
ただし、URL エンコードの変換方法は JavaScript と CGI では一部異なる
スペースは JavaScript の場合には %20 に変換されるが、
CGIでは + に変換される
URLEncoding が必要な理由
メール( SMTP )や HTTP などのパケット( Dataの塊 )は
ヘッダ部に宛先, その他メッセージの制御に関わる情報が追記されてます
このヘッダは途中のゲートウエーを幾つか中継され、端から端のノードに伝達されます
これらのノードに理解できるコードと文字で表現されなければならない
ヘッダ部に日本語のような 2 バイト文字が入ると、
ノードはこれを 1 バイトずつ解釈して誤解してしまう
これを避けるため URL エンコードができた
名前と値にある「安全でない」文字は"%xx" に変換する
"xx" はその文字のASCII値を16進表示したもの
しかし送られる情報をすべて「見える」文字列に変換するのは都合が良いことが多く
ボディ部分にも使われる
ボディ部分の変換にはもうひとつ
MIME(Multi-Purpose Internet Mail Extensions)エンコーディングがある
これは2バイトのバイナリ・データを3バイトの
7ビットASCII(American Standard Code for Information Interchange)
文字に変換するもので
マルチメディア情報の転送に使われる
RFC 2396 2.4
Browser で実行される URLEncode は htm file の文字コードでされるみたいです
Encode する文字
変換するのは
[ = & % + ]
プリントできない文字
[&] -> [%26]
[%] -> [%25]
[+] -> [%2B]
[=] -> [%3D]
[< ] -> [%3C]
[>] -> [%3E]
特別なルールとして
ASCII スペース[ ] -> [+]
Encode しない文字
IE をつかって google で検索をすると URLEncode の結果がみれます
".NET TIPS"
を検索したときには
検索結果の画面でのURL(IEの[アドレス]部分)が次のようになっているはずだ
このURLでは
検索文字列 ".NET TIPS" が
「%22.NET+TIPS%22」にエンコードされている(「"」が「%22」に
半角スペースが「+」に変換される
POINT
IE が自動的にエンコードをしてるが
プログラムで同様のリクエストを送信するときは
文字列データを自前でエンコードすること
URL に Encode が必要な文字を含めるには URLEncode 変換後の文字をいれる
"B&B" を URL に含めたい
"&" は変換する文字 -> "%26"
"B%26B"
変換するには, 単に 1byte ずつ %[16進数]といいかえるだけ
byte をなめるので 文字コードによって表記はかわる
POINt
% とついていないのは そのまま ということ
// ASCII Code は Encode しなくてもいいです
Lucky池田 < -> Lucky%92r%93c
// SJIS [あ] を UrlEncode
あああ < -> %82%A0%82%A0%82%A0
// [ニュース速報] を UrlEncode
EUC-JP
%A5%CB%A5%E5%A1%BC%A5%B9%C2%AE%CA%F3
SJIS
%83j%83%85%81%5B%83X%91%AC%95%F1
JIS
%1B%24B%25K%25e%21%3C%259B.Js%1B%28B
UTF-8
%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%B9%E9%80%9F%E5%A0%B1
逆に 何の Encode の URLEncode かを指定しないと元に戻せない
# [ あああ ] を 辞書ページで検索してみた. ( これこそまさに DB の機能が使えそう. )
# p = =%E3%81%82%E3%81%82%E3%81%82
# あ = %E3%81%82 となる.
# あ = E38182 ( これ何の文字コード ? )
elisp/urlencode/src/urlencode.el
* TextEditor で 文字をかいて , 特定の文字 Code で保存して
dump して % つけたもの
TIP
emacs では urlencode-region なんてものを 利用すると便利
すべての WebAddress を UTF-8 で Encode する
たいていの WebApplication 用の言語には URLEncode のメソッドがある
// C# VB.NET HttpUtilityクラス(System.Web名前空間
HttpUtility.UrlEncode( "あいうえお" )
// Java ( JSP )
この文字列が POST のボディ部分
あるいは GET のクエリとしてはめ込まれるのである
Tomcat は URL をデコードして Java の使用文字コードの Unicode に変換します
[ URL からのデータ ]
| Decode 前の文字Code は ISO8859_1 とみなして StringObject に変換
( Tomcat が URL を Decode して Java の内部 Code の Unicode に変換 )
|
|
[ StringObject ]
|
| < - ISO8859_1 の byte 列にもどす
| name.getBytes( "8859_1" );
URLDecode 直後の状態になる
|
[ Byte 列 ]
|
| Byte 列 を SJIS として, 再度 StringObject をつくる
| String( byteArray, "Shift_JIS" )
|
まとめ
Tomcat で日本語を使った JSP を利用するためには, 次の方法をする
String wordSjis = new String( word.getBytes("8859_1" ), "Shift_JIS" );
1. getBytes(“8859_1”) で一旦 Unicode の文字列を
ISO8859_1(Latin-1) に変換したうえでバイト列へ
URLデコードしたときのバイト列が得られることになる
そのバイト列をString(bytes,”Shift_JIS”)のコンストラクタで
Shift_JIS のバイト列だと指定して
String型のオブジェクト(Unicode)をつくる
"Shift_JIS" の部分は
"EUC-JP" , "JISAutoDetect" もともと期待していたエンコーディングを指定する
"JISAutoDetect" は短い文字列の場合に正しく判定しないことがあるので使用しないほうが好ましい
まずしたうえでバイト列にする
そうするとURL デコードした直後のバイト列がとれます
そのうえでそのバイト列をString(bytes,”Shift_JIS”)のコンストラクタでShift_JISの
バイト列だと指定して String 型のオブジェクト(Unicode)を生成する
名前と値を=と&でつないでひとつの文字列にする
EX : name1=value1&name2=value2&name3=value3
htm の form で送っているやつか.
POINT
// 実は [?] 以降は HTTP のヘッダを渡していた.
http://server/foo.jsp?name=val&name=val&name=val&
■ MIME
DESC
MultiPurpose Internet Mail Expression ( 多目的 internet )
規格のひとつ. ( Internet で Data のやりとりをする時の規格 )
internet の 文字 Code は 7bit
だから binary を扱うときは uuencode で変更して uudecode で戻していた
でも internet が普及したので Programmer でない人がこれをするのは不便
そこで MIME という規格をつくり, Binary を扱えるようにした
ボディ部分の変換にはもうひとつ
MIME(Multi-Purpose Internet Mail Extensions)
エンコーディングがある( これも Encode )
1. MultiPurpose Internet Mail Expression ( 多目的 internet )
これは2バイトのバイナリ・データを3バイトの7ビットASCII(American Standard Code for Information Interchange)文字に変換するもので
マルチメディア情報の転送に使われる
( binary から ASCII に置換するのが目的 )
■ 基本的なこと
■ Socket
DESC
Socket とは ユーザにとってのデータの出入り口
ユーザはソケットへデータを読み書きするだけで通信できる。
( とりあえず FILE Pointer みたいなものだと思えば OK )
Socket の裏側の通信 Protocol を意識しなくても通信Programができます
Socket は 2 つ以上の Soket が関係をもって初めて意味があります
( 1 : N であることもある )
Socketの種類
DESC
2 つあります
Block とは
パケットがくるまでスレッドがブロックされるモードのこと
パケット到着まで他の作業はできません
受信では
アプリケーションが
WSARecv 関数か WSARecvFrom ()に受信用のメモリを提供する
受信前に一つ以上のメモリを用意しておくと
データが到着したらそのメモリにすぐ配置される
それらの関数を使えば recv 関数か recvfrom 関数の呼びだし時にコピーしなくて済む
受信用メモリを提供したときに
すでにデータがあれば
すぐにそのメモリにコピーされる
アプリケーションが受信用メモリを用意していないときにデータが到着すると
ネットワークがお決まりの形式で同期的に処理します
内部のメモリに保存します
アプリケーションが
setsockopt 関数で受信用の一時メモリの大きさをゼロに設定した場合は例外です
信頼性が保証されている通信規格では
アプリケーションがメモリを用意するとその分のデータを受信できる
信頼性が保証されていない通信規格ではデータを失う
■ Packet
DESC
バイト列のことをネットワーク上ではパケットという。
送信されるデータだけではなく、宛先などの制御情報が含まれる。
この情報をみてルータなどは制御をする。
小包での宛先と中身みたいなもの。
■ Protocol
DESC
通信プログラムが情報を交換する際の手順や, パケットの内容についての決まりごと
ネットワークで通信しあうための各ルールを階層構造のモジュール単位でわけている。
これを プロトコルスイート, プロトコルファミリという ( TCP/IP など )
[ ソケット ]
[ HTTP ]
[ TCP ][ UDP ]
[ IP ]
■ ApplicationProtocol
DESC
TCP/IP プロトコルがしてくれるのは
データを送信したり、受信をすることだけ。
実際のソケットを使ったプログラムでは
やり取りする情報をどんな形式で、いつどちらから送るかなどの取り決めが必要になる。
これをアプリケーションプロトコルという。
■ Order
POINT
ソケット経由で多バイトのデータを送信する場合は
アドレスの昇順で送る。
受信側も同じく昇順で受信する必要がある。
昇順とは data の小さい順番に並べること。階段でいうと上にあがるのと同じ
送信側と受信側のプロセッサ固有のバイトオーダーが逆になると問題がおきる
POINT
あるマシンから別マシンへ多バイトのバイナリ値を送る場合は hton*, ntoh * を使うこと。
もし ネットワークのバイトオーダーとプロセッサ固有のオーダーが同じ場合は
変換処理はされない。
■ バッファリング
POINT
recv(), send() は一対一対応をしていない。
TCPコネクション上を流れるデータは全体でひとつのデータ列とみなせるが
3箇所のバッファに分かれて存在する。
send() -> [ SendQ ] ---> [ RecvQ ] --(recv())--> [ Deliverd ]
send() は SendQ にすべてのデータが入るまでブロックされる。
複数の send() が1回の recv() で受け取ることがあるため
受信するときは メッセージの解析をすることが必須。
通常は 区切り文字でする。
■ ソケットの実装
POINT
TCP ソケットの理解するには ソケット構造体やプロトコルの仕組みを理解することが大切
ソケット構造体は次の情報をもつ
[ローカルIPアドレス]
[ローカルポート]
[リモートIPアドレス] // ローカルソケットの接続先を指定する
[リモートポート]
[受信バッファ]
[送信バッファ]
POINT
ローカルポートは bind() で明示できる。また初めて使用する際に自動でアサインされる。
サーバは bind() をコールして明示して、クライアントはお任せするのが普通。
■ ソケットの状態
[ closed ] ---> [ ]
POINT
send(), recv() に対応関係があるとは限らない
1回のsend() を複数の recv() で受けとったり
3回のsend() を1回の recv() で受けとることもある
POINT
TCP ソケットで send(), recv() を呼び出せるのは 状態が Establishing のときだけ
connect()
bind() を指定していないので 他のソケットが利用していないポートが自動でアサインされる
[ Closed ] -> [ Connecting ] -> [ Establishing ]
[ ] [ P ]
[ ] [ P ]
[ ] [ P ]
[ ]
サーバ側
bind() listen
[ Closed ] -> [ Closed ] -> [ listening ]
[ ] [ Q ]
[ ] [ ]
[ ] [ ]
[ ]
connect があった場合に新規ソケットを作成して IP, リモートポートが割り当てられる
接続可能なソケットリストに入る
accept() は Establishing なソケットをひとつ返す
->
[ Establishing ]
[ ]
[ ]
■ 切断処理の流れ
POINT
TCP には正当な切断処理という仕組みがある
これにより アプリケーションは転送中のデータを損失せずに済む。
POINT
切断は双方向に行うことができる。
close(), shutdown() は データ送信の終了を表明する。
close() を受けた TCP 側は 送信バッファの情報の残りをすべて送信して
切断の意思(ハンドシェイク)を送信する。
これで受け手はこれ以上受信バッファにデータがこないことを確認できる。
これが recv = 0 ( close() )の意味
肯定応答があれば送信した側の情報がすべて 受信バッファに格納されたことが確認できる。
コネクションは Half-Colsed の状態になる
これを双方に行うことで完全に切断する。
POINT
ディスクリプタの割り当てが解除されても ソケット自体は TCP に残る。
これが Time-Wait の状態。
Time-Wait がある意味は ソケット構造体が存在し続けて
他のプログラムが同じポートのソケットをバインドすることを防ぐことにある。
( EADDINUSE を返す )
POINT
後から接続を解除する場合
相手からの解除要求がきて肯定応答をすると Close-Wait 状態に移行する
[ Establishing ] ---> [ Close-Wait ]
このとき Application の close() 待ちになる。
close() を呼ぶと ソケット構造体へのディスクリプタが解除される。
そして肯定応答が返ると ソケット自体が消滅する。
( つまり 互いにすべての情報を相手側に送れたことを確認できるため )
■ Firewall
DESC
プログラムのインターネットへの接続の可否と種類を制御する
ファイアウォールが搭載されてる。
Windows ファイアウォールが通信を遮断していると
Web サイトにアクセスしたときに「サーバが見つかりません」とエラーが表示される
インターネットなどからの不正なアクセスを防ぐことのできるセキュリティ機能
外部からの侵入を防ぐ System(Software)
NetworkProgram をつくったときは, 自分で 用意している port をあけないとだめ
POINT
これがないと, 危険な Client があると , 好き勝手されてしまう
// こんな感じに穴をあける
//
[ 不正な Program ] [ port ] < ---[FireWall]< ----[ 外部 ]
[ 不正な Program ] [ port ] < ---[FireWall]< ----[ 外部 ]
[ 自分の Program ] [ port ] < ---[ ]< ----[ 外部 ]
[ 不正な Program ] [ port ] < ---[FireWall]< ----[ 外部 ]
[ 不正な Program ] [ port ] < ---[FireWall]< ----[ 外部 ]
Block を指定する単位は port, Program で指定できる
* PC 間の通信と Security 対策
同じ PC ならば問題にならない
* TCPServer の listen で問題になる
* 外部との境界を流れるデータを監視 & 不正なアクセスを検出・遮断
* 127 から始まる id は block する必要なし
// ここが よいかも
こうやって指定します
// 指定した Port を例外にする
CP > Windows > FireWall
* Registry に値が設定される
* 40000 - 40100 Port ( UDP TCP )とかいう
* 全般 tab でファイアウォール機能 の ON/OFF を選びます
[全般] タブ
[有効]
Windows ファイアウォール機能を有効に設定します
外部からの不正アクセスを防いでくれます
D: 有効
[例外を許可しない][例外] タブ (下記参照) にて登録した設定を無効にします
[無効]
機能を無効にする
ほかのファイアウォール ソフトがインストールされている場合を除き
できれば設定はやめた方がいいです
( 外部からの不正アクセスに無防備になります )
* 機能を ON にしたまま 自分のプログラムは Block しないようにできます
[例外]
指定した Program, Port をブロックしないでね ! と伝えます
(例:ネットワーク ゲームやファイル転送など)
ただし [全般] タブにて [例外を許可しない] を有効にした場合
[例外] タブの設定は反映されません
この一覧に表示されたプログラム, ポートは
それぞれの項目において
通信の許可, 不許可の設定やスコープの変更ができる
[プログラムの追加]
追加したプログラムによる入力方向のネットワーク接続をブロックしません
セキュリティ上の危険があるので確認の上追加すること
( 入力とは recv ってことか ? )
[ポートの追加]
特定のポートにおける外部からのアクセスをポート番号, プロトコルを選択して許可する
スコープの変更では
ポートまたはプログラムをどのコンピュータに対してブロックを解除するかの指定も行えます
ポートを追加することで
外部からの不正なアクセスなどに対して無防備な状態になり
[編集]
登録すみの内容を修正します
[削除]
登録したものを削除します
[詳細設定]
* ネットワークごとに 設定をしたいときはここ ( 例: LAN のみとか )
* 他には LOG をとったり 規定値に戻したりとか
複数のネットワーク接続がある環境でネットワーク接続別の設定
パソコンに対しての接続や
[ネットワーク接続の設定]
特定のネットワーク接続に対して例外の追加や詳細設定をする
今回したのはこちら
インターネットのユーザーがアクセスできる
Network で実行中のサービスを選択してください
WebServer ( HTTP )
[セキュリティのログ]
ファイアウォールによって許可された接続やブロックされた接続に関する情報を記録します
[ICMP]
ファイアウォールが有効でも
トラブルシューティングに使用するユーティリティ(PING コマンドなど)
を使用できるように設定する
POINT
PING も network を使った Program
[既定の設定]
Default 設定を戻したいときはこちら
■ JSP
POINT
JSP が処理された結果は, HTML になる. ( RMB > ソースを表示 で対応 )
使いどころ
顧客の情報を集めたい -> DB との連携
■ 環境設定
■ JSP Program の基本
■ Tutorial
// test.jsp
< font color=red> // 文字コードの指定とか < /font>
< %@ page language="java" contentType="text/html; charset=Windows-31J" pageEncoding="Windows-31J" %>
< %@ page language="java" contentType="text/html; charset=SJIS" pageEncoding="Windows-31J" %>
out.println( request.getParameter( "param" ) ); // RET : B&B
// Browser の URL 入力
[ http://localhost/app/test.jsp?param=B%26B ]
簡単な JSP Sample
< html>
< head>< title>Hello World!< /title>< /head>
< body>
< h1>Hello World!< /h1>
< p>
# Java の実行結果におきかわる
< %
// ここに Java Code をかく
String hello = "Hello World!" ;
out.println(hello);
%>
< /p>
< /body>
< /html>
■ 暗黙オブジェクト
以下のオブジェクトは JSP 内で明示的に宣言しなくても利用できる。
request
out
■ Javascript.との違い
POINT
違いをみるには, Browser でソースを表示をするとわかる
jsp 側では HTML の記述のみが表示
// JavaScript Sample
< html>
< head>< title>Hello World!< /title>< /head>
< body>
< h1>Hello World!< /h1>
< p>
< SCRIPT Language="JavaScript" >
< !--
hello = "Hello World!" ;
document.write(hello);
// -->
< /SCRIPT>
< /p>
< /body>
< /html>
[ Browser ] ---( リクエスト ) ---> [ Server ]
[ Browser ] < ---( JavaScript を含んだ HTML ) --- [ Server ]
JavaScript に向いていること
Client で処理が完結すること. ( Game, Animation )
JSP に向いていること
■ Class.を利用する
DESC
Java でつくった Class を JSP から使える
設定ファイル web.xml , context.xml の指定は不要
つくりかた
1. Java Class をつくる ( Java と同じ構文で OK )
2. Compile して指定場所に配置する
3. JSP から class を import して利用する
JSP から利用できるクラスは次の条件が必要
// public 属性にする
1. VarMem
1. MethodMem
// Public Class
public class Test {
public String func() {
return "test class" ;
}
}
ここに置く
/tom/app/WEB-INF/classes/testpkg/Test.java
/tom/app/WEB-INF/classes/testpkg/Test.class
/tom/app/testcalljava.jsp
Package 名 : testpkg
Class 名 : Test
/TOMROOT/app/WEB-INF/classes/testpkg/Test.class
$docBase/WEB-INF/classes/namePkg/original.class
* JSP からよびだします
// import 宣言をします
< %@ page contentType="text/html; charset=sjis" import="testpkg.Test" %>
< %
Test t = new Test();
out.println( t.testfunc() );
%>
/* testcalljava.jsp */
# ( $docBase とは jsp のある dir と同階層にする必要がある )
# examples 相当
$TOMCAT/testdir/WEB-INF/classes/atmarkit/CopyrightHTML.class
useclass.jsp
POINT
WEB-INF/classes/ 以下にクラスを配置することで
新しく作成したクラスは Tomcat を再起動なしで読める
もともと存在しなかった WEB-INF Directory を新しく
作成したときはパスが認識されないことがある
そのときは Tomcat を再起動する
WARNING
class を再コンパイルしても反映されないかも
再起動すれば反映する
■ 自作.Bean.の作成とその使用
DESC
Bean とは
JavaBeans の仕様にのとった命名, 設計規則 Java のクラス
Rule を準備するだけで, 特定のクラスをBeanとして扱えます
うまく使用することで、開発効率を向上させたり、
より信頼性の高いプログラミングができる
Java 言語を用いて開発され
部品化されたプログラムを組みあわせてアプリケーションソフトを構築する手法
Java言語でBeanを作成するための技術仕様
プログラムを部品化すり
複数の開発者間での共有やコードの再利用が容易になり
また出来合いのBeanやサードパーティから提供されるBean
を組み合わせてアプリケーションソフトを開発すり
開発効率が飛躍的に向上します
こんなルールあります
Bean を利用する場合は異なる tag ( BeanTag )を利用する.
< jsp:useBean>
Bean の使用を宣言
# atmarkit.CopyrightHTMLBean
クラスのBeanをcopyrightという名前で使用することを宣言しています。
# Program でいうと,
atmarkit.CopyrightHTMLBean copyright = new atmarkit.CopyrightHTMLBean();
< jsp:useBean id="copyright" class="atmarkit.CopyrightHTMLBean" />
< jsp:setProperty>
Bean のプロパティの値を設定する
// copyrightという名前で宣言された
Bean の year というプロパティに 2001 という値を設定
// 具体的には、copyright オブジェクトの setYear メソッドが実行されるので、
// 通常のプログラム: copyright.setYear("2001" );
< jsp:setProperty name="copyright" property="year" value="2001" />
< jsp:getProperty>
Bean のプロパティの値を取得
// copyright という名前で宣言された Bean の shortHTML というプロパティの値を取得
// copyright オブジェクトの getShortHTML メソッドが実行される
// 通常のプログラムにおける、copyright.getShortHTML();
< jsp:getProperty name="copyright" property="shortHTML" />
次のように考えれば OK .
InstanceName + get + property < -> copyright.getShortHTML();
Bean のスコープ
今回は、1ページの表示で完結するJSPプログラムだったので
特に意識しませんでしたが
Bean にはその有効範囲を指定できる scope 属性があります
種類は
page : 現在の page DEF
request
session
application
< jsp:useBean> タグを次のように記述することでscopeを指定できます
< jsp:useBean id="id_name" class="class_name" scope="scope" />
Scope とは.
一度宣言された Bean にアクセスできる有効範囲を意味する.
scope 属性は
EX
scopeにsessionを指定した場合、同じセッションの範囲においては
違うページであっても同じidの値を指定することで、
同一のBeanにアクセスできる
このようにすることで、セッション間の各ページで1つのBeanを共有できて
異なるページで同じ情報へアクセスできます
それぞれのscopeを指定したときのBeanの特徴は次のようになります
scopeの指定によって、Beanをより有効に利用できるようになります。
scope アクセス可能な範囲 特徴
page 現在のページのみ
デフォルトで使用されるスコープ もっとも有効な期間が短い
request 現在のリクエストの範囲(インクルードされたページ、転送先のページを含む)
レスポンスがユーザーに返されるまでの間だけ有効
EX : form で入力されたデータを保持させる
session 現在のセッションの範囲 セッションと同じだけの有効期間を持つ
EX : ログインしたユーザーの情報を保持させる
application 現在のWebアプリケーション
アプリケーションの有効期間と同じだけ永続的に使用できる
EX : アクセスカウントなどアプリケーション間で共有する情報を保持させる
■ クッキー(cookie)を使う
DESC
前回は JSP でセッションを扱う方法を説明しました。
JSP のセッションでは
セッションの識別 ID がクッキーとして自動的にブラウザ側に格納された
POINT
Cookie とは
Server から渡される設定 file ( NAME = VAL で構成される )
なぜ Cookie ができたか ?
Server で 情報を提供できるが, User の端末に情報を記述できなかった.
というわけで設定 file を渡そうということになった. )
[ Browser ] < -> [ Server ]
< --- 書き込みができない.
JSP から Cookie の値を取得できる API がある
Cookie をどうやってわたすか ?
クッキーがサポートされるブラウザへ
Server が小さなデータ(name=valueの組み合わせ:クッキー)を
レスポンスのヘッダ部分に記述することで保存させる
-> Response にも Header がある.( ということは相互に HTTP 通信. )
-> Browser が Cookie を利用できるようにしなければならない.
shell> curl -I http://www.google.co.jp | grep "Set-Cookie"
// RET
Set-Cookie:
PREF=ID=1a1244830643bcfa:FF=0:TM=1320792924:LM=1320792924:S=1bEhuL4795SjGXSc;
expires=Thu, 07-Nov-2013 22:55:24 GMT; path=/; domain=.google.com
Cookie を Browser から渡すには
Browser は次回そのクッキーを発行したサイトに訪れた際に
持っているクッキーをサーバに提出
サーバはその内容を確認して, 前回自分がブラウザとやりとりしたデータを復元できる
クライアント側がクッキーを受け取った後は、
ブラウザを閉じたり、コンピュータの電源を切ってもクッキーの情報を再度利用できる
クッキーには有効期限を設定できるので
特定の期間を過ぎたクッキーを無効にできる
[ Browser ] --------> [ Server ]
< --------
RESPONSE
name=key1
name=key2
REQUEST
name=key1
name=key2
[ Browser ] --------> [ Server ]
< --------
Cookie が利用されるケース
会員サイトのユーザーIDを保存して、次回の訪問でユーザーを認識する
最後に訪れた日時を保存して、ユーザーの訪問頻度を計る
ユーザーがカスタマイズしたサイトの情報(ユーザーの好み)を保存、次回もその設定を適用する
掲示板へ投稿するときに入力した名前とメールアドレスを保存しておき次回の入力の手間を省く
ページにアクセスすると, アクセスした時刻が Cookie として渡される
< %@ page language="java" contentType="text/html; charset=Windows-31J" pageEncoding="Windows-31J" %>
< %@ page import="java.net.*, java.util.Date" %>
< %
// 現在の時刻を取得
Date now = new Date();
// クッキーに格納する文字列を作成(URLエンコードをする)
String value = URLEncoder.encode(now.toString());
// 名前が"accesstime" 、値が現在時刻であるクッキーを作成
Cookie c = new Cookie("accesstime" ,value);
// 有効期間を1週間に設定
c.setMaxAge(7 * 24 * 60 * 60);
// クッキーを追加 ( Http Response Header に追加される )
response.addCookie( c );
%>
POINT
Cookie を破棄する方法はないため
期間 0 の Cookie を再発行して上書きする
< %@ include file="util.jsp" %>
< %@ page language="java" contentType="text/html; charset=Windows-31J" pageEncoding="Windows-31J" %>
< %@ page import="java.net.*, java.util.Date" %>
< %
// クッキーを使用する(クッキーを破棄する)
// クッキーを作成
Cookie c = new Cookie("accesstime" ,"" );
// クッキーの有効期間を0秒に設定
c.setMaxAge(0);
// クッキーを発行
response.addCookie(c);
%>
< pre>
クッキーの内容を削除しました
< a href="cok.jsp" >クッキーの内容を確認する< /a>
< /pre>
■ 別サイトへの転送(Redirect)
DESC
forward アクションを用いて、アクセス先を転送できる
特定の処理の前
または処理の途中で条件に応じたアクセス先の振り分けに使う
POINT
forwardアクションの用途は
特定のJSPのページから別のページへアクセス先を転送できる
転送先のページには別の JSP 以外にも, HTML, CGI のページも指定できる
代表的な例
UserAgent ( クライアントのブラウザの種類 )によるページの振り分け
// 転送処理をしないと if で処理わけをします
// 煩雑になります
//
if ( PC からのアクセス ) {
// PC HTML 出力
}
else {
// 携帯 HTML 出力
}
// 転送処理ができれば, 別ファイルを用意すれば良い
if ( PC からのアクセス ) {
// PC Page へ転送. pc.jsp
}
else {
// 携帯 Page へ転送. keitai.jsp
}
page Directive ( page に対する指示 )
< %@ page contentType="text/html; charset=Shift_JIS" %>
< %
// request から Browser 情報を取得する
String s = request.getHeader("user-agent" );
// そして Browser によって別ページへ誘導する
// forward アクションは、転送先を ( ローカル URL ) しか指定できない。
if ( s.indexOf("Opera" ) >= 0 ) {
response.sendRedirect("http://yahoo.co.jp" );
}
else {
response.sendRedirect("http://google.co.jp" );
}
%>
転送元と転送先での変数の共有
転送処理を行うと便利なケースのもう1つの例としてエラー処理があります
EX
ユーザーから情報を入力してもらうような場合
必ずしもその入力が完全であるとは限りません
入力内容をチェックし、正しければそのまま処理を続け、
問題がある場合にはエラーページへ転送することで効率の良いプログラムができます
このような場合
転送先のエラーページでは、エラーの内容を表示するのが一般的
このためには
エラーの情報が転送元のページからエラーページへ渡される必要がある
forward アクションによる転送では転送元と転送先で、
session オブジェクト、
request オブジェクトが共有される
従って
これらのオブジェクトに変数を格納することで情報を持ちまわることができる
転送元と転送先がどちらも同じアプリケーションに属する場合は、
application オブジェクトも共有されるため、
このオブジェクトに変数を格納することもできます
■ DB.へアクセスする
DESC
業務アプリケーションの世界では、
顧客情報や商品情報( Tool の統計情報. )
などを格納した DB とJSPの連携が必要になることが多い
PostgreSQL のセットアップ
JSP からデータベースへアクセスするためには、JDBC ドライバ が必要になります。
-> JavaDataBaseContainar
テーブルの作成
PostgreSQLのセットアップが完了したら、
データを格納するためのテーブルを作成する
テーブルの作成は、SQLの create table 文でする
table をつくります
create table member (
member_id serial primary key not null,
name varchar(32) not null,
kana varchar(32) not null
);
データベースアクセス用クラスの作成( java で実現する )
JDBC とは
JDBC とは Java から DB にアクセスするための API
■ SYNTAX
■ Directive
SYNTAX
< %@ %>
DESC
JSP Container に Page を処理する方法を指示
// import Directive
< %@ page ... %>
import "java.util.*" ;
< %@ page import="java.util.*" %>
< %@ page import="java.lang.String" %>
< %@ page import="java.io.*" %>
# 式 出力文を生成するのに使用
# Java Code を記述していることを忘れずに.
< %= "foo" %> == < % out.println("foo" ); %>
// 日本語をつかうときは, どの文字Code で出力するか指定します
// Browser が文字化けしないならよいですが、 基本は指定します
// JSP での出力結果を指定する
// Default は ISO-8859-1
// JSP の処理結果であって
日本語< br> と記述した場合は Editor での文字 Code になる
// 現時点では正しく動作しない. -> ないと正しく動作する.
// EUC で出力
< %@ page contentType="text/html; charset=euc-jp" %>
// SJIS で出力
< %@ page contentType="text/html; charset=Shift_JIS" %>
// MIME Type を指定する
// JSP で 特殊文字が文字化けする場合の対処方法
< %@ page contentType="text/html; charset=windows-31j" %>
String はなくもて利用できる
# 適切な module を import しない ERROR になる.
# スクリプトレット
# 実際の処理の内容を指定のスクリプト言語( デフォルトでJava言語 )で記述
# [;] 必須.
< % out.print(nowTime); %>
# Package の使用を宣言
# Package を使用すると, namespace みたいなもの.
# == import "java.util.*" ;
< %@ page import="java.util.*" %>
# テキストの出力部分とロジック部分を混在させることも可能.
# Demerit として, ロジックとHTML部分が混在し、メンテナンス性が低下
# -> String に出力結果を格納して, 最後に, < %= message %> とすべし.
Directive tag は< font color=red> // < /font> とは無関係.
< %! %> 宣言
この宣言文を含むJSPページに固有の変数とメソッドを定義するのに使用します
User の入力をうける.
# これ経由でないと, 送信先で正しく開くことができない.
# ie. 直接 /form.jsp を指定することはできない.
< form action="form.jsp" >
// 文字コードの変換を行う
// EUC_JP として渡された Code
// Java の内部 Code : Unicode
name = new String( name.getBytes("8859_1" ), "EUC_JP" );
birthday = new String(birthday.getBytes("8859_1" ), "EUC_JP" );
■ 関数(Function)
SYNTAX
< %!
function(){}
%>
DESC
JSP 内で利用する関数の定義
< %!
// この Directive 内に関数を定義する
// フォントカラーの指定を行ったHTML文を作成する
public String getColoredHTML(String str, String color) {
return "< font color=\" " + color + " \">" + str + "< /font>" ;
}
public int add( int x1, int x2 )
{
return x1 + x2;
}
%>
< %
// 呼び出す
out.print( add( 1, 2 ) );
%>
■ Include
SYNTAX
// 静的 include
< %@ include file="include.jsp" %>
DESC
* JSP でも Include できます
1. Code 量を減らすため.
2. Maintenance 効率をあげるため
JSP でのプログラムの再利用方法
flush 属性
インクルードされるページを読み込む前にページの
出力バッファをフラッシュするかどうかを制御
現バージョンのJSPでは、必ず true に設定することになっているので、
インクルードアクションを使用する場合は、flush="true" を必ず記述するようにします
#
< jsp:include page="include.jsp" flush="true" />
インクルードディレクティブ
jsp code を埋め込んでから, CMP . そして 実行.
# Servlet の本質は Program を走らせて, Redirect することかも.
[ sinc.jsp ] ---> ( include( 埋め込み ) )
---> [ Compile ] ---> ( class file == Servlet )
■ IncludeAction
DESC
実行時に 組み込んで, 結果を埋め込む. 要は Action を組み込む
どういうときに利用するか ?
1. 特定のフォーマットで Copyright を表示するプログラムを再利用する
[ ainc.jsp ] ---> [ Compile ] ---> ( include( 埋め込み ) )
( class file servlet )
2 回 Compile されている.
1. include.jsp の Compile 結果を include
ファイルパスの指定方法
Include する際の, path は JSP file からの相対. 絶対のどちらも可能.
同一の Context にある必要がある.
BAD
# context.xml での appBase が基準.
< jsp:include page="/home/username/jsp/include.jsp" flush="true" />
# url の指定もできない.
< jsp:include page="http://localhost/jsp/include.jsp" flush="true" />
# Relative
OK
# Relative
< jsp:include page="../include.jsp" flush="true" />
# absolue
< jsp:include page="../include.jsp" flush="true" />
Java コードの比較
JSPプログラムがサーバ側でJavaコードに翻訳されるときにはどのような
処理がなされるのでしょう。
本連載の第4回で“Hello, World!”
の文字列を表示するJSPプログラムがJavaコードに翻訳された結果を見たように、
今回も5-1.jsp、5-2.jspがJavaコードに翻訳された結果を見てみましょう
Javaコードは非常に長いので、実際にHTMLを出力する部分だけに注目してみます。
■ 基本事項
# servlet を拡張したもの.
// Servlet をつかった hello world Sample
// 見てのとおり メンドイです
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorld extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html" );
PrintWriter out = response.getWriter();
out.println("< html>" );
out.println("< head>< title>Hello World!< /title>< /head>" );
out.println("< body>" );
out.println("< h1>Hello World!< /h1>" );
out.println("< p>" );
out.println("Hello World!" );
out.println("< /p>" );
out.println("< /body>" );
out.println("< /html>" );
}
}
■ Server側のJSPの処理
1. Request をうける. ( Socket 通信かな ? )
2. jsp を Compile する.
foo.jsp ---> foo.java ---> CMP
( $tomcat/work/ に生成 )
3.
[ Browser ] ---> [ Server ] -(Compile)->
Context Path に jsp というパスを追加して、
その中に hello.jsp という名前でファイルを保存した場合
localhost_8080%2Fjsp/_0002fhello_0002ejsphello_jsp_0.java
という名前で、このソースコードが存在する
自分の環境では,
$tomcat/work/Catalina/localhost/my/org/apache/jsp/test_jsp.java
-> Java Code が生成されている. 文字列も Java で記述するのか
変換される内容
HTML 文字 < -> out.write( "html" );
1. ImplicitObject [ out ]
JspWriter out = out = pageContext.getOut();
1. JSP ページによって生成されるクラスは HttpJspBase クラスを継承する
-> JSP は 動的に Servelet を生成する仕組み. ( ie. Servelet に集約する. )
HTML 文の出力は HttpJspBase::_jspService() メソッドでされている
HTML 文の出力 JSP ページで記述した処理は
try{ }catch(Exception ex){} 構文の中でする
■ HTTPHeaderの情報
DESC
HTTPHeader とは
Client と Server が Data をやりとりする際の付加される情報
荷物でいうところの 宛名、差出人
クライアントが利用しているブラウザソフトの名称やクッキー情報
ホスト名などが含まれる
// Client がだれか調べて, 携帯用のコンテンツに出力もできます
String user_agent = request.getHeader("user-agent" );
// 接続元の IP をえる
out.print( request.getRemoteHost() );
// Header の項目をすべて取得する
out.print( request.getHeaderNames() );
// getHeader() に渡せばすべて取得できる
■ HTTP
■ リクエスト(Request)
HTTP プロトコルではクライアントは GET メッセージでネットワーク先のリソースを取得する。
SYNTAX
GET / HTTP/1.0
option
...
パスとバージョンの後に、0個以上の任意のオプション行をいれる。
空行でリクエストの終了を意味する。
---------------------------
GET リソースパス バージョン
オプション1
オプション2
...
---------------------------
一番シンプルなメッセージは以下になる
オプションは 属性名: 値 で指定する。
Host を指定する
GET / HTTP/1.0
Host: ooo.iiyudana.net
プログラムでは以下の文字列を send する。
const char *msg = "GET / HTTP/1.0\r\nHost: ooo.iiyudana.net\r\n\r\n" ;
send( sock, msg, strlen(msg), 0 );
■ リクエストオプション
■ Host
仮想ホストによってひとつの IP に複数のホスト名を対応づける場合に
クライアント側がどの URL のページを要求しているか指定をする。
xxx.xxx.xxx.xxx < -> host1.com
host2.com
host3.com
クライアントが GET / HTTP/1.0 を要求しても
サーバは host1.com/ host2.com/ host3.com/ のどれを返せばいいかわからない。
そこでクライアントは host1.com/ に対応づいたページが必要は場合は
Host: host1.com とする。
■ レスポンス
POINT
HTTP ではリクエストの結果が成功なのか失敗なのかは
最初の一行に書いてある
// 200 : StatusCode ( Success )
// OK : 適当な Message ( OKDayo ! )でもいい
HTTP/1.0 200 OK
// 404 : そんな file ありません
HTTP/1.0 404 NOT FOUND
Webブラウザーなどで HP を見るときに使用するプロトコル
----------------------------------------------------------------
// 結果
HTTP/1.1 200 OK
// 更新日
Last-Modified: Sun, 19 Mar 2000 07:16:23 GMT
// ヘッダ
Content-Length: 1484
Content-Type: text/html
( 空行 )
----------------------------------------------------------------
// 空行をはさんでデータ本体がきます
< HTML>
〜
< /HTML>
----------------------------------------------------------------
■ JSP.文字コード
この文字列がPOSTのボディ部分
( POST は Body に組み込まれる )
日本語の場合は歴史的な経過から文字エンコーディングが複数存在する
現在はMicrosoftが開発しWindows系で一般的に採用されている
Shift_JIS と呼ばれるもの
POINT
win32 環境では SJIS が一般的
Linux系で一般的に採用されてい る
EUC-JP の2つのエンコーディング双方が利用
WebSphere はShift_JIS がデフォルト
WTE のサーブレット・エンジンもクライアントから
URLエンコードで送られてきたパラメタ(バイト列)を
Shift_JISの文字コード列だと見なしてこれをJavaのUnicodeに変換する
WTE の環境では
クライアントがEUC-JPで要求パケットを送ってくると問題
WebSphere のアプリケーション・サーバでは設定ファイルの
デフォルトの
クライアント・エンコーディングを変更してこれに対応することになる
これ以外にもPOST要求でHTTP要求のボディ部分に
クライアントからのパラメタを書き込ませ
これをバイトごとに読み出してEUC-JPとしてUnicodeに変換する手段もあるが一般的ではない
WTEの環境ではクライアントから必ず
Shift_JISでエンコードされた要求が到来するようにすることが推奨される
サーバに情報を送信するためのフォームを含む HTML ドキュメントが表示され
ユーザがその画面の送信ボタンをクリックして必要な情報をサーバに送信する際
POINT
IE, Netscapeのブラウザは
HTML文書の文字コードを使ってバイト列を作成し、URLエンコードする
クライアントに渡すフォームを含む
HTMLドキュメントには以下の要素を含めることが必要
これは SJIS でかかれてまよ ! と宣言する
そして SJIS として URLEncode される
< META HTTP-EQUIV="Content-Type" CONTENT="text/html;charset=Shift_JIS" >
更にサーブレットからそのような画面をクライアントに返すときは
エンジンに変換する文字エンコーディングを明示的に指示するだけでなく
クライアントに
HTTP 応答パケットのヘッダ行で使用文字エンコーディングをはっきり通知すべき
response.setContentType("text/html; charset=Shift_JIS" ); // 応答ヘッダContent-Type行追加
Websphere のエンジンはShift_JISでエンコードされた要求パラメタは正しくUnicodeに変換してくれている
Tomcat のエンジンはそれほど親切ではない
このサーブレットは
Tomcat のコンテナで走らせると漢字は正しく表示されない
変換する前の文字コードは ISO8859_1(Latin-1) であると仮定して
8859_1 から Unicode への変換テーブルを用いて
String 型 のオブジェクトにしてしまう
ここが Java の Default の操作
POINT
JAVA は URL を decode するときに "8859-1" として仮定していること -> それを UNICODE に変換する
Byte --> 8859_1 --> request.getParameter( "xxx" )
---> getBytes( "8859-1" ); ---> 指定した文字コードに変換しろ となる
( Java の勝手な解釈 ) もう一度 バイトコードをくれ getBytes( "8859-1" ) これをもとに
Shift_JIS や EUC-JP の文字列が URLエンコード されてくると
対応した 正しい Unicode の文字列が得られない
String correctName = new String(name.getBytes("8859_1" ), "Shift_JIS" );
// 指定文字コードに対応した Byte 列をかえす
getBytes(String charsetName)
指定された文字セットを使用してこの String をバイトシーケンスに符号化し
結果を新規バイト配列に格納します
// HttpRequestDumpの要求パラメタの出力部分を
// Tomcat用に変更する
out.println();
out.println("要求パラメタ:" );
// すべての Parameter を列挙する
Enumeration paramNames = request.getParameterNames();
while ( paramNames.hasMoreElements() ) {
String name = (String) paramNames.nextElement();
String[] values = request.getParameterValues( name );
// out.println(" " + name + ":" );
// Tomcatの場合は上の1行は以下の2行のように変更し
日本語の文字化けに対応すること
// TomcatはURLエンコードされているパラメタのコードが
// 標準のISO8859_1(Latin-1)であると仮定し
// それを単にStringに(つまりUnicodeで)格納して
// 各サーブレットにわたす
// そのためパラメタ文字列を取得するときに
// ISO8859_1から自分が期待する
WARNING
短いデータではJISAutoDetectは正しく機能しない
String correctName = new String(name.getBytes("8859_1" ), "Shift_JIS" );
out.println(" " + correctName + ":" );
for (int i = 0; i < values.length; i++) {
try{
// out.println(" " + values[i]);
// Tomcatの場合は上の1行も同様に以下の2行のように変更すること
String correctValue = new String(values[i].getBytes("8859_1" ), "Shift_JIS" );
out.println(" " + correctValue);
}catch(Exception e){
System.out.println("URL Decoding Error" );
}
}
}
// これは失敗する
8859_1 という文字セットは8ビット単位なので
Unicodeの1文字は確実に8859_1の1バイト
(null(%00)も含めた256文字)に1対1で対応しており問題が生じなかった
WTE の SJISコンバータは
変換できないバイトは無視してしまうので
そのような逆操作をしても元には戻らない
■ Network基礎知識
■ 名前解決
DESC
TCP/IP 通信では IPアドレスが分れば通信はできる。
しかし数字で表すため、覚えずらい。
また IP はホストが所属するネットワークに関係しているため移動すると値が変わってしまう。
そこで、名前をつけておくことで
覚えやすく、変更があった場合でもクライアント側が代えなくても良いようにした。
携帯の電話帳の名前から電話をかけるのと同じ。
あくまで IP による通信は全てIPアドレスを指定する。
InterNetwork だけではなく, LocalAreaNetwork でも必要です
POINT
索引をする方法はこんなものがある
■ DNS
DESC
Internet でつかわれてる 名前解決方法
ホスト名(www.google.co.jp)とIPアドレスの対応表を管理した分散データベースのこと。
DNS(DomainNameSystem)Serverが.Host名.をIPAddress.を変換する。
LAN で DNS による名前解決 をするためには 自前で DNS Server を用意する
Network 内の IPAddress < -> PC名の対応表をもたせれば良い
Domain を取得していないときは 慣例的に [ local ] とつける
名前が foo だったら, 完全修飾ドメイン名 は foo.local
POINT
PC名 + DomainName となる
TCP/IP 通信では 相手の IP がわかっていれば通信できる。
でも数値ではわかりずらい
そこで名前( DomainName )とIPを関連づけようということになった。
この仕組みを DNS と言う。
POINT
:: 数値では 指定しずらいので 名前をつける
11.123.123.11 < -> nantoka.com
// 電話番号の変換と同じです
090-xxxx-xxxx < -> 佐藤さん
dot 表記とは
www.so-net.ne.jp
www : Domain 取得者が自由につけて良い
so-net: Domain 取得者が自由につけて良い
ne : 組織の種類
www.so-net.com
gTLD : generic Top Level Domain ( だれが取得しても OK )
DomainName には 商標権 は配慮されない.
* DNS Server に問い合わせている
* 近年では、日本語など各国独自の言語
文字でドメインを登録できる国際化ドメイン名も利用できるようになった
DNS とは
* 全世界のDNSサーバが連携して運用する
* 一つのドメインに複数のIPアドレスを対応させたり
一つのIPアドレスに複数のドメインを対応もできる
* DNSServer は自分が管理するネットワークの IP < -> DomainName を相互変換する
実世界の住所のように階層になっていて[ . ]で区切って表す。
// 一番が右が TOP です
[ThirdLevelDomain][SecondLevelDomain][TopLevelDomain]
Domain には種類がある
トップレベルドメイン
ドメインが重複しないよう、ドメイン管理はICANNという組織が一元管理してます
ICANNから委任を受けた各国の機関が割り当て業務をしてます
日本の ccTLD であるJPドメイン(.jp)はJPNICが管理する
TIP
Internet, Mail は WebServer, MailServer と[ 通信 ]することでできる
階層構造の電話張( 関連つけ )
XXX さんは < -> 090-xxxx-xxxx
foo@bar.co.jp
[jp] を管理している. DNS Server に IP 問い合わせると答えを教えてくれる
@ の後が DomainName ( Computer のこと )
DomainName < -> IPAddress ( Computer に対して 一意につけられるアドレス. )
nslookup
tracert www.google.com
tracert www.google.com -> stracert 210.111.222.333 と変換
Domain 名 == LAN 名
自分の IP 情報を見てみる
> ipconfig /all
Windows IP Configuration
Host Name . . . . . . . . . . . . : xxx
Primary Dns Suffix . . . . . . . :
Node Type . . . . . . . . . . . . : Unknown
IP Routing Enabled. . . . . . . . : No
WINS Proxy Enabled. . . . . . . . : No
Ethernet adapter ローカル エリア接続:
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : Realtek RTL8139/810x
Family Fast Ethernet NIC
Physical Address. . . . . . . . . : xx-xx-xx-xx-xx-xx
Dhcp Enabled. . . . . . . . . . . : Yes
Autoconfiguration Enabled . . . . : Yes
// 自分の IP です
// Private Address です
IP Address. . . . . . . . . . . . : 192.168.0.2
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.0.1
// DHCP サーバの IP Address
DHCP Server . . . . . . . . . . . : 192.168.0.1
// DNS Server の IP Address
DNS Servers . . . . . . . . . . . : 192.168.0.1
Lease Obtained. . . . . . . . . . : 2010年4月29日 6:49:22
Lease Expires . . . . . . . . . . : 2010年4月30日 6:49:22
nslookup で DNSServer に問い合わせて, FQDN を IP Address に変換します
// google.co.jp の IPAddress を教えて !
nslookup google.co.jp
[ . ] は 名前の階層だった. DNS に対して責任範囲を限定させる
DNS suffix = local ( 設定されている )
名前とIPアドレスの変換は
DNSサーバに名前に対応するIPアドレスを質問します
DNSサーバは、問い合わせを受け取ると対応した対応するIPアドレスを教えてくれる。
教えてもらったIPアドレスで通信できる。
しかし ひとつのDNSサーバでインターネット上の全ての名前と全てのIPアドレスを一箇所で把握するのはムリ。
そのため、名前情報を一箇所で集中管理しなくても良いように、
DNSは自律分散協調するシステムとしてできている。
具体的には
[ 名前に階層を持たせ ]て各DNSサーバに各自の守備範囲を持たせている
[ RootDNS ]
|
[jp] [net] [com]
|
| |
[co.jp] [or.jp]
|
[yahoo.co.jp] [google.co.jp]
こんな順番
// xxx.co.jp の IPAddress を知りたい!
1. 自分の所属する Network 内の DNS Server に聞く
2. わからないなら, ROOT JP に聞く
2. co.jp にきく
3. xxx.co.jp にきく
4. わかった!
■ Broadcast
DESC
Network 上のすべての PC に一斉に呼びかける。
そして、呼ばれたコンピュータは自分の IPAddress を返す。( 返事をする )
WindowsNetwork で NetBIOS over TCP/IP が ON になっているとこの手法が使える。
呼びかける 名前は TCP/IP のホスト名ではなく, DNFQ ではなく, NetBIOS 名です
WARNING
同一 SubnetMask しか使えません
■ LMHOSTS/HOSTS
DESC
LMHOSTS.SAM という テキストファイルに IP < -> PC名 という情報を記述しておく。
TCP/IP Property > [WINS]
WARNING
次のようなデメリットがある
すべての PC に用意する必要がある。
変更、追加があるとそのたびに追加しないといけない
■ WINS(WindowsInternetNamingService)
DESC
DNS と同じく , IPAddress < -> コンピュータ名を変換する方式
* 違うのは DNS が
HostName + DomainName で Computer を管理して
WINS は
NetBIOS名 で管理すること
名前解決
サーバに再帰的に問い合わせて結果を得るというシステム
例えば
example.comというドメイン内のあるユーザーが
www.foo.co.jpというホストのIPアドレスを探す場合
まず最初はexample.comのDNSサーバに問い合わせる
しかしこのDNSサーバは、example.com の内部のホスト名しか管理しておらず
他のドメインの情報は持っていない
そこでこのDNSサーバは、DNSの階層構造ツリーを逆にたどって
まずjpドメイン内のホストを管理しているDNSサーバを見つける
次に、そのサーバを使って、co.jpドメインを管理しているDNSサーバを見つけ
さらに foo.co.jp を管理しているDNSサーバを見つけ、最終的には
そのDNSサーバに対してwwwというホスト名の検索を依頼する
その結果が最初にDNSの検索要求を出したマシンに伝えられ、名前解決 が完了する
* IPAddress は通信をするすべての Program に使われてます
■ IPAddress.と.MacAddress.の対応
DESC
internet では IP Address が付加した data が行き来している
MacAddress が最終的な Address ( IP Address を変換する必要あり )
ARP ( AdressResoulutionProtocol )が解決してくれます
arp コマンドで IP < -> MacAddress の変換表がみる。
// > arp -p
Interface: 192.168.0.2 --- 0x2
Internet Address Physical Address Type
192.168.0.1 xx-xx-xx-xx-xx-xx dynamic
■ Web
DESC
WWW. Mail は Internet の利用したサービスひとつ。
HyperTextTransferProtocol (HTTP)
「http://」というのは「通信方式はHTTPを使いますよ」という意味
webを閲覧するときには
HyperTextTransferProtocol というプロトコルを利用して通信する
実は「http://」というのは「通信方式はHTTPを使いますよ」
と説明を意味する。
実は新聞や雑誌の広告に度々登場する「http://」というのは
「インターネットを使って通信を行う時のプロトコルはHTTPを使ってくださいね」
という意味
他にこんな通信方法( Protocol )がある。
HTTP の基礎的な部分はとても単純でわかりやすいプロトコル( 通信方法 )です
クライアントである web ブラウザ
はwebサーバに TCP でコネクションを張る。
出来上がった TCPコネクションを通じて「このページ下さい」
というとサーバは「ハイどうぞ」とページを渡してくれる。
HTTP の動作
[ Browser ] < -------------------> [ Web Server ] tomcat は WebServer なのか ?
| ( このページください ) --->
|
|
| < --- ( はい, どーぞ )
|
ブラウザにURLをいれると、こんなことおきてます
[ Browser ]
// URL を打ちます
// これは http 通信方式で送るんだな!
// で送り先は xxx.xxxx.jp だな!
// でもこのままだと送れないから, DNS に教えてもらおう
//
[http://xxx.xxxx.jp/xxx.htm]
ブラウザはIPアドレスに向けて [ TCP コネクションを確立 ]しようとする。
TCPコネクションを確立するには、TCPヘッダを含むIPパケットを
webサーバへ送信する。
IPパケットを送信するためにブラウザが動作している機器のOS
は手元にある経路情報を確認して送信するネットワークインターフェースを調べます
調べた結果、機器に付属しているイーサネット経由でTCPパケットを送信すれば
webサーバに送れそうだという事がわかったとします。
送信するネットワークインターフェースがわかると
ブラウザが動作している機器は
TCPパケットをネットワークインターフェースに送信します
ブラウザが動作している機器から送信されたTCPパケットは
イーサネット経由でルータに届きます
ルータは、TCPパケットのIPヘッダを見て転送する先を考えます
ルータのバケツリレー
転送先は、ルータがもつ経路表を見て決まる。
ルータは次のルータに TCPパケットを転送する。
あとはこの繰り返し
[ RouterA ] ----> [ RouterC ] ------> ...
経路表
[---------]
[---------]
[---------]
[---------]
[---------]
[---------]
最終的にTCPパケットはwebサーバまで転送される。
webサーバは、転送されてきたTCPパケットを受け取ると
ブラウザがある機器に対して返事をする。
(webサーバが返事をできるのは
TCPパケットに送信元IPアドレスが記述されているためです。)
このようにしてブラウザの機器とwebサーバの間にTCPコネクションが確立します。
Header は 郵便物のあて先.
ブラウザは、作成したTCPコネクションを使って
-> ここまでは, 単なる[ コネクション処理 ]
HTTP プロトコルで www.yahoo.co.jp 内の「/index.html」というページを要求します
web サーバは、それに応答してページデータを送信する。
送信されたデータの記述を見てブラウザはページを表示する。
(データの記述にはHTML(HyperTextMarkupLanguage)という言語が使われます
但し、画像やその他言語によるデータがHTTP上に流れたりもします。)
金利比較.COM