[[C++のiostream]]

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

** 修正 2010-01-13 [#pa91b783]
- sync() の return の直前に setp() による位置修正が必要。加えた。

** Windows (winsock) での利用 [#r8d72bd7]
今まで(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 [#u0b544e0]
前節までと異なり、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 [#q5f4faaf]
かなり長くなっているが、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 [#k32bfe6e]
さて、今まで 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