C++ iostream 実装例 その 3
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
検索
|
最終更新
|
ヘルプ
]
開始行:
[[C++のiostream]]
* C++ iostream 実装例 その 3 [#z4058b31]
前節までは、効率を無視して、1 octet 入出力だけで TCP/IP ...
行ってみた。 さすがに 1 octet 入出力のたびに send()/recv(...
実行するのは、あまりにも非効率だろう。
そこで、ここでは、効率化を考えてみよう。
最初に述べたように、iostream や streambuf は実装が極めて...
設計されている。それも buffering することを基本にしてある。
前節に示した実装例は、むしろ邪道に近い。
ここでは buffering を使った具体例を示す。
ここで例と、前の例を比べてみれば、書き方も code size も
ほとんど同じということがわかるだろう。つまり、1文字入出力と
同程度の簡単さで実装できる。
** 修正 2010-01-13 [#wfb75b9c]
- sync() の後、setp() を呼び出す必要がある。これを加えた。
** 内部 buffer と基底 class [#la48b4da]
send()/recv() call を減らすという意味では、内部 buffer を...
実際、"[[C++ iostream 実装例 その 1]]" の実装でも underf...
ため、1 octet の buffer を持っていると見なせる(m_gpend_c...
これを増やせばよい。
内部 buffer を持つことで、ある程度の効率化はもちろんでき...
それだけでは underflow()/uflow()/overflow()
の呼び出し数を減らすことはできない。ここまでの実装では
基底 class である streambuf 側で、どこに buffer があり、
どれだけ buffer に data が準備できているかなどが、わから...
基底 class に buffer の開始位置や data の準備のできている...
手段が用意されている。この手段を使って、基底 class が必要と
する pointer を知らせることにより、入力 buffer が
空になる、または出力 buffer が一杯になるまでは、underflow...
はよばれずに、streambuf 基底 class の方で処理が行われる。
(私の[[C++のiostream]]ページに示してある実装資料参照)。
また、派生 class の方では buffer を詰めたり、出力したりす...
基底 class がどこまで buffer を使っているのか知る必要も出...
そのための関数も用意されている。
** 使われる buffer pointer と member 関数[#ne98476c]
Buffer pointer は、読み出し(get)側、書き込み(put)側、...
開始位置、現在位置、終端位置の3つが使われる。
派生 class からstreambuf class に対し読み出し buffer の各...
関数が
void streambuf::setg(char* gbeg, char* gnext, char* gend);
である。引数は左からそれぞれ開始位置、次に読み込まれる位...
である(実は開始位置というのは正しくない。後で述べる)。
終端位置とは有効 data のある次の位置である。つまり、gbeg ...
n 文字有効 data が詰められているとすると、
gend = gbeg + n;
である。
書き込み buffer に対しては
void streambuf::setp(char* pbeg, char* pend);
が用意されている。引数は左からそれぞれ開始位置、終端位置...
次に書き込まれる位置になる。終端位置は、この pointer の一...
までが書き込み可能であることを示す。
一方、streambuf が現在、各 pointer に対し、どのような値を...
示す関数も用意されている。
char* streambuf::eback() const;
char* streambuf::gptr() const;
char* streambuf::egptr() const;
は、それぞれ、読み出し buffer の開始位置、現在(次に読み...
終端位置である。実は最初の関数を読み出し buffer の開始位...
正しくなくて(実際名前が bgptr()とか gbase() とかにはなっ...
backup 列の終端位置である(だから eback())。
同様の機能を果たす書き込み buffer に対する関数は
char* streambuf::pbase() const;
char* streambuf::pptr() const;
char* streambuf::epptr() const;
である。現在位置(次に読み込まれる位置や次に書き込める位...
操作する関数も用意されている。
void streambuf::gbump(int n);
void streambuf::pbump(int n);
である。ここで n は負であってもよい。
** backup 列 [#tb9d8266]
さて、上で読み出し buffer の先頭位置は開始位置というのは
正しくないと述べた。ここでは、その理由を説明する。
gptr() が buffer の先頭位置より大きい時、buffer の先頭
位置から gptr() の一つ前までは、今まで読み込まれ、ユーザ...
渡された文字である。この部分の範囲はまだ有効文字として残...
gptr() を設定することが可能である。つまり、元に戻すことが
可能である。これを backup 列と呼ぶ。buffer の先頭位置は、
この backup 可能な最後の位置なので eback() というわけであ...
この eback() に至るまでは、streambuf 側で sputbackc() や ...
処理される。eback() を越えてこれらが呼ばれた時には、pback...
呼ばれる。従って、もし double-buffering などを行っていて...
ことが可能ならば、pbackfail() が呼ばれた時に setg() を使...
使った設定に戻すことで、対応可能となる。
また、underflow() の時に、前の data の最後の何文字分かを...
ようにすることも可能であろう。いずれにせよ、十分な自由度...
** header (宣言) [#sd090138]
// tcpbuf.h
#ifndef TCPBUFFER_H_INCLUDED
#define TCPBUFFER_H_INCLUDED
#include <streambuf>
namespace mynetlibrary
{
class tcpbuffer :
public std::streambuf
{
public:
tcpbuffer(int sock);
virtual ~tcpbuffer(void);
int state(void) const;
protected:
virtual int underflow(void);
virtual int overflow(int c = std::char_traits<char>::eo...
virtual int sync(void);
protected:
static const int MY_GBUFSIZE = 1024;
static const int MY_PBUFSIZE = 1024;
protected:
int m_socket;
unsigned char m_gbuf[(MY_GBUFSIZE + 1)];
unsigned char m_pbuf[(MY_PBUFSIZE + 1)];
};
} // namespace mynetlibrary
#endif // TCPBUFFER_H_INCLUDED
ここで、m_gbuf や m_pbuf は C の string でもないのに、わ...
一つ余分に確保しているか疑問に思うかも知れない(実際、使...
これは、buffer の終端を示す pointer が、正しい pointer と
なることを保証するためにこうしている。
unsigned char m_gbuf[MY_GBUFSIZE];
としてしまった時、setg() などで行っている
(char*)(m_gbuf + MY_GBUFSIZE)
が、正しい pointer になる保証はない。例えば MY_GBUFSIZE ...
(char*)(m_gbuf + MY_GBUFSIZE -1)
が pointer として表せる限界一杯だったということだってあり...
この時、
(char*)(m_gbuf + MY_GBUFSIZE)
は、pointer の格納 size を overflow して、とんでもない値が
入るだろう。その後、基底 class がこれを使って計算して acc...
時に、何が起こるか予測できない(まあ、access violation で...
というのが一番ありそうではあるが)。しかも、これは compil...
error にならず、実行してもたまたま偶然、そういう memory ...
だけ起こる現象なので、発見は非常に困難である。もちろん、...
なることは、滅多に無いというか、まず無いだろう。しかし、
原因究明を困難にする bug を引き起こす、そんな risk を少し...
なら1文字分余計に確保して(計算機の資源 cost に負わせて)...
いうのが筆者の考え方である。
** 本体(定義) [#r783147a]
// tcpbuf.cpp
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "tcpbuf.h"
namespace mynetlibrary
{
tcpbuffer::tcpbuffer(int sock)
{
m_socket = ::dup(sock);
setg((char*)m_gbuf, (char*)m_gbuf, (char*)m_gbuf);
setp((char*)m_pbuf, (char*)(m_pbuf + MY_PBUFSIZE));
}
tcpbuffer::~tcpbuffer(void)
{
if(m_socket != -1)
::close(m_socket);
}
int
tcpbuffer::state(void) const
{
if(m_socket == -1)
return -1;
return 0;
}
int
tcpbuffer::underflow(void)
{
if(m_socket == -1)
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;
}
int n;
if((n = ::recv(m_socket, m_gbuf, MY_GBUFSIZE, 0)) <= 0)
return std::char_traits<char>::eof();
setg((char*)m_gbuf, (char*)m_gbuf, (char*)(m_gbuf + n));
c = (int)m_gbuf[0]; c &= 255;
return c;
}
int
tcpbuffer::overflow(int c)
{
if(m_socket == -1)
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_PBUFS...
if(c == std::char_traits<char>::eof())
return 0;
*pptr() = (char)(c & 255);
pbump(1);
return c;
}
int
tcpbuffer::sync(void)
{
if(m_socket == -1)
return (-1);
int nleft = (int)(pptr() - (char*)m_pbuf);
int nsend;
unsigned 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
終了行:
[[C++のiostream]]
* C++ iostream 実装例 その 3 [#z4058b31]
前節までは、効率を無視して、1 octet 入出力だけで TCP/IP ...
行ってみた。 さすがに 1 octet 入出力のたびに send()/recv(...
実行するのは、あまりにも非効率だろう。
そこで、ここでは、効率化を考えてみよう。
最初に述べたように、iostream や streambuf は実装が極めて...
設計されている。それも buffering することを基本にしてある。
前節に示した実装例は、むしろ邪道に近い。
ここでは buffering を使った具体例を示す。
ここで例と、前の例を比べてみれば、書き方も code size も
ほとんど同じということがわかるだろう。つまり、1文字入出力と
同程度の簡単さで実装できる。
** 修正 2010-01-13 [#wfb75b9c]
- sync() の後、setp() を呼び出す必要がある。これを加えた。
** 内部 buffer と基底 class [#la48b4da]
send()/recv() call を減らすという意味では、内部 buffer を...
実際、"[[C++ iostream 実装例 その 1]]" の実装でも underf...
ため、1 octet の buffer を持っていると見なせる(m_gpend_c...
これを増やせばよい。
内部 buffer を持つことで、ある程度の効率化はもちろんでき...
それだけでは underflow()/uflow()/overflow()
の呼び出し数を減らすことはできない。ここまでの実装では
基底 class である streambuf 側で、どこに buffer があり、
どれだけ buffer に data が準備できているかなどが、わから...
基底 class に buffer の開始位置や data の準備のできている...
手段が用意されている。この手段を使って、基底 class が必要と
する pointer を知らせることにより、入力 buffer が
空になる、または出力 buffer が一杯になるまでは、underflow...
はよばれずに、streambuf 基底 class の方で処理が行われる。
(私の[[C++のiostream]]ページに示してある実装資料参照)。
また、派生 class の方では buffer を詰めたり、出力したりす...
基底 class がどこまで buffer を使っているのか知る必要も出...
そのための関数も用意されている。
** 使われる buffer pointer と member 関数[#ne98476c]
Buffer pointer は、読み出し(get)側、書き込み(put)側、...
開始位置、現在位置、終端位置の3つが使われる。
派生 class からstreambuf class に対し読み出し buffer の各...
関数が
void streambuf::setg(char* gbeg, char* gnext, char* gend);
である。引数は左からそれぞれ開始位置、次に読み込まれる位...
である(実は開始位置というのは正しくない。後で述べる)。
終端位置とは有効 data のある次の位置である。つまり、gbeg ...
n 文字有効 data が詰められているとすると、
gend = gbeg + n;
である。
書き込み buffer に対しては
void streambuf::setp(char* pbeg, char* pend);
が用意されている。引数は左からそれぞれ開始位置、終端位置...
次に書き込まれる位置になる。終端位置は、この pointer の一...
までが書き込み可能であることを示す。
一方、streambuf が現在、各 pointer に対し、どのような値を...
示す関数も用意されている。
char* streambuf::eback() const;
char* streambuf::gptr() const;
char* streambuf::egptr() const;
は、それぞれ、読み出し buffer の開始位置、現在(次に読み...
終端位置である。実は最初の関数を読み出し buffer の開始位...
正しくなくて(実際名前が bgptr()とか gbase() とかにはなっ...
backup 列の終端位置である(だから eback())。
同様の機能を果たす書き込み buffer に対する関数は
char* streambuf::pbase() const;
char* streambuf::pptr() const;
char* streambuf::epptr() const;
である。現在位置(次に読み込まれる位置や次に書き込める位...
操作する関数も用意されている。
void streambuf::gbump(int n);
void streambuf::pbump(int n);
である。ここで n は負であってもよい。
** backup 列 [#tb9d8266]
さて、上で読み出し buffer の先頭位置は開始位置というのは
正しくないと述べた。ここでは、その理由を説明する。
gptr() が buffer の先頭位置より大きい時、buffer の先頭
位置から gptr() の一つ前までは、今まで読み込まれ、ユーザ...
渡された文字である。この部分の範囲はまだ有効文字として残...
gptr() を設定することが可能である。つまり、元に戻すことが
可能である。これを backup 列と呼ぶ。buffer の先頭位置は、
この backup 可能な最後の位置なので eback() というわけであ...
この eback() に至るまでは、streambuf 側で sputbackc() や ...
処理される。eback() を越えてこれらが呼ばれた時には、pback...
呼ばれる。従って、もし double-buffering などを行っていて...
ことが可能ならば、pbackfail() が呼ばれた時に setg() を使...
使った設定に戻すことで、対応可能となる。
また、underflow() の時に、前の data の最後の何文字分かを...
ようにすることも可能であろう。いずれにせよ、十分な自由度...
** header (宣言) [#sd090138]
// tcpbuf.h
#ifndef TCPBUFFER_H_INCLUDED
#define TCPBUFFER_H_INCLUDED
#include <streambuf>
namespace mynetlibrary
{
class tcpbuffer :
public std::streambuf
{
public:
tcpbuffer(int sock);
virtual ~tcpbuffer(void);
int state(void) const;
protected:
virtual int underflow(void);
virtual int overflow(int c = std::char_traits<char>::eo...
virtual int sync(void);
protected:
static const int MY_GBUFSIZE = 1024;
static const int MY_PBUFSIZE = 1024;
protected:
int m_socket;
unsigned char m_gbuf[(MY_GBUFSIZE + 1)];
unsigned char m_pbuf[(MY_PBUFSIZE + 1)];
};
} // namespace mynetlibrary
#endif // TCPBUFFER_H_INCLUDED
ここで、m_gbuf や m_pbuf は C の string でもないのに、わ...
一つ余分に確保しているか疑問に思うかも知れない(実際、使...
これは、buffer の終端を示す pointer が、正しい pointer と
なることを保証するためにこうしている。
unsigned char m_gbuf[MY_GBUFSIZE];
としてしまった時、setg() などで行っている
(char*)(m_gbuf + MY_GBUFSIZE)
が、正しい pointer になる保証はない。例えば MY_GBUFSIZE ...
(char*)(m_gbuf + MY_GBUFSIZE -1)
が pointer として表せる限界一杯だったということだってあり...
この時、
(char*)(m_gbuf + MY_GBUFSIZE)
は、pointer の格納 size を overflow して、とんでもない値が
入るだろう。その後、基底 class がこれを使って計算して acc...
時に、何が起こるか予測できない(まあ、access violation で...
というのが一番ありそうではあるが)。しかも、これは compil...
error にならず、実行してもたまたま偶然、そういう memory ...
だけ起こる現象なので、発見は非常に困難である。もちろん、...
なることは、滅多に無いというか、まず無いだろう。しかし、
原因究明を困難にする bug を引き起こす、そんな risk を少し...
なら1文字分余計に確保して(計算機の資源 cost に負わせて)...
いうのが筆者の考え方である。
** 本体(定義) [#r783147a]
// tcpbuf.cpp
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "tcpbuf.h"
namespace mynetlibrary
{
tcpbuffer::tcpbuffer(int sock)
{
m_socket = ::dup(sock);
setg((char*)m_gbuf, (char*)m_gbuf, (char*)m_gbuf);
setp((char*)m_pbuf, (char*)(m_pbuf + MY_PBUFSIZE));
}
tcpbuffer::~tcpbuffer(void)
{
if(m_socket != -1)
::close(m_socket);
}
int
tcpbuffer::state(void) const
{
if(m_socket == -1)
return -1;
return 0;
}
int
tcpbuffer::underflow(void)
{
if(m_socket == -1)
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;
}
int n;
if((n = ::recv(m_socket, m_gbuf, MY_GBUFSIZE, 0)) <= 0)
return std::char_traits<char>::eof();
setg((char*)m_gbuf, (char*)m_gbuf, (char*)(m_gbuf + n));
c = (int)m_gbuf[0]; c &= 255;
return c;
}
int
tcpbuffer::overflow(int c)
{
if(m_socket == -1)
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_PBUFS...
if(c == std::char_traits<char>::eof())
return 0;
*pptr() = (char)(c & 255);
pbump(1);
return c;
}
int
tcpbuffer::sync(void)
{
if(m_socket == -1)
return (-1);
int nleft = (int)(pptr() - (char*)m_pbuf);
int nsend;
unsigned 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
ページ名: