C++のiostream

C++ iostream 実装例 その 2

ここでは前節 "C++ iostream 実装例 その 1" で示した class を iostream として使う例を示す。

作成する program

作成する program は internet host (Web server) 名を与えると、 http (hyper text transfer protocol) を使って、 その host の root document を取得するものとしよう。

流れを示すために、まずは途中 host に connect する部分を省略 して示す。

// testbuf.cpp

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <iostream>
#include "tcpbuf.h"

#define MY_BUFSIZE 4096
char linebuf[MY_BUFSIZE];

int
main(int argc, char* argv[])
{
	if(argc != 2)
	{
		std::cerr << "Usage: " << argv[0] << " host" << std::endl;
		return (-1);
	}
	int sock = ::socket(PF_INET, SOCK_STREAM, 0);
	if(sock == -1)
	{
		std::cerr << "Socket creation failure." << std::endl;
		return (-1);
	}

	const char* host = argv[1];
	int port = 80;

	//              :
	// host に connect するための一連の記述(後述)
	//              :

	mynetlibrary::tcpbuffer tcpbuf(sock);
	::close(sock);	// この module では、もはや使わないので閉じておく。 
	std::iostream tcpstream((std::streambuf*)&tcpbuf);

	tcpstream << "GET / HTTP/1.0\r\n";
	tcpstream << "Host: " << host << "\r\n";
	tcpstream << "\r\n";
	tcpstream.flush();
	while(tcpstream.getline(linebuf, (MY_BUFSIZE - 1)))
	{
		std::streamsize n = tcpstream.gcount();
		linebuf[n] = 0;
		std::cout << linebuf << std::endl;
	}
	std::cout.flush();
	return 0;   
}

このように、std::iostream の constructor の引数として std::streambuf の object の pointer を渡せば、operator << やら getline() やら、 gcount() やら、iostream が提供する機能が使いたい放題使える。

tcpbuffer を construct した直後に sock を close() していることに 注意する。constructor 中で socket descriptor を dup() しているので、 ここで close() しても正しく動作する。むしろ、 呼び出し側は(こちら側ではもはや使わないので) ここで close() すべきで ある。

multi-thread の場合は、このような仕掛けが無いと、二つ以上の thread で 同じ socket descriptor を共有した時に、片方が close() してしまうと 相手まで close() されてしまう。最初からこのような使い方を意図していたので あればそれでよいが、そうでない場合は、動作時 error となり、しかも thread の 状態により error になったり、ならなかったりするので原因追求が困難になる。

それでも明示的に close() を呼んでいれば、 まだ見つかる可能性はあるが、desctructor などで close() していると 相手が scope から抜けた途端に close() される可能性もあり、発見は 非常に困難になる。

host に connect する部分

では、(本題とは関係無いが)省略した部分を示しておこう。この部分で、その前の(上の) code から引き継いでいるのは

(int sock, char* host, int port)

であり、実用上は、この三つを引数とした関数にしてしまうのが よいだろう(接続できなかった時にどうするかを考察する必要があるが)。 もっと言えば、socket descriptor も隠蔽する class を 作るのがよいだろう(ここでは示さないが、実際、筆者はそうしている)。

なお、 Windows では getaddrinfo() を使うには _WIN32_WINNT >= 0x0501 (Windows XP 以降)である必要がある。それ以前の Windows OS の場合は gethostbyname() を使う必要があろう。この関数は thread safe では ないので、multi-thread 環境で複数の thread から同時に gethostbyname() を 使う可能性がある場合には、この関数を使っている block ごと排他制御を かける必要がある。

[追記:2009-09-02] _WIN32_WINNT < 0x0501 で Windows SDK の場合、 include file <wspiapi.h> を使うことで、getaddrinfo を使うことができる。ただし、MinGW の場合、 当該 include file が無い。

	struct sockaddr_in srvaddr;
	struct sockaddr_in* resaddr;
	struct addrinfo hints;
	struct addrinfo* restop;
	struct addrinfo* res;
	int err;

	res = 0;
	::memset((char*)&hints, 0, sizeof(hints));
	hints.ai_family = PF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	if((err = ::getaddrinfo(host, 0, &hints, &restop)) != 0)
	{
		::close(sock);
		std::cerr << "Unknown host" << std::endl;
		return (-1);
	}
	res = restop;
	while(res)
	{
		resaddr = (struct sockaddr_in*)res->ai_addr;
		::memset((char*)&srvaddr, 0, sizeof(srvaddr));
		srvaddr.sin_family = AF_INET;
		srvaddr.sin_port = ::htons((u_short)port);
		srvaddr.sin_addr = resaddr->sin_addr;
		if(::connect(sock, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) == 0)
			break;
		res = res->ai_next;
	}
	::freeaddrinfo(restop);
	if(!res)
	{
		::close(sock);
		std::cerr << "Connection failed." << std::endl;
		return (-1);
	}

なお、この program では、複数の address を持つ host に対しては、 getaddrinfo() の返してきた list の順に connect を試みて、最初に 成功したところで抜けるようにしている。

一応 Windows 2000 なども想定して、gethostbyname() 版も示しておこう。 上に述べたように、これは thread safe ではないことに注意する。 また、inet_addr() は inet_aton() を使いたいところであるが、 調査(2009年8月31日)時点で、Windows OS には無いようなので、 inet_addr() を使っておく。

	struct sockaddr_in srvaddr;
	unsigned long inaddr;
	struct hostent *hp;
	::memset((char*)&srvaddr, 0, sizeof(srvaddr));

	srvaddr.sin_family = AF_INET;
	srvaddr.sin_port = ::htons((u_short)port);

	if((inaddr = ::inet_addr(host)) != INADDR_NONE)
	{
		::memcpy((char*)&srvaddr.sin_addr, (char*)&inaddr, sizeof(inaddr));
		if(::connect(sock, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) != 0)
		{
			::close(sock);
			std::cerr << "Connection failed." << std::endl;
			return (-1);
		}
	}
	else
	{
		if((hp = ::gethostbyname(host)) == 0)
		{
			::close(sock);
			std::cerr << "Unknown host." << std::endl;
			return (-1);
		}
		if(hp->h_addrtype != AF_INET)
		{
			::close(sock);
			std::cerr << "Different address type." << std::endl;
			return (-1);
		}
		const char** addr = (const char**)hp->h_addr_list;
		while(*addr)
		{
			::memcpy(((char*)&srvaddr.sin_addr, (char*)(*addr), hp->h_length);
			if(::connect(sock, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) == 0)
				break;
			++addr;
		}
		if(*addr == 0)
		{
			::close(sock);
			std::cerr << "Connection failed." << std::endl;
			return (-1);
		}
	}

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-09-03 (木) 10:44:37