C++のiostream

C++ iostream 実装例 その 7

前節(C++ iostream 実装例 その 6)で、必要な機能は全部 streambuf へ 押し込めた。ここまでくれば、iostream 化は容易である。

まず名前であるが、tcpstream としてもよいのだが、tcpclient とする。 TCP の stream には他にも、例えば accept が返す socket に対する stream など が考えられるからである。

fstream に習って、constructor は

  • tcpclient();
  • tcpclient(const char* host, int port);

の二つ。いずれも例外を throw することはない。

独自に実装する member 関数も fstream に習って

  • tcpclient* rdbuf() const;
  • void connect(const char* host, int port);
  • void close();
  • bool is_connect() const;

の4つとする。

修正 2010-01-13

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

概要

基本的には、tcpclient は tcpbuffer を member 変数として保持し、 tcpclient に独自に実装する関数は、tcpbuffer の関数を呼び出す だけである。この時、connect() 及び close() はその結果により、 failbit を設定する。 connect() まで行う constructor にあっても同様である。 この bit を設定すると、基底 class の iostream で exception flag を 判定して、exception を throw するかどうかを決めるので、派生 class 側で 実装する必要は無い。

header file

この class のための include file を別に作ってもよいのだが、 tcpbuffer とは切っても切れない関係なので、 前の mytcpbuf.h に追加する形にして、ここでは mytcplib.h という 一つの file にまとめる。

// mytcplib.h

#ifndef MY_TCPLIBRARY_H_INCLUDED
#define MY_TCPLIBRARY_H_INCLUDED

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

#include <streambuf>
#include <iostream>

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)];
};

class tcpclient :
	public std::iostream
{
public:
	tcpclient(void);
	tcpclient(const char* host, int port);
	tcpbuffer* rdbuf(void) const;
	void connect(const char* host, int port);
	void close(void);
	bool is_connect(void) const;

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

protected:
	tcpbuffer m_tcpbuf;
};

}	// namespace mynetlibrary
#endif	// MY_TCPLIBRARY_H_INCLUDED

source file

定義の方は、簡単である。

// mytcpcli.cpp

#include "mytcplib.h"

namespace mynetlibrary
{

tcpclient::tcpclient(void) :
	std::iostream(0)
{
	this->init(&m_tcpbuf);
}

tcpclient::tcpclient(const char* host, int port) :
	std::iostream(0)
{
	this->init(&m_tcpbuf);
	if(m_tcpbuf.connect(host, port) != 0)
		this->setstate(std::ios::failbit);
	else
		this->clear();
}

tcpbuffer*
tcpclient::rdbuf(void) const
{
	return const_cast<tcpbuffer*>(&m_tcpbuf);
}

void
tcpclient::connect(const char* host, int port)
{
	if(m_tcpbuf.connect(host, port) != 0)
		this->setstate(std::ios::failbit);
	else
		this->clear();
}

void
tcpclient::close(void)
{
	if(m_tcpbuf.close() != 0)
		this->setstate(std::ios::failbit);
	else
		this->clear();
}

bool
tcpclient::is_connect(void) const
{
	return m_tcpbuf.is_connect();
}

}	// namespace mynetlibrary

sample programs

前の sample も書き換えてみよう。

// testbuf3.cpp

#include "mytcplib.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::tcpclient tcpstream(argv[1], 80);
	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;   
}

前に fstream でやった try-catch の例も、全く対称に書ける。

// testbuf4.cpp

#include "mytcplib.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::tcpclient tcpstream;
	tcpstream.exceptions(mynetlibrary::tcpclient::badbit | mynetlibrary::tcpclient::failbit);
	try
	{
		tcpstream.connect(argv[1],80);
		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();
	}
	catch(mynetlibrary::tcpclient::failure& e)
	{
		std::cerr << "Failure: " << e.what() << std::endl;
	}
	return 0;   
}

補足

mytcpbuf.cpp の方も(本質的ではないが)若干書き換えたので、再掲しておく。

// mytcpbuf.cpp

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

#include "mytcplib.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;
	this->setg((char*)m_gbuf, (char*)m_gbuf, (char*)m_gbuf);
	this->setp((char*)m_pbuf, (char*)(m_pbuf + MY_PBUFSIZE));
}

tcpbuffer::~tcpbuffer(void)
{
	this->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)
	{
		this->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)
	{
		this->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)
		{
			this->close();
			return (-3);
		}
	}
	else
	{
		if((hp = ::gethostbyname(host)) == 0)
		{
			this->close();
			return (-4);
		}
		if(hp->h_addrtype != AF_INET)
		{
			this->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)
		{
			this->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();
	this->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);
		}
	}
	this->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

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