The core of the Socket Adapter is the RWSocket class: a thin stateless veneer over a socket. For each socket API function, there is a corresponding RWSocket member function. Here are some of the things we've done to make the RWSocket member functions more robust and easier to use than their Berkeley sockets counterparts:
Strong typing lets you catch errors at compile time.
Default arguments make common usage easier.
Errors are indicated using exceptions-you don't have to check returns codes.
Encapsulation and overloading coalesce multiple socket calls into one call. For example, you can set up a server with a single call to listen()-you don't have to call socket() and bind() first.
Socket Address Adapter classes simplify passing and returning socket addresses.
Here's a simple program that connects to a socket and sends it a line of output, without error handling.
#include <rw/rstream.h> #include <rw/toolpro/socket.h> //1 #include <rw/toolpro/inetaddr.h> //2 #include <rw/toolpro/winsock.h> //3 main() { RWWinSockInfo info; //4 RWSocket p; //5 p.connect( RWInetAddr(3010,"net.roguewave.com") ); //6 p.sendAtLeast("Hello out there!"); //7 p.closesocket(); //8 return 0; }
//1 | Include the header file rw/toolpro/socket.h. This includes the declarations needed for the RWSocket class. |
//2 | Include the Internet address header file, rw/toolpro/inetaddr.h. This declares the TCP/IP address classes, which are necessary to connect to Internet servers. |
//3 | The header file rw/toolpro/winsock.h includes declarations necessary for using the Winsock DLL. If you are using a Unix operating system, then this include statement does nothing, but increases code portability. |
//4 | If you are using a Winsock DLL, the DLL needs to be initialized before use and cleaned up at process termination. Defining an instance of the RWWinSockInfo class in a scope to contain the use of the net library, as shown here, performs this initialization and cleanup.
For applications running under Unix, this line does nothing. It is wise to include an RWWinSockInfo object where appropriate, in case your code ever needs to be ported to Windows. |
//5 | Define a socket object. The socket needs to be initialized using a call to one of the member functions socket(), bind(), connect(), or listen() before it can be used. |
//6 | Connect the socket to the Internet server on net.roguewave.com at port 3010. |
//7 | Send a message to the server. There are several member functions for sending data, depending on the format of the information, and whether or not it is acceptable to send only part of the data. The sendAtLeast() member function calls the underlying communications system's send() function multiple times, if necessary, to make sure that all data is sent. |
//8 | Close down the socket. This frees up any system resources used by the socket. If copies of the socket have been created using the copy constructor or assignment operator, those copies become invalid. If you try to use them, an exception is thrown. |
If you forget to close a socket using closesocket(), then its resources remain in use by the system. Resource leaks, like the memory leaks, can lead to subtle bugs. For example, examine the following code.
try { RWSocket s; s.connect( RWInetAddr(3010,"net.roguewave.com") ); s.sendAtLeast( "hi Bob" ); //1 s.closesocket(); //2 } catch (const RWxmsg& x) { //3 // Handle error here }
Suppose that the call to sendAtLeast on line //1 fails. An exception is thrown, and caught by the exception handler on line //3. The problem? The socket is never closed. Because of the exception, the call to closesocket() on line //2 is missed.
The problem could be fixed if the RWSocket class had used the "resource acquisition is initialization" technique described by Stroustrup in The C++ Programming Language. In this technique, freeing a resource-closing a socket-is done by a destructor. However, RWSocket is very low level, and as such wraps Berkeley sockets very thinly. We chose not to prohibit code like this:
RWSocket getSocket() { RWSocket s; . . // Connect the socket s . return s; //1 }
If the RWSocket destructor executed at line //1 were to close the socket, then the RWSocket returned by the function would not be of much use. Since RWSocket is a thin veneer over sockets, there is no way for one RWSocket to know about the existence of copies. This means that the destructor can't selectively close the socket, leaving it open if there are more copies of the socket in use. Worse, there might be legacy C code in use that holds a C representation of the socket. Such code would surely be confused if the C++ destructor closed the socket. Therefore, you are responsible for writing code that closes each socket exactly once.
©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.