[C ++] apply the standard Io library to sockets

Source: Internet
Author: User

I am writing a network recentlyProgram, You need to write the string into the socket line by line, and then read it from the socket line by line. Because there are no ready-made functions to operate sockets in the form of rows, you can only write such a function yourself. I suddenly thought that the C ++ standard I/O library could easily operate the input/output stream. I wonder if I could extend it and apply it to the socket? The answer is yes. The standard library itself is a convenient and scalable library. In the C ++ standard library, I explain in detail how to expand it. The following content is based on the guidance of this book. It is mainly my own understanding of it, so it is very similar to the book.

 

First, let's take a look at the class architecture of IO in the Standard Library:

 

In addition to ios_base, other classes are defined as templates, because C ++ has two character types: Char and wchar_t. Ios_base defines attributes and operations unrelated to the same character type, while basic_ios defines attributes and operations related to the same character type. basic_istream and basic_ostream define operations related to the same input and output, respectively, basic_iostream supports both input and output.

 

In the entire class architecture, the most important thing is basic_streambuf, which provides the buffer function and truly operates on external devices. Other classes are only responsible for string formatting. This reflects the design principle of "separation of duties". basic_streambuf is loosely coupled with other classes. Modifications to one of these classes will not affect the other. Therefore, we only need to inherit basic_streambuf, define a class that uses Sockets for Io operations.

 

Basic_streambuf is a template, and the IO library defines two classes based on it (the actual definition statement is not like this, And the template parameter is not just one, here just for convenience ):

Typedef basic_streambuf <char> streambuf; typedef basic_streambuf <wchar_t> wstreambuf;

 

We can choose to inherit streambuf or wstreambuf based on the actual type of characters. Of course, you can also define your class as a template to inherit from basic_streambuf. However, you need to write moreCodeFor specific operations, see C ++ standard library. The example in this article inherits streambuf directly.

 

Basic_streambuf defines both output-related operations and input-related operations, which means that it supports both input and output. We can also only implement output or input so that it only supports some operation. First, let's look at how to implement the output.

 

Streambuf for output

The output operations in basic_streambuf mainly include sputc and sputn. The former outputs one character, and the latter outputs multiple characters. If a buffer zone is provided, sputc copies the characters to the buffer zone. If the buffer zone is full or the buffer zone is not provided, sputc calls overflow to write data to an external device and clear the buffer zone. Sputn calls xsputn, and the default operation of xsputn is to call sputc for each character. It can be seen that the implementation of output is very simple, as long as the overflow method is rewritten. You can also rewrite the xsputn method to optimize the output of multiple characters.

 

No Buffer

The following is an implementation method that does not use a buffer:

 
# Include <streambuf> # include <winsock2.h> class socketoutstreambuf: public STD: streambuf {public: socketoutstreambuf (socket 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 ;};

 

It can be seen that the implementation of the unbuffered mode is very simple, as long as the parameter is directly written into the socket, if the write is successful, the character just written is returned; if the write fails, the EOF, you can also throw an exception. This is up to you. Int_type is the type defined in the character feature class (traits), which indicates the type that can accommodate all characters. This type must not be char or wchar_t, because EOF and Weof are out of the range of these types.

 

Buffer Mode

Writing characters to the socket one by one is very inefficient. Therefore, we hope that socketoutstreambuf can provide the buffer function. The following describes how to implement the buffer method:

 
# Include <streambuf> # include <winsock2.h> class socketoutstreambuf: public STD: streambuf {public: socketoutstreambuf (socket 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] ;};

 

First, we need to define a buffer, and then use the setp method in the constructor to tell basic_streambuf the header and tail pointer of the buffer, so that the buffer function is available. There are three methods to obtain the buffer-related pointers: pbase, pptr, and epptr. they obtain the buffer header pointers respectively, the pointer to the current write position and the pointer to the next position at the end of the buffer, as shown in:

 

