C++ iostream 実装例 その 6 †前節 C++ iostream 実装例 その 5 の考察に基づき、今まで 作成してきた tcpbuffer に connect() と close() を付加しよう。 また、fstream に習って、is_connect() も用意する。 修正 2010-01-13 †
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; } |