ソケットプログラミング

HTTPサーバに接続してデータを取得する、クライアントプログラムを作ります。

簡単なサンプル (Linux)

httpget.c

/*
 * name:   httpget version 0.1
 * use:	   TCP/IP socket client sample program for Linux
 * date:   Tue Jul 17 2001
 * author: itouh@lycos.ne.jp
 */

#include <stdio.h> //fprintf(), stderr
#include <unistd.h> //write(), close()
#include <sys/types.h> //socket(), connect(), send(), recv()
#include <sys/socket.h> //socket(), connect(), send(), recv()
#include <netdb.h> //gethostbyname(), struct hostent
#include <netinet/in.h> //htons(), struct in_addr

#define MAXBUF 1500
#define HTTP_SERVER_PORT 80
#define HOSTNAME "www.ibrado.com"
#define FILE_PATH "/sock-faq/index.html"
#define USER_AGENT "Mozilla/4.0 (compatible; httpget 0.1; Linux)"

/* func prototype */
void host2addr(const char *hostname, struct in_addr *addr);
void errexit(char *s);


int main(void){
	struct sockaddr_in addr; //接続先のIPアドレスとポート番号を格納する
	unsigned char buf[MAXBUF];
	int rc; //result code
	int sock; //file descripter


	/* サーバのHOSTNAME文字列を IPアドレスに変換して、構造体に格納する */
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(HTTP_SERVER_PORT);
	host2addr(HOSTNAME, &addr.sin_addr);

	/* ソケットを生成する */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
		errexit("unable to generate a socket");

	/* ソケットを介して、サーバと接続する */
	rc = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
	if(rc != 0)
		errexit("unable to connect");

	/* サーバに HTMLファイル送出を要求する */
	{
		char s[MAXBUF];
		memset(s, 0x00, MAXBUF);
		sprintf(s, 
			"GET %s HTTP/1.0\r\n"
			"User-Agent: %s\r\n"
			"Host: %s\r\n"
			"Connection: close\r\n\r\n",
			FILE_PATH, USER_AGENT, HOSTNAME);

		rc = send(sock, s, strlen(s), 0);
		if(rc < 0)
			errexit("unable to send");
	}	

	/* データを受信する */
	for(;;){
		memset(buf, 0x00, MAXBUF);
		rc = recv(sock,buf,sizeof(buf),0);

		if(rc < 0)
			errexit("unable to receive data correctly");
		else if(rc == 0)
			break; //データ受信完了

		write(1, buf, rc); //標準出力 stdout に出力する
	}		

	close(sock); /* ソケットを閉じる */
	fprintf(stderr, "\n\nhttpget: Receiving data is completed.\n");
	return 0;
}


/*
 * host文字列を IPアドレスに変換して、in_addr構造体に格納する
 */
void host2addr(const char *hostname, struct in_addr *ip){
	struct hostent *host;
	host = gethostbyname(hostname);
	if(host != NULL){
		*ip = *(struct in_addr *)(host->h_addr_list[0]);
		return;
	}
	errexit("unable to locate the server\n");
}


/*
 * エラーメッセージを表示して終了する
 */
void errexit(char *s){
	fprintf(stderr, "error: %s\n", s);
	exit(1);
}

コンパイル、実行は、

$ cc -o httpget httpget.c
$ ./httpget | less
とすればいいでしょう。 VineLinux2.1.5で動作を確認しました。

説明

#define HTTP_SERVER_PORT 80
#define HOSTNAME "www.ibrado.com"
#define FILE_PATH "/sock-faq/index.html"
ここでは、www.ibrado.com のサイトのポート80番 (一般的にはHTTPサーバ) につないて、/sock-faq/index.html というファイルを要求しようとしてます。URL でいえば、http://www.ibrado.com/sock-faq/index.html のファイル。

手順としてはまず、「www.ibrado.com」というホストネームを、IPアドレスに変換します。そのためには DNSサーバにアクセスしなくてはなりませんが、C言語では gethostbyname() を使えばいいです。

そのIPアドレスと、ポート番号 ( htons でネットワークバイトオーダに変換しておきます)、そしてアドレスファミリ (インターネットに接続する場合は AF_INET でいい)を、sock_addr_in構造体に代入します。この構造体の中身は、man ip で調べることができます。

ソケットを生成します。そのソケットを使用して、サーバと接続。HTTPプロトコルにのっとって、文字列を送り、「ファイルを送ってくれたまえ」と要求します。

データを受信して、全部受けとったらソケットを閉じます。 これが HTTPプロトコルでファイルを取得する流れです。1ファイルごとにこの手順をふまなければなりません (IPアドレス調べて構造体に代入するとかの手順は、同じサイトに接続するときは省いてよし)


itouh