When pptr ()! = Epptr (), the buffer is not full. In this case, sputc only copies the characters to the position of pptr, and then moves pptr to the next position without calling overflow. When pptr () = epptr () indicates that the buffer zone is full. In this case, sputc calls overflow and uses the characters not in the buffer zone as the overflow parameter. In the constructor of the code above, the last position of the buffer is used as the tail pointer (m_buffer + buffersize-1 is used as the second parameter, rather than m_buffer + buffersize ), this is because you can manually put the parameters in the last position in overflow and then send the data in the entire buffer zone together. The pbump method is used to move the pointer at the current write position. The parameter value is relative. After sending the data, you need to use pbump to move the pointer back to the buffer header.

 

In addition, if the buffer function is provided, you also need to rewrite the Sync method. This method is used to synchronize the data in the buffer zone with the external device, that is, write the data in the buffer zone to the external device, whether it is full or not. If the method is successful, 0 is returned; otherwise,-1 is returned. In the destructor, you must also call sync to ensure that data is written to an external device.

 

Use custom output streambuf

After defining our own socketoutstreambuf, you only need to combine it with ostream to use the powerful functions of the IO library on the socket, as shown below:

Socket socket ;... Socketoutstreambuf outbuf (socket); STD: ostream outstream (& outbuf); STD: String line; while (STD: Getline (STD: Cin, line )) {outstream <line <STD: Endl ;}

The above code is used to write the input on the console to the socket.

 

Streambuf used for Input

The input operations in basic_streambuf include sgetc, sbumpc, sgetn, sungetc, and sputbackc. Here, sungetc and sputbackc are used for character rollback. This function is not often used and is unlikely to be used for character rollback on the socket. Therefore, we omit the introduction of the character rollback, for more information, see C ++ standard library.

 

Both sgetc and sbumpc are used to read a character. The difference is that the latter will move the reading position backward, but the former will not change the reading position. If no buffer is provided or the buffer content has been read, sgetc will call the underflow method, while sbumpc will call the uflow method to read more data from external devices. By default, uflow calls underflow and moves the read pointer of the buffer zone. If the buffer zone is not provided, underflow and uflow must be overwritten at the same time. Sgetn is used to read multiple characters. It calls xsgetn, and the default behavior of xsgetn is to call sbumpc in sequence. To improve the performance of Reading multiple characters, you can override the xsgetn method.

 

No Buffer

First, let's take a look at the input implementation without buffering, as shown below:

 
# Include <streambuf> # include <winsock2.h> class socketinstreambuf: public STD: streambuf {public: socketinstreambuf (socket 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 ;};

 

The underflow and uflow must be rewritten for the implementation without buffering. According to the definitions of the two methods, the former does not move the read location, and the latter does not, the msg_peek option of the Recv function corresponds to the two actions.

 

Buffer Mode

Reading characters one by one from the socket is also very inefficient. Adding the buffer function is a natural problem, as shown below:

# Include <streambuf> # include <winsock2.h> class socketinstreambuf: public STD: streambuf {public: socketinstreambuf (socket 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];};

 

Like the output implementation, we also need to define a buffer, and then set the buffer pointer using the setg method. Unlike setp, The setg method requires three pointers, namely the buffer header pointer, the current read position pointer, And the next position pointer at the end of the buffer. These pointers can be passed through eback (), obtain the gptr () and egptr () methods. This is more complex than the output buffer because the input buffer must support the rollback function. The following figure shows the input buffer:

 

When reading characters, gptr moves to the right until gptr () = egptr (), underflow is called to supplement data from external devices. When the character is rolled back, gptr moves to the left until gptr () = gback (), the character cannot be rolled back.

 

In the constructor of the code above, setg is used to set all three pointers to the buffer header. In this way, rollback is not supported, and the first read will cause underflow to be called. In underflow, you need to call setg to reset the buffer pointer after reading data to the buffer zone. Because gptr () = eback () is used, rollback is still not supported.

 

As mentioned above, if a buffer zone is provided, uflow does not need to be rewritten. Therefore, socketinstreambuf, which provides the buffer function, looks easier than without the buffer function.

 

Use custom input streambuf

Just like the output, as long as socketinstreambuf and istream are combined, powerful Io functions can be used:

 
Socket socket ;... Socketinstreambuf inbuf (socket); STD: istream socketstream (& inbuf); STD: String line; while (STD: Getline (socketstream, line) {STD :: cout <line <STD: Endl ;}

the above Code reads data from the socket and then outputs the data to the console.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.