Internet Programming
HTTPクライアントの作成(非同期型)
[1997/11/28]
実際に非同期のAPIを使ってTCPクライアントを作成する例として、HTTPクライアントを作成してみます。
HTTPは、WebサーバからHTMLなどを取得するプロトコルです。
HTTP(Hypertext Transfer Protocol)についての詳細は、RFC 1945(HTTP1.0)、RFC 2068(HTTP1.1)を参照してください。
HTTPクライアントの流れは、
- ソケットの作成を行う。
- Webサーバに接続する。
- Webサーバに"GET"リクエストを行う。
- Webサーバから送信されたデータをすべて受信する。
- ソケットを破棄する。
という感じになります。
HTTPでは、クライアントがリクエストを送ると、Webサーバはデータを送信して送信が終わると切断するので、クライアントはすべて受信してしまえばソケットを破棄します。
以下のソースは、サンプル中の実際にWebサーバと接続してデータを取得する部分です。
#define WSOCK_GETHOST WM_USER + 1 /* ホスト情報のイベントを通知するメッセージ */
#define WSOCK_SELECT WM_USER + 2 /* ソケットイベントを通知するメッセージ */
/*---------------------------------------------------
ソケットイベントの通知を設定する関数
---------------------------------------------------*/
BOOL SocketSelect(int sSoc,HWND hWnd)
{
/* 接続、受信、切断のソケットイベントを通知させるように設定する */
/* ソケットイベントは、ウィンドウにWSOCK_SELECTメッセージで通知されるように設定する */
if(WSAAsyncSelect(sSoc, hWnd, WSOCK_SELECT, FD_CONNECT | FD_READ | FD_CLOSE) == SOCKET_ERROR){
return FALSE;
}
return TRUE;
}
/*---------------------------------------------------
サーバに接続要求を出す関数
---------------------------------------------------*/
BOOL ConnectServer(int cSoc,unsigned long cIPaddr,int cPort)
{
struct sockaddr_in serversockaddr;
/* サーバのアドレスの構造体にサーバのIPアドレスとポート番号を設定します */
serversockaddr.sin_family = AF_INET;
serversockaddr.sin_addr.s_addr = cIPaddr; /* IPアドレス */
serversockaddr.sin_port = htons((unsigned short)cPort); /* ポート番号 */
memset(serversockaddr.sin_zero,(int)0,sizeof(serversockaddr.sin_zero));
/* サーバにコネクトする */
if(connect(cSoc,(struct sockaddr *)&serversockaddr,sizeof(serversockaddr)) == SOCKET_ERROR){
if(WSAGetLastError() != WSAEWOULDBLOCK){
return FALSE;
}
}
return TRUE;
}
/*---------------------------------------------------
ソケットを破棄する関数
---------------------------------------------------*/
void SocketClose(HWND hWnd)
{
/* WSAAsyncGetHostByName関数の非同期なタスクがある場合はキャンセルする */
if(hGetHost != NULL){
WSACancelAsyncRequest(hGetHost);
hGetHost = NULL;
}
/* ソケットイベントの通知を取り消す */
WSAAsyncSelect(soc, hWnd, 0,0);
/* ソケットを破棄する */
closesocket(soc);
soc = -1;
SetWindowText(hBTNGET,"GO");
}
/*---------------------------------------------------
ソケットのエラー処理関数
---------------------------------------------------*/
void SocketError(HWND hWnd,char *msgbuf)
{
/* ソケットを破棄する */
SocketClose(hWnd);
/* エラーメッセージ */
MessageBox(hWnd,msgbuf,"Error",MB_OK | MB_ICONERROR);
}
/*---------------------------------------------------
HTTPセッションの開始
---------------------------------------------------*/
void HTTPStart(HWND hWnd,char *buf)
{
unsigned long serveraddr; /* サーバのIPアドレス */
/* URLからサーバ名とパスを取得する */
Port = GetURL(buf,SvName,Path);
if(Port == -1){
MessageBox(hWnd, "URLが不正です。", "Error", MB_OK | MB_ICONERROR);
SetWindowText(hBTNGET,"GO");
return;
}
/* socにソケットを作成します */
soc = socket(PF_INET, SOCK_STREAM, 0);
if(soc == INVALID_SOCKET){
MessageBox(hWnd, "ソケットの作成に失敗しました。", "Error", MB_OK | MB_ICONERROR);
soc = -1;
SetWindowText(hBTNGET,"GO");
return;
}
/* svNameにドットで区切った10進数のIPアドレスが入っている場合、serveraddrに32bit整数のIPアドレスが返ります */
serveraddr = inet_addr((char*)SvName);
if(serveraddr == -1){
/* サーバ名からサーバのホスト情報を取得する */
/* ホスト情報が取得できると、ウィンドウにWSOCK_GETHOSTが通知されるようにする */
hGetHost = WSAAsyncGetHostByName(hWnd,WSOCK_GETHOST,SvName,gHostEnt, MAXGETHOSTSTRUCT);
if(hGetHost == 0){
SocketError(hWnd,"サーバのIPアドレスの取得に失敗しました。");
return;
}
return;
}
/* 接続、受信、切断のイベントをウィンドウメッセージで通知されるようにする */
if(SocketSelect(soc, hWnd) == FALSE){
SocketError(hWnd,"ソケットイベントを通知させる設定に失敗しました。");
return;
}
/* サーバに接続する */
if(ConnectServer(soc,serveraddr,Port) == FALSE){
SocketError(hWnd,"サーバへの接続に失敗しました。");
}
}
/*---------------------------------------------------
IPアドレスを取得して接続開始する
---------------------------------------------------*/
void HTTPGetHostToIPaddr(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
struct hostent FAR *HostEntry; /* ホスト情報 */
unsigned long serveraddr; /* サーバのIPアドレス */
/* エラーの判定 */
if(WSAGETASYNCERROR(lParam) != 0){
SocketError(hWnd,"サーバのIPアドレスの取得に失敗しました。");
return;
}
/* WSAAsyncGetHostByNameの戻り値である非同期なタスクのハンドルか判定*/
if(hGetHost != (HANDLE)wParam){
return;
}
HostEntry = (struct hostent FAR *)gHostEnt;
serveraddr = *((unsigned long *)((HostEntry->h_addr_list)[0])); /* IPアドレスを取得する */
hGetHost = NULL;
/* ソケットイベントをウィンドウメッセージで通知されるようにする */
if(SocketSelect(soc, hWnd) == FALSE){
SocketError(hWnd,"ソケットイベントを通知させる設定に失敗しました。");
return;
}
/* サーバに接続する */
if(ConnectServer(soc,serveraddr,Port) == FALSE){
SocketError(hWnd,"サーバへの接続に失敗しました。");
return;
}
}
/*---------------------------------------------------
HTTPリクエストを送信する
---------------------------------------------------*/
void HTTPSendRequest(HWND hWnd)
{
char SendBuf[BUFSIZE]; /* 送信するバッファ */
/* HTTPリクエストの作成 */
wsprintf(SendBuf,"GET %s HTTP/1.0\r\nHost: %s:%d\r\nUser-Agent: httpcex/0.0\r\n\r\n",Path,SvName,Port);
/* データを送信する */
if(send(soc, SendBuf, lstrlen(SendBuf), 0) == SOCKET_ERROR){
SocketError(hWnd,"送信に失敗しました。");
return;
}
strcpy(OUT_BUF,"");
}
/*---------------------------------------------------
データを受信して蓄積する
---------------------------------------------------*/
void HTTPRecvData(HWND hWnd)
{
char RecvBuf[RECVSIZE]; /* 受信するバッファ */
int buf_len; /* 受信したバイト数 */
/* データを受信する */
buf_len = recv(soc, RecvBuf, RECVSIZE - 1, 0);
if (buf_len == SOCKET_ERROR ){
SocketError(hWnd,"受信に失敗しました。");
return;
}
RecvBuf[buf_len] = '\0'; /* 受信するバッファの後ろにヌル文字を付加する */
if((strlen(OUT_BUF) + buf_len) < OUTSIZE){
strcat(OUT_BUF,RecvBuf); /* バッファに受信文字列を蓄積する */
}
}
/*---------------------------------------------------
サーバから切断する
---------------------------------------------------*/
void HTTPClose(HWND hWnd)
{
char RecvBuf[RECVSIZE]; /* 受信するバッファ */
int buf_len; /* 受信したバイト数 */
/* 受信バッファにデータがあればすべて受信する */
while((buf_len = recv(soc, RecvBuf, RECVSIZE - 1, 0)) > 0 ){
RecvBuf[buf_len] = '\0'; /* バッファの後ろにヌル文字を付加する */
strcat(OUT_BUF,RecvBuf);
}
/* ソケットを破棄する */
SocketClose(hWnd);
/* エディットボックスに文字列の表示を行う */
ConvBuf(OUT_BUF); /* 文字列を整形する */
SendMessage(HEdit,WM_SETTEXT,0,(LPARAM) ((LPSTR)OUT_BUF));
}
/*---------------------------------------------------
メインウィンドウのプロシージャ
---------------------------------------------------*/
LONG APIENTRY MainProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
char buf[BUFSIZE];
RECT WinRec;
switch (uMsg)
{
case WM_CREATE: /* ウィンドウ作成時 */
/* ウィンドウ内のコントロールを作成する */
URLEdit = CreateWindowEx(WS_EX_CLIENTEDGE,"EDIT",(LPSTR)NULL,WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,5,10,300,25,hWnd,(HMENU)ID_EDIT_URL,g_hinst,NULL);
hBTNGET = CreateWindow("BUTTON",(LPSTR)"GO",WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,310,10,50,25,hWnd,(HMENU)ID_GO,g_hinst,NULL);
HEdit = CreateWindowEx(WS_EX_CLIENTEDGE,"EDIT",(LPSTR)NULL,WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | ES_NOHIDESEL | ES_MULTILINE | ES_LEFT | ES_READONLY,0,40,0,0,hWnd,(HMENU)ID_EDIT_VIEW,g_hinst,NULL);
/* 初期化 */
soc = -1;
hGetHost = NULL;
break;
case WSOCK_GETHOST: /* サーバのホスト情報関数のイベント */
/* ホスト情報からIPアドレスを取得して接続を開始する */
HTTPGetHostToIPaddr(hWnd,wParam,lParam);
break;
case WSOCK_SELECT: /* ソケットイベントのメッセージの場合(WSAAsyncSelect関数にて登録) */
/* エラーの判定 */
if(soc != -1 && WSAGETSELECTERROR(lParam) != 0){
SocketError(hWnd,"ソケットイベントの通知でエラーが発生しました。");
break;
}
/* 処理すべきソケットか判定 */
if(soc != (int)wParam){
break;
}
/* ソケットイベント毎の処理を行う */
switch(WSAGETSELECTEVENT(lParam))
{
case FD_CONNECT: /* サーバへの接続が完了した事を示すイベント */
/* HTTPリクエストを送信する */
HTTPSendRequest(hWnd);
break;
case FD_READ: /* 受信バッファにデータがある事を示すイベント */
/* データを受信して蓄積する */
HTTPRecvData(hWnd);
break;
case FD_CLOSE: /* サーバへの接続が終了した事を示すイベント */
/* 接続を終了する */
HTTPClose(hWnd);
break;
}
break;
case WM_COMMAND:
switch(LOWORD(wParam)){
case ID_GO:
if(soc != -1){ /* 既に通信中の場合はキャンセルする */
/* ソケットを破棄する */
SocketClose(hWnd);
}else{ /* 通信を開始する */
/* URLを取得する */
SendMessage(URLEdit,WM_GETTEXT,BUFSIZE - 1,(LPARAM)buf);
SetWindowText(hBTNGET,"Cancel");
/* HTTPセッションを開始する */
HTTPStart(hWnd,buf);
}
break;
}
break;
:
:
サンプルのダウンロード:
httpcex00.zip
- httpcex.c
- httpcex.rc
- resource.h
Res
- icon1.ico
- httpcex.exe
このプログラムは、GUIベースで動作します。
httpcex.cをコンパイルする場合は、wsock32.lib をリンクしてください。

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

Internet Programming