[2018.6]
C++ の IOストリームで, 新しい対象に対して入出力できるようにしたい場合, istream
, ostream
, iostream
の各クラスに似たストリームクラスと, basic_streambuf
クラスから派生させたクラスをペアで作ります。
iostream
クラスは書式付き入出力を担当し, 媒体への書き込み・読み込みは streambuf
クラスで行なうようになっています。
例えばファイルに対しては fstream
クラスと filebuf
クラスがペアになります。
ソケットの入出力のストリームクラス, streambuf
クラスを作りました。
作ったソースコードはこちら; network · master · netsphere / my-cpp-lib · GitLab
TLS 版については, HTTP/2 のページに掲載しています。HTTP/2 クライアント実装サンプル (TLS版)
仕様
全体的に, なんでこんな仕様になってるの? というところが多い。
書式化は iostream
のほうでするのに, ロケールを streambuf
のほうに持たせるのは何でだ? streambuf
がえらく複雑になっている。
ストリームクラスが streambuf
を持つ、というのがチグハグと思う。
バッファリング
読み込みバッファと書き込みバッファを別に設定できる。読み込みバッファは protected setg()
で, 書き込みバッファは protected setp()
で設定する。(g は get のg, p は put のp. 略さないでほしい...)
これらを呼び出さなければ, バッファリングしない. バッファリングしない場合, public sungetc()
, public sputbackc()
が失敗する.
読み込み
public sgetn()
が固定長の読み込み. 読めた分だけ返すのではなく、指定のバイト数に達するまでブロックする、となっている。::recv()
と挙動が違う。recv()
のつもりで呼び出すと, スタックしてしまう。
行末まで読みたい、など、どれだけ読めばいいか事前に分からないときは, sgetn()
は使えない. そうすると, 1文字ずつ読むしかなく、バッファリングが必須.
実装は, protected xsgetn()
に投げてる. そこから uflow()
で行っている。uflow()
は単に underflow()
に投げてる.
basic_streambuf<>::underflow()
{ return traits_type::eof(); }
このメソッドだけを override すれば, とりあえず動くようになる。
エラーハンドリング
underflow()
は, 高々1バイトを読み込む。エラーが起こったときの通知方法がない。握りつぶすよりかは, とりあえず例外でも投げるか。
書き込み側, xsputn()
も, エラーが起こったときの通知ができない。こちらも, とりあえず例外にするか。
何でこんなことになっているのか.
ヘッダファイル (実装)
やや長くなるが、全文を示す。
socket_streambuf.h
:
C++
- #include <ios>
- #include <assert.h>
- #include <algorithm>
- #include <string.h>
- #ifdef _WIN32
- #define STRICT
- #define WIN32_LEAN_AND_MEAN
- #include <winsock2.h>
- #include <windows.h>
- #define SHUT_RDWR SD_BOTH
- #else
- #include <sys/socket.h>
- #include <unistd.h>
- typedef int SOCKET;
- typedef unsigned char BYTE;
- #define INVALID_SOCKET -1
- #endif
-
-
-
-
- class SocketStreamBuf: public std::streambuf
- {
-
-
-
-
-
- char* m_buf;
-
- #define max_headroom ((ssize_t) 16)
- static constexpr long bufsiz = 4096 + max_headroom;
-
- protected:
-
- SOCKET m_sock;
-
- public:
- SocketStreamBuf(): m_buf(nullptr), m_sock(INVALID_SOCKET) { }
-
- virtual ~SocketStreamBuf() {
- close();
- if (m_buf) {
- free(m_buf);
- m_buf = nullptr;
- }
- }
-
-
-
- virtual void connected( SOCKET sock, const char* host ) {
- assert( sock != INVALID_SOCKET );
- assert( host && *host );
- assert( m_sock == INVALID_SOCKET );
-
- m_sock = sock;
- if (!m_buf)
- m_buf = (char*) malloc(bufsiz);
- setg(m_buf, m_buf, m_buf);
- }
-
-
-
- virtual int close() {
- if (m_sock == INVALID_SOCKET )
- return 0;
-
-
-
-
-
-
-
- ::shutdown(m_sock, SHUT_RDWR);
-
- int r;
- #ifdef _WIN32
- r = ::closesocket(m_sock);
- #else
- r = ::close(m_sock);
- #endif
- m_sock = INVALID_SOCKET;
- return r;
- }
-
-
- bool is_open() const throw() {
- return m_sock != INVALID_SOCKET;
- }
-
-
- protected:
-
-
-
- virtual ssize_t sysread(void* buffer, size_t length) {
- ssize_t ret;
- do {
- ret = ::recv(m_sock, (char*) buffer, length, 0);
- } while (ret == -1 && errno == EINTR);
- return ret;
- }
-
-
-
- virtual int_type underflow() {
- if ( !is_open() )
- return traits_type::eof();
-
-
- if (!gptr() || gptr() >= egptr()) {
-
-
- long rest = std::min(egptr() - eback(), max_headroom);
- if (rest > 0)
- memmove(m_buf, gptr() - rest, rest);
- ssize_t r = this->sysread(m_buf + rest, bufsiz - rest);
- if (r == 0)
- return traits_type::eof();
- else if (r < 0)
- throw errno;
- setg(m_buf, m_buf + rest, m_buf + rest + r);
- }
-
- return *gptr();
- }
上述のとおり underflow()
だけを override すればとりあえず動くが、バッファリングして, sysread()
でドカンと読み込むようにする。
C++
-
-
-
-
-
-
-
-
- virtual int_type overflow(int_type __c = traits_type::eof() )
- {
- if ( traits_type::eq_int_type(__c, traits_type::eof()) )
- return __c;
- char cc = __c;
- if ( xsputn(&cc, 1) <= 0 )
- return traits_type::eof();
-
- return 1;
- }
-
-
-
-
-
-
- virtual std::streamsize xsputn( const char_type* s,
- std::streamsize count )
- {
- if ( !is_open() )
- return 0;
- ssize_t r = ::send(m_sock, s, count, 0 );
- if (r >= 0)
- return r;
- else
- throw errno;
- }
- };