程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++ I/O 重定向方法(定向到串口或Socket)

C++ I/O 重定向方法(定向到串口或Socket)

編輯:C++入門知識

 

首先來看一下標准庫中有關IO的類體系結構:

\

 

除了ios_base之外,其它類都定義為模板,這是因為C++中有兩種字符類型:char和wchar_t。ios_base定義了同字符類型無關的屬性和操作,basic_ios則定義了同字符類型相關的屬性和操作,basic_istream和basic_ostream分別定義了同輸入和輸出相關的操作,basic_iostream同時支持輸入和輸出。

 

在整個類體系結構中,最重要的的是basic_streambuf,它提供了緩沖功能以及真正地操作外部設備,其它類則只負責字符串的格式化操作。這體現了“職責分離”的設計原則,basic_streambuf和其它類之間是松耦合關系,對其中一方進行修改不會影響到另一方,因此,我們只需要繼承basic_streambuf,定義出一個使用套接字進行IO操作的類即可。

 

basic_streambuf是一個模板,IO庫根據它分別定義了兩個類(真正的定義語句並不是這樣的,模板參數不僅僅是一個,這裡只是為了方便說明):

? 1 2 typedef basic_streambuf<char> streambuf; typedef basic_streambuf<wchar_t> wstreambuf;

 

我們可以根據字符的實際類型選擇繼承streambuf或wstreambuf。當然,也可以將自己的類定義為模板,繼承basic_streambuf,不過這樣的話需要多寫一些代碼,具體操作可以參考《C++標准程序庫》,本文的例子直接繼承streambuf。

 

basic_streambuf既定義了輸出相關操作,也定義了輸入相關操作,這意味它同時支持輸入和輸出。我們也可以只實現輸出或者輸入,讓它只支持某種操作。首先來看下如何實現輸出。

 

用於輸出的streambuf

basic_streambuf中輸出相關的操作主要有sputc和sputn,前者輸出一個字符,後者輸出多個字符。如果提供了緩沖區,那麼sputc將字符復制到緩沖區內,如果緩沖區已經滿了或者沒有提供緩沖區,sputc會調用overflow,將數據寫入外部設備並清空緩沖區。sputn會調用xsputn,而xsputn的默認操作是對每個字符調用sputc。由此可見,實現輸出要做的事情很簡單,只要重寫overflow方法即可。另外也可以重寫xsputn方法,以優化多個字符的輸出。

 

無緩沖方式

下面是不使用緩沖區的實現方式:

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include #include class SocketOutStreamBuf : public std::streambuf { public: SocketOutStreamBuf(SOCKET socket) : m_socket(socket) { } protected: int_type overflow(int_type c) { if (c != EOF) { if (send(m_socket, (char*)&c, 1, 0) <= 0) { return EOF; } } return c; } private: SOCKET m_socket; };

 

可以看到,無緩沖方式的實現非常簡單,只要將參數直接寫入到套接字中就可以了,如果寫入成功,返回剛寫入的那個字符;如果失敗,返回EOF,也可以拋出異常——這個由你決定。int_type是在字符特性類(traits)中定義的類型,表示能容納所有字符的類型,這個類型肯定不是char或wchar_t,因為EOF和WEOF超出了這些類型的范圍。

 

有緩沖方式

把字符一個一個地寫入套接字是非常低效的,因此我們希望SocketOutStreamBuf能提供緩沖功能,有緩沖方式的實現如下所示:

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include #include class SocketOutStreamBuf : public std::streambuf { public: SocketOutStreamBuf(SOCKET socket) : m_socket(socket) { setp(m_buffer, m_buffer + BufferSize - 1); } ~SocketOutStreamBuf() { sync(); } protected: int_type overflow(int_type c) { if (c != EOF) { *pptr() = c; pbump(1); } if (FlushBuffer() == EOF) { return EOF; } return c; } int sync() { if (FlushBuffer() == EOF) { return -1; } return 0; } private: int FlushBuffer() { int len = pptr() - pbase(); if (send(m_socket, m_buffer, len, 0) <= 0) { return EOF; } pbump(-len); return len; } SOCKET m_socket; static const int BufferSize = 512; char m_buffer[BufferSize]; };

 

首先我們需要自己定義一個緩沖區,然後在構造方法中通過setp方法把緩沖區的頭尾指針告訴basic_streambuf,這樣一來就具有了緩沖功能。有三個方法可以獲取與緩沖區相關的指針:pbase,pptr和epptr,它們分別獲取的是緩沖區的頭指針,當前寫入位置的指針以及緩沖區尾部下一個位置的指針,如下圖所示:

\

 

當pptr() != epptr()時,緩沖區是未滿的,此時sputc只是把字符復制到pptr所在位置,然後把pptr移動到下一個位置,不會調用overflow;當pptr() == epptr()時,緩沖區是滿的,此時sputc會調用overflow,並把放不進緩沖區內的字符作為overflow的參數。在上面代碼的構造方法中,之所以把緩沖區的最後一個位置作為尾指針(用m_buffer + BufferSize - 1作為第二個參數,而不是m_buffer + BufferSize),是因為這樣可以在overflow中手動將參數放到最後一個位置,然後將整個緩沖區的數據一起發送出去。pbump方法用來移動當前寫入位置的指針,參數的值是相對位置,在發送完數據之後需要用pbump將指針移回到緩沖區頭部。

 

另外,提供了緩沖功能的話還需要重寫sync方法,該方法用於同步緩沖區同外部設備的數據,意思就是將緩沖區的數據寫入到外部設備中,不管它有沒有滿。如果該方法成功的話, 返回0,否則返回-1。在析構方法中也要調用sync,確保數據被寫入到外部設備中。

 

使用自定義的輸出streambuf

定義好了我們自己的SocketOutStreamBuf之後,只要將它與ostream組合在一起就能在套接字上使用IO庫的強大功能,如下所示:

? 1 2 3 4 5 6 7 8 9 SOCKET socket; SocketOutStreamBuf outBuf(socket); std::ostream outStream(&outBuf); std::string line; while (std::getline(std::cin, line)) { outStream << line << std::endl; }

上面的代碼用於將控制台上的輸入寫入到套接字中。

 

用於輸入的streambuf

basic_streambuf中輸入相關的操作有sgetc,sbumpc,sgetn,sungetc和sputbackc。其中sungetc和sputbackc用於回退字符,這個功能不常用到,而且也不太可能在套接字上回退字符,因此這裡省略對回退字符的介紹,關於這方面的內容可以參考《C++標准程序庫》。

 

sgetc和sbumpc都用於讀取一個字符,區別是後者會將讀取位置向後移動一個位置,而前者不會改變讀取位置。如果沒有提供緩沖區,或者緩沖區的內容已經讀完,那麼sgetc會調用underflow方法,而sbumpc會調用uflow方法,從外部設備讀取更多數據。uflow的默認行為是調用underflow,然後移動緩沖區的讀取指針,如果沒有提供緩沖區,則必須同時重寫underflow和uflow。sgetn用於讀取多個字符,它會調用xsgetn,而xsgetn的默認行為是依次調用sbumpc,如果為了改善讀取多個字符的性能,可以重寫xsgetn方法。

 

basic_streambuf的源碼中:

 

  1. int_type
  2. sputc(char_type __c)
  3. {
  4. int_type __ret;
  5. if (__builtin_expect(this->pptr() < this->epptr(), true))
  6. {
  7. *this->pptr() = __c;
  8. this->pbump(1);
  9. __ret = traits_type::to_int_type(__c);
  10. }
  11. else
  12. __ret = this->overflow(traits_type::to_int_type(__c));
  13. return __ret;
  14. }
  15.  
  16. /**
  17. * @brief Multiple character insertion.
  18. * @param s A buffer area.
  19. * @param n Maximum number of characters to write.
  20. * @return The number of characters written.
  21. *
  22. * Writes @a s[0] through @a s[n-1] to the output sequence, as if
  23. * by @c sputc(). Stops when either @a n characters have been
  24. * copied, or when @c sputc() would return @c traits::eof().
  25. *
  26. * It is expected that derived classes provide a more efficient
  27. * implementation by overriding this definition.
  28. */
  29. virtual streamsize
  30. xsputn(const char_type* __s, streamsize __n);
  31.  
  32.  
  33. /**
  34. * @brief Consumes data from the buffer; writes to the
  35. * controlled sequence.
  36. * @param c An additional character to consume.
  37. * @return eof() to indicate failure, something else (usually
  38. * @a c, or not_eof())
  39. *
  40. * Informally, this function is called when the output buffer
  41. * is full (or does not exist, as buffering need not actually
  42. * be done). If a buffer exists, it is @a consumed, with
  43. * some effect on the controlled sequence.
  44. * (Typically, the buffer is written out to the sequence
  45. * verbatim.) In either case, the character @a c is also
  46. * written out, if @a c is not @c eof().
  47. *
  48. * For a formal definition of this function, see a good text
  49. * such as Langer & Kreft, or [27.5.2.4.5]/3-7.
  50. *
  51. * A functioning output streambuf can be created by overriding only
  52. * this function (no buffer area will be used).
  53. *
  54. * @note Base class version does nothing, returns eof().
  55. */
  56. virtual int_type
  57. overflow(int_type /* __c */ = traits_type::eof())
  58. { return traits_type::eof(); }
  59.  
  60.  
  61.  
  62.  
  63. /**
  64. * @brief Synchronizes the buffer arrays with the controlled sequences.
  65. * @return -1 on failure.
  66. *
  67. * Each derived class provides its own appropriate behavior,
  68. * including the definition of @a failure.
  69. * @note Base class version does nothing, returns zero.
  70. */
  71. virtual int
  72. sync() { return 0; }
  73.  
  74.  
  75. /**
  76. * @brief Entry point for all single-character output functions.
  77. * @param s A buffer read area.
  78. * @param n A count.
  79. *
  80. * One of two public output functions.
  81. *
  82. *
  83. * Returns xsputn(s,n). The effect is to write @a s[0] through
  84. * @a s[n-1] to the output sequence, if possible.
  85. */
  86. streamsize
  87. sputn(const char_type* __s, streamsize __n)
  88. { return this->xsputn(__s, __n); }
  89.  
  90.  
  91.  
  92. /**
  93. * @brief Synchronizes the buffer arrays with the controlled sequences.
  94. * @return -1 on failure.
  95. *
  96. * Each derived class provides its own appropriate behavior,
  97. * including the definition of @a failure.
  98. * @note Base class version does nothing, returns zero.
  99. */
  100. virtual int
  101. sync() { return 0; }

     

    無緩沖方式

    首先來看下無緩沖方式的輸入實現,如下所示:

    ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include #include class SocketInStreamBuf : public std::streambuf { public: SocketInStreamBuf(SOCKET socket) : m_socket(socket) { } int_type underflow() { char c; if (recv(m_socket, &c, 1, MSG_PEEK) <= 0) { return EOF; } return c; } int_type uflow() { char c; if (recv(m_socket, &c, 1, 0) <= 0) { return EOF; } return c; } private: SOCKET m_socket; };

     

    無緩沖的實現需要同時重寫underflow和uflow,根據這兩個方法的定義,前者不移動讀取位置,後者反之,而recv函數的MSG_PEEK選項剛好可以對應這兩種行為。

     

    有緩沖方式

    從套接字逐個讀取字符也是非常低效的過程,添加緩沖功能是再自然不過的事情,如下所示:

    ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include #include class SocketInStreamBuf : public std::streambuf { public: SocketInStreamBuf(SOCKET socket) : m_socket(socket) { setg(m_buffer, m_buffer, m_buffer); } int_type underflow() { int recvLen = recv(m_socket, m_buffer, BufferSize, 0); if (recvLen <= 0) { return EOF; } setg(m_buffer, m_buffer, m_buffer + recvLen); return *gptr(); } private: SOCKET m_socket; static const int BufferSize = 512; char m_buffer[BufferSize]; };

     

    跟輸出的實現一樣,我們也需要自己定義一個緩沖區,然後用setg方法設置緩沖區的指針。與setp不同,setg方法需要設置三個指針,分別是緩沖區頭指針,當前讀取位置指針以及緩沖區尾部下一個位置指針,這些指針可通過eback(),gptr(),egptr()方法獲取。這比輸出緩沖區復雜,因為輸入緩沖區需要支持回退功能。輸入緩沖區圖示如下:

    \

     

    當讀取字符時,gptr向右移動,直到gptr() == egptr()時,調用underflow從外部設備補充數據。當回退字符時,gptr向左移動,直到gptr() == gback()時,就不能再回退字符了。

     

    在上面代碼的構造方法中,用setg把三個指針都設置到緩沖區頭部,這樣一來,就不支持回退了,而且第一次讀取會導致underflow被調用。在underflow中,將數據讀取到緩沖區之後還要調用setg重新設置一下緩沖區指針,由於是gptr() == eback(),所以仍然不支持回退。

     

    上文說過,如果提供了緩沖區,那麼就不需要重寫uflow了,所以提供了緩沖功能的SocketInStreamBuf看上去比無緩沖功能的還要簡單。

     

    使用自定義的輸入streambuf

    跟輸出的一樣,只要將SocketInStreamBuf與istream組合在一起,就可以利用強大的IO功能了:

    ? 1 2 3 4 5 6 7 8 9 SOCKET socket; SocketInStreamBuf inBuf(socket); std::istream socketStream(&inBuf); std::string line; while (std::getline(socketStream, line)) { std::cout << line << std::endl; }

    上面的代碼從套接字讀取數據,然後輸出到控制台上。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved