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クライアントの流れは、
  1. ソケットの作成を行う。

  2. Webサーバに接続する。

  3. Webサーバに"GET"リクエストを行う。

  4. Webサーバから送信されたデータをすべて受信する。

  5. ソケットを破棄する。
という感じになります。

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 をリンクしてください。



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


Internet Programming
Internet Programming