This semester we will use the Cleansocks library. It provides a C++-based
interface to the standard sockets designed to preserve the concepts and
procedures of the sockets library, while attempting to hide some of the
C-ish grunginess. It was created under Linux, and subsequently dragged over to
Windows, so it can be used on either platform. Others which support some
kind of socket-like networking should be possible as well.
The current release is marked at version 0.1.4.
Download links and installation instructions
are here.
This page describes the interface.
I have ported some of Comer's CNAIAPI examples to use Cleansocks. They are
here:
Ported CNAI Chat Client Example
Ported CNAI Chat Server Example
Ported CNAI Web Client Example
Ported CNAI Web Server Example
Sockets Proper
This section describes the socket interface “proper”. Sockets
are an abstract interface designed to deal with any sort of networking.
To use the classes and operations in this section, include the
header file cleansocks.h. All types and operations described here
are defined in the cleansocks namespace, so you might want to
say using namespace cleansocks;, or qualify the names you are
using.
class socket s;
A socket is a communications endpoint. All messages or connections
travel between to sockets, usually located on different computers.
A C++ object of class socket represents one of these endpoints.
You won't generally use this class directly, however, since it is
abstract and does not
belong to any particular type of network. You will want to create
concrete sockets, such as the TCPsocket object described below.
Concrete sockets are derived from class socket, and can be used with
all the operations described here.
class endpoint e;
This is also abstract, and it is the name of a communications endpoint;
essentially the identifier of a socket. The system uses an endpoint description
to direct the information to the correct socket.
Like sockets, you will not create endpoints
directly, but use the concrete versions for a specific type of
network, such as IPendpoint described later.
An IPendpoint is a host name combined with a port number.
connect(socket s, endpoint e)
This call attempts to connect the socket s to the remote endpoint
identified by e. It is used for stream-oriented protocols like TCP,
usually by client programs.
If the connection is successful, the socket can be used in the send
and recv operations below. If unsuccessful, the method will throw
an exception.
bind(socket s, endpoint e)
This associates socket
s with the local endpoint identifier
e located on this computer. This is most often used by servers
to specify where clients will need to send information to contact them.
For instance, a TCP-based server will use
bind specify which port
clients should try to contact.
It will succeed or throw an exception.
listen(socket s [, int b])
This places socket
s in listen mode.
It is used by connection-oriented protocols to allow other
sockets to
connect to
s. Servers use this call to listen for
clients to connect.
Think of it as turning on your phone so you can receive calls.
The optional parameter b is the backlog size. It tell how many
un-received connections will be held by the system before new connections
are refused. Think of it as the maximum number of calls you are allowed
to have waiting.
listen will succeed or throw an exception.
socket s2 = accept(socket s)
socket s2 = accept(socket s, endpoint& e)
Accept receives a connection from a remote socket. The socket
s must
have been put into listening mode previously. A call to
accept
suspends the calling thread until some other socket attempts to connect
to
s, then it resumes the caller and returns.
Think of it as answering on your phone.
Perhaps more accurately, answering your phone after it wakes you up.
(This is not unlike reading from the keyboard, where the read waits
until you type something.)
The return value s2 is another socket. This one is already connected
to the remote socket, and you use s2 (with send and recv) to
communicate with the socket that connected to s and woke you up.
The e in the second form is an output parameter. After the call returns,
it will contain the endpoint id of the socket that connected to s.
accept will succeed or throw an exception.
int i = send(socket s, const void *buf, int size [, int flags ] )
This attempts to send the first
size bytes starting from the location
given by
buf to the socket
s is connected to. That means
s
must have been sent to a successful
connect, or returned by a
successful
accept. The return value
i is the number of bytes
actually sent, which should generally be
size. Upon failure,
send will throw an exception.
Whether a return value less than
size is bad depends on what
protocol you are using. It should not happen in most situations.
The optional
flag are
the options for the standard socket
send operation; you won't
generally need to provide this. Optional behaviors may differ a bit from this
description.
This is an alternate version of send which accepts a C++ standard
string instead of buf and size.
int i = recv(socket s, const void *buf, int size [, int flags ] )
This receives up to
size bytes from the socket
s is connected to
and places them into the location given by
buf . That means
s
must have been sent to a successful
connect, or returned by a
successful
accept. The return value
i is the number of bytes
actually received, which might be less than
size. Upon failure,
send will throw an exception. If there is no data available,
recv will cause your program to wait until some arrives. Even then,
return values less than
size
are common, and simply indicate that
size bytes have not yet
arrived. A return value of zero indicates that the remote socket has
been closed, and indicates a normal shutdown.
Same story for flags.
int i = sendto(socket s, const void *buf, int size [, int flags ], endpoint e)
The sendto call is used with message-oriented protocols (like
UDP), in which sockets are not connected. It sends the single message
in the location indicated by buf and
size bytes long. The function returns the size of the
message sent. The sent message size may be less than size if that
is too large to be handled by the protocol in use.
int i = recvfrom(socket s, const void *buf, int size [, int flags ] [, endpoint & e])
The recvfrom call is also used with message-oriented protocols
It receives a single message
and places it into the location indicated by buf which must be at least
size bytes long. If the message is longer than size, the extra
bytes are discarded. The function returns the (original) size of the
message. If e is provided, it is an output parameter and recvfrom
fills it in with the endpoint identifier of the socket which sent the
message.
close(s)
This closes the socket, indicating it is no longer used and cleaning up
associated system resources. Sockets should be closed when
no longer needed (but see below).
Assigning and copying sockets is generally allowed, but can be
problematic. A copy of a socket is not an independent resource, but refers
to the same underlying object as the original. If the original or the copy
is closed, the other will be invalid, and operations will fail. Closing both
is usually safe, but could be problematic, at least on a Unix style OS.
This reflects a behavior of the underlying socket layer which cleansocks does
not attempt to modify. This might be done in a later version.
IP 4 Sockets
This section describes the part of Cleansocks which deals with IP version 4.
Include
cleanip.h.
class TCPsocket
class UDPsocket
These are the classes representing sockets that use the TCP and UDP protocols,
respectively.
They are derived from the socket class, so the socket operations described
there can be applied to them.
class IPaddress a;
class IPaddress a("w.x.y.z");
class IPaddress a(unsigned int v);
This class represents a IP version 4 address. It can be constructed
from a string giving and address in dotted-decimal notation, or
from an unsigned integer value. The default constructor produces an invalid address,
but you may want to use it to create a variable which you can assign later.
Use lookup_host (below) to get an
address from a host name.
This succeeds or throws an exception.
class IPport;
class IPport(int p)
This class represents an IP port number. It may be constructed by
by specifying the number, or by default to assign later.
Use lookup_service (see below) to look up a port number by its service
name.
class IPendpoint;
class IPendpoint(IPaddress a, IPport p)
This is the IP derivative of an endpoint, built from an IP address and a port
number.
IPaddress a = lookup_host(string hn)
The lookup_host method takes a host name and returns its
IP address. It takes a C++ standard string. It will succeed or
throw an exception.
IPport a = lookup_service(string hn)
The lookup_service method takes the name of a standard service
and looks up the standard port number. For instance, if you look up
http you will get port 80.
IPaddress::any()
This is the so-called wild-card address. It is used to build an IPendpoint
that represents a specified port at any address.
This is used so that a server can bind to a port on any local address.
Buffered Stream Sockets
The
buffered_socket is a convenience which extends the functionality
of the basic socket interface. The buffered socket class can be used
with any connected socket. It supports
send and
recv,
but the object may read more bytes than the user requests, and save them to
return on the next receive. This can increase efficiency of applications
which want to do small reads, and allows an efficient
recvln method
which is useful for line-oriented protocols. Include
cleanbuf.h to
use this facility.
class buffered_socket q(socket s [, int size ])
This constructs a buffered_socket object, q. The size is
the amount of local storage allocated, and defaults to 1024 bytes.
The constructor assumes that s supports the send and recv
operations, and later operations will fail if this is not true.
int i = recv(buffered_socket s, const void *buf, int size [, int flags ] )
This has the same client semantics as the regular socket recv, except that
the underlying implementation may ask for more than size bytes,
which it then retains and returns upon subsequent calls.
int i = recvln(buffered_socket s, const void *buf, int size [, char term ] [, int flags ] )
The recvln method behaves like recv, except that it stops
reading at the first occurrence of the terminator character term, which
is newline by default. It will continue to try to get data until
buf is full, or until term is found. This is very useful for
protocols which use line breaks, such as HTTP, FTP, and the email protocols.
int i = send(buffered_socket s, const void *buf, int size [, int flags ] )
This just calls send on the socket object contained in s. A
future version of the library may attempt to buffer sending, but this one
does not.
Exceptions
Cleansocks throws exceptions to report errors (the regular socket
interface does not use exceptions).
The library has several, all derived from
the class
socket_error, which is derived from the standard C++
runtime_error. Their inheritance relationship is like this:
std::runtime_error

