[[C++のiostream]]

* C++ iostream 実装例 その 7 [#dde2b5e9]
前節([[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 [#j40cbd5d]
- sync() の return の前に setp() による位置調整が必要。加えた。

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

** header file [#q0c4d474]
この 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 [#la17886e]
定義の方は、簡単である。
 // 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 [#ucc46599]
前の 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;   
 }

** 補足 [#d339ae34]
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