C++のiostream

C++ iostream 実装例 その 6

前節 C++ iostream 実装例 その 5 の考察に基づき、今まで 作成してきた tcpbuffer に connect() と close() を付加しよう。 また、fstream に習って、is_connect() も用意する。

修正 2010-01-13

  • sync() の return の直前に setp() による位置修正が必要。加えた。

Windows (winsock) での利用

今まで(source が読みづらくなるので)Windows 版を 省略していたが、ここでは全部示す。Windows の時は、compile 時に WINSOCK を定義すること。また、getaddrinfo() ではなく gethostbyname() を使う時は USE_GETHOSTBYNAME を定義すること。

例えば、MinGW/g++ で両方を定義するのであれば

g++ -c -DWINSOCK -DUSE_GETHOSTBYNAME mytcpbuf.cpp

などとする。

また、winsock は library の load/unload を必要とするので、 netlibrary という class を用意した。main 関数の先頭にでも object を生成しておく(winsock を使わない時でも dummy として 働くので、source は共通にできる)。destructor で unload される。

header file

前節までと異なり、buffer を unsigned char から char に変更した。 これは、send()、recv() の引数の型が system により異なるためで、 header で吸収してもよいのだが、iostream がもともと char 型の ためのものでもあるので、char に変更した。

char 型が signed か unsigned かは実装依存(だったと思う)なので、int にもって いく時に符号拡張されないように注意する必要がある。

また、copy constructor と代入 operator は単純に禁止してある。 (private で宣言して、定義しない)。

// mytcpbuf.h

#ifndef TCPBUFFER_H_INCLUDED
#define TCPBUFFER_H_INCLUDED

#ifdef WINSOCK
#include <winsock2.h>
#include <ws2tcpip.h>
#endif	// WINSOCK

#include <streambuf>

namespace mynetlibrary
{

#ifndef WINSOCK
typedef int SOCKET;
#endif	// WINSOCK

class netlibrary
{
public:
	netlibrary(void);
	~netlibrary(void);

private:
	static bool m_libloaded;
};

class tcpbuffer :
	public std::streambuf
{
public:
	tcpbuffer(void);
	virtual ~tcpbuffer(void);
	bool is_connect(void) const;
	int connect(const char* host, int port);
	int close(void);

protected:
	virtual int underflow(void);
	virtual int overflow(int c = std::char_traits<char>::eof());
	virtual int sync(void);

private:
	tcpbuffer(const tcpbuffer& src);
	tcpbuffer& operator=(const tcpbuffer& src);

protected:
#ifndef WINSOCK
	static const int INVALID_SOCKET = (-1);
#endif	// WINSOCK
	static const int MY_MAXGBACK = 128;
	static const int MY_GBUFSIZE = 1024;
	static const int MY_PBUFSIZE = 1024;

protected:
	SOCKET m_socket;
	char* m_gbuf;
	char m_rbuf[(MY_MAXGBACK + MY_GBUFSIZE + 1)];
	char m_pbuf[(MY_PBUFSIZE + 1)];
};

}	// namespace mynetlibrary
#endif	// TCPBUFFER_H_INCLUDED

source file

かなり長くなっているが、connect に、かなりの量を使っていることが わかるだろう。 しかも connect では getaddrinfo() 版と gethostbyname() 版 を merge しているので、余計長くなっている。

なお、underflow と overflow にある 255 による mask は char から int への変換で、符号拡張されないようにするためである。

// mytcpbuf.cpp

#ifndef WINSOCK
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#endif	// WINSOCK

#include "mytcpbuf.h"

namespace mynetlibrary
{

bool
netlibrary::m_libloaded = false;

netlibrary::netlibrary(void)
{
	if(m_libloaded)
		return;
#ifdef WINSOCK
	WORD wVersion;
	WSADATA wsaData;
	wVersion = MAKEWORD(2,2);
	if(::WSAStartup( wVersion, &wsaData ))
		m_libloaded = false;
	else
		m_libloaded = true;
#else	// WINSOCK
	m_libloaded = true;
#endif	// WINSOCK
}

netlibrary::~netlibrary(void)
{
	if(!m_libloaded)
		return;
#ifdef WINSOCK
	::WSACleanup();
#endif	// WINSOCK
	m_libloaded = false;
}

tcpbuffer::tcpbuffer(void)
{
	m_socket = INVALID_SOCKET;
	m_gbuf = m_rbuf + MY_MAXGBACK;
	setg((char*)m_gbuf, (char*)m_gbuf, (char*)m_gbuf);
	setp((char*)m_pbuf, (char*)(m_pbuf + MY_PBUFSIZE));
}

tcpbuffer::~tcpbuffer(void)
{
	close();
}

bool
tcpbuffer::is_connect(void) const
{
	if(m_socket == INVALID_SOCKET)
		return false;
	return true;
}

int
tcpbuffer::connect(const char* host, int port)
{
	// Returns 0 if success, otherwise returns non-zero.

	if(m_socket != INVALID_SOCKET)
		return (-1);	// already conected

	m_socket = ::socket(PF_INET, SOCK_STREAM, 0);
	if(m_socket == INVALID_SOCKET)
		return (-2);

#ifndef USE_GETHOSTBYNAME
	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();
		return (-3);
	}
	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(m_socket, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) == 0)
			break;
		res = res->ai_next;
	}
	::freeaddrinfo(restop);
	if(!res)
	{
		close();
		return (-4);
	}

#else	// USE_GETHOSTBYNAME

	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(m_socket, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) != 0)
		{
			close();
			return (-3);
		}
	}
	else
	{
		if((hp = ::gethostbyname(host)) == 0)
		{
			close();
			return (-4);
		}
		if(hp->h_addrtype != AF_INET)
		{
			close();
			return (-5);
		}
		const char** addr = (const char**)hp->h_addr_list;
		while(*addr)
		{
			::memcpy((char*)&srvaddr.sin_addr, (char*)(*addr), hp->h_length);
			if(::connect(m_socket, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) == 0)
				break;
			++addr;
		}
		if(*addr == 0)
		{
			close();
			return (-6);
		}
	}
#endif	// USE_GETHOSTBYNAME
	return 0;
}

int
tcpbuffer::close(void)
{
	if(m_socket == INVALID_SOCKET)
		return -1;
	int ret;
#ifndef WINSOCK
	ret = ::close(m_socket);
#else	// WINSOCK
	ret = ::closesocket(m_socket);
#endif	// WINSOCK
	m_socket = INVALID_SOCKET;
	return ret;
}

int
tcpbuffer::underflow(void)
{
	if(m_socket == INVALID_SOCKET)
		return std::char_traits<char>::eof();
	int c;
	if((gptr() >= (char*)m_gbuf) && (gptr() < egptr()))
	{
		// Why call me ?
		// Still we have enough data in the buffer.
		c = (int)*gptr(); c &= 255;
		return c;
	}

	// Move the previos (tail) data in backup area.
	int nback = (int)(egptr() - eback());
	if(nback > MY_MAXGBACK)
		nback = MY_MAXGBACK;
	if(nback > 0)
		::memmove((char*)(m_gbuf - nback), (egptr() - nback), nback);

	// Fill the new data in read area.
	int n;
	if((n = ::recv(m_socket, m_gbuf, MY_GBUFSIZE, 0)) <= 0)
		return std::char_traits<char>::eof();
	setg((char*)(m_gbuf - nback), (char*)m_gbuf, (char*)(m_gbuf + n));
	c = (int)m_gbuf[0]; c &= 255;
	return c;
}

int
tcpbuffer::overflow(int c)
{
	if(m_socket == INVALID_SOCKET)
		return std::char_traits<char>::eof();
	// Try to make rooms to put a new character
	int npend = (int)(pptr() - (char*)m_pbuf);
	if(npend < 0)
		return std::char_traits<char>::eof();
	if(npend > 0)
	{
		int nsend = ::send(m_socket, m_pbuf, npend, 0);
		if(nsend <= 0)
			return std::char_traits<char>::eof();
		npend -= nsend;
		if(npend > 0)
		{
			// Oooops!! Still we have pending data.
			// Move the pending data to the beginning of
			// the buffer to send them in next time.
			::memmove(m_pbuf, (m_pbuf + nsend), npend);
		}
	}
	setp((char*)(m_pbuf + npend), (char*)(m_pbuf + MY_PBUFSIZE));
	if(c == std::char_traits<char>::eof())
		return 0;
	*pptr() = (char)(c & 255);
	pbump(1);
	return c;
}

int
tcpbuffer::sync(void)
{
	if(m_socket == INVALID_SOCKET)
		return (-1);

	int nleft = (int)(pptr() - (char*)m_pbuf);
	int nsend;

	char* p = m_pbuf;
	while( nleft > 0 )
	{
		nsend = ::send(m_socket, p, nleft, 0);
		if( nsend <= 0 )
			return (-1);
		nleft -= nsend;
		p += nsend;
	}
	setp((char*)m_pbuf, (char*)(m_pbuf + MY_PBUFSIZE));
	return 0;
}

}	// namespace mynetlibrary

sample program

さて、今まで windows 版を示してないので、それも兼ねて sample program を示す。機能は今まで示したものと同じ。 MinGW/g++ で gethostbyname を使うので、あれば

g++ -DWINSOCK -DUSE_GETHOSTBYNAME testbuf2.cpp mytcpbuf.cpp -lws2_32 -o testbuf2.exe

とすればよい。

// testbuf2.cpp

#include <iostream>
#include "mytcpbuf.h"

char linebuf[4096];

int
main(int argc, char* argv[])
{
	mynetlibrary::netlibrary lib;

	if(argc != 2)
	{
		std::cerr << "Usage: " << argv[0] << " host" << std::endl;
		return (-1);
	}
	mynetlibrary::tcpbuffer tcp;
	tcp.connect(argv[1], 80));
	if(!tcp.is_connect())
	{
		std::cerr << "Tcp connection error." << std::endl;
		return (-1);
	}
	std::iostream tcpstream((std::streambuf*)&tcp);
	tcpstream << "GET / HTTP/1.0\r\n";
	tcpstream << "Host: " << argv[1] << "\r\n";
	tcpstream << "\r\n";
	tcpstream.flush();
	while(tcpstream.getline(linebuf, 4095))
	{
		std::streamsize n = tcpstream.gcount();
		linebuf[n] = 0;
		std::cout << linebuf << std::endl;
	}
	std::cout.flush();
	return 0;   
}

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-01-13 (水) 15:30:08