C++ iostream 実装例 その 7 †前節(C++ iostream 実装例 その 6)で、必要な機能は全部 streambuf へ 押し込めた。ここまでくれば、iostream 化は容易である。 まず名前であるが、tcpstream としてもよいのだが、tcpclient とする。 TCP の stream には他にも、例えば accept が返す socket に対する stream など が考えられるからである。 fstream に習って、constructor は
の二つ。いずれも例外を throw することはない。 独自に実装する member 関数も fstream に習って
の4つとする。 修正 2010-01-13 †
概要 †基本的には、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 |