socket_error

socket_sys_error

socket_db_error

socket_would_block
The
socket_error class is the base of any exception detected by
this library. There are a few places which throw exceptions of exactly
this type. For most purposes, if you need to catch anything, catch
socket_error &.
The
socket_sys_error exceptions represent errors
reported from the basic (generic) socket calls described in the first section of
this document. The
socket_db_error is thrown by failures of
host or service name performed by
lookup_host or
lookup_service.
This division reflects one in the underlying socket interface.
The
std::runtime_error class and all its children have a
.what()
method which returns a descriptive string. The
socket_sys_error
and
socket_db_error also implement a method
.code() which
returns the integer error code reported by the system.
These values will differ based on the underlying operating system,
so you should probably avoid using them.
The socket_would_block error wraps the system error return of
the same type (hence it's a socket_sys_error). This
occurs only when using non-blocking sends or receives (which is not
the default). It is represented by a separate exception since it
will often need separate treatment by programs which use it.
The .what() strings for socket_sys_error and socket_db_error
are the error messages provided by the underlying system, and will
have different text on different OSes. Error codes for socket_db_error
are error return values from the underlying socket functions,
mapped to strings by the socket call gai_strerror().
For socket_sys_error, under Unix the codes come from errno and
are mapped by strerror_r; on Winsock, they come from
WSAGetLastError() and are interpreted by FormatMessage(),
a seven-parameter monstrosity that could have come from nowhere but
Redmond.
Thread Safety
The Cleansocks library is implemented using Winsock on Windows and
the standard socket and IP lookup calls on Unix, as well as the standard
C++ libraries. AFAK, all the underlying calls are thread-safe. In
particular, the host and service lookups on Linux are done using the
newer getaddrinfo() call, rather than the older interface.