Introduction
SocketLib is a cross platform event based semi-asynchronous stream library. It is derived from the standard IO stream. Including SocketLib headers will not include any WinSock, Windows, or BSD socket headers. Therefore, your code will be clean of any C macros that might cause problems. Moreover, the library consists of several files which are packed together, it does not require to be in your include path. The only dependency on Linux is pThread, and on Windows, pThread and WinSock2 libraries.
Background
Basic knowledge on networking and C++ streams is required.
Using the Code
SocketLib is an event based, semi-asynchronous socket stream. It derives from standard C++ sockets, therefore all extractors (>>) and inserters (<<) can be used. The semi-asynchronous method allows the programmer to define an event handler to handle incoming data asynchronously without taking the ability to read blocking data. The main aim of this system is to reduce the difficulty of sockets programming and make socket systems a lot more C++ friendly. The second aim is to make the system small enough to allow it to be integrated with any project. In fact, there are many frameworks that allow easy to use sockets. However, as far as I have seen, none work in semi-asynchronous mode. Moreover, most socket libraries are a part of a larger framework requiring you to add hundreds of files to your project.
Normally, working with sockets in C or C++ requires knowledge on the BSD Sockets API or the Windows Sockets (WinSock) API (probably both to make your program cross platform), system calls, threads, and processes. This has two implications, the code written will require additional work to be cross platform, and you have to do a lot of learning. An intuitive C++ socket stream will take minutes to understand opposed to hours of reading for the other option.
The second improvement over the BSD or WinSock alternative is using C++ classes and namespaces. Especially, WinSock heavily uses macros which disturbs C++ coding and might cause problems. For instance, WinSock has the macro which replaces errno
with *errno()
, effectively disallowing you to use a variable named errno
.
SocketLib uses socketlib
as the namespace: every class, type, and enumeration resides in this namespace. However, there is another namespace (networking
) that contains network related information; this namespace is also defined in the socketlibb
namespace. networking
contains three enumerations and their respective types: Protocol
and ProtocolType
, Port
and PortNumber
, Family
and FamilyType
. These types are used in other functions as input parameters or results. The namespace socketlib::prvt
is used internally.
Requirements
SocketLib requires the GGE/Utils package and the pThread library to work. The GGE/Utils package is included in the project as well as pThread headers. However, you may need to copy pthread32.dll to the Windows directory. Currently, it is tested on Windows XP and Centos 5.5; however, it is written and designed to work on all *nix class Operating Systems.
Currently, the system is compiled on Microsoft C++ Compiler 14.0 (which is shipped with Visual Studio 2005) and GCC 4.1.2.
HostInfo and AddressInfo
Our first two classes are HostInfo
and AddressInfo
. HostInfo
resolves and contains all address information that a host has. It is basically a collection of AddressInfo
, where each AddressInfo
holds network related information about a specific address. AddressInfo
allows easy access to the IP address and family (IP v4, IP v6). However, other information can be accessed by obtaining the raw addrinfo
pointer.
The Resolve
function of the HostInfo
class can be used to resolve a domain name (or an IP address). There is also the StartResolve
function which can be used for asynchronous checking; whenever Resolve
finishes, the ResolveComplete
event is called. HostInfo
can be used as a boolean value to check if the resolve succeeded. The following example illustrates the use of this system. It can print more than one IP address for a server.
#include "SocketLib/HostInfo.h"
#include <iostream>
using namespace socketlib;
using namespace std;
void resolved(HostInfo &info) {
if(!info) { cout<<"Cannot resolve host"<<endl;
return;
}
foreach(AddressInfo, ai, info) { cout<<endl<<"IP address: "<<ai->IPAddress()<<endl;
}
}
void main() {
HostInfo h;
h.ResolveComplete.Register(&resolved);
h.StartResolve("cmpe.emu.edu.tr");
cin.sync();
cin.ignore(1);
return 0;
}
TCPServer
Currently, only the TCP side of the system is complete. TCPServer
is the class that listens and accepts incoming connections. First, the Listen
function should be called to bind the server to a specific port. The Accept
procedure can work synchronously or asynchronously. The asynchronous mode fires the ConnectionReceived
event. If desired, this event can be fired in a different thread. The CallConnRcvedEvtInNThrd
property controls this behavior. The ConnectionReceived
event uses TCPServer::accept_param
s for the parameter object which contains the accepted TCPSocketStream
. The ConnectionLost
event is fired when one of the clients looses connection. The ConnectionLost
event uses TCPServer::connlost_params
for the parameter object which contains the disconnected TCPSocketStream
. This system also provides safe resource allocation, i.e., whenever the server object is destroyed, all the connections will be disconnected (calling ConnectionLost
events), the accept thread is terminated, the port is released, and all resources are freed.
The following is the list of TCPServer
methods:
Listen( port )
: Binds the server to the specified port; it can be a PortNumber
(can be obtained from the networking::Port::portname syntax), integer port number, or a port representation string (like http, ftp, etc... numbers are also accepted).StartAccept( )
: Starts accepting new connections asynchronously.TCPSocketStream &Accept( Timeout )
: Accepts a connection; if Timeout
is not specified, this function will wait indefinitely until a connection is received or the socket is closed.TCPServer::Status getStatus( )
: This function returns the current status of the server. It can be one of the following:
Idle
: Server performs no operationsListening
: Server is listening to the specified port, but not accepting any connectionsAccepting
: Server is asynchronously accepting connectionsBlockingAccept
: Server is blocked in an Accept
function
StopListening( )
: Closes the server socket, effectively unbinding the port and stopping the asynchronous accept thread, if runningCloseAll( )
: Closes all connectionsint LiveConnections( )
: Returns the total number of live connections
Following is a simple server that sends a "Hello" to every connected client and disconnects:
#include <iostream>
#include "SocketLib/TCPServer.h"
using namespace std;
using namespace socketlib;
void connect(TCPServer::accept_params params) {
cout<<"Connection received from "<<params.addrinfo.IPAddress()<<endl;
params.socket<<"Hello"<<endl;
params.socket.Close();
}
int main() {
TCPServer server;
server.Listen("444");
server.ConnectionReceived.Register(&connect);
server.StartAccept();
cin.sync();
cin.ignore(1);
return 0;
}
TCPSocketStream / TCPClient
This class has two different names, TCPSocketStream
and TCPClient
. Its main purpose is to stream data between two sockets. Its second aim is to be the client, connecting to a server. Therefore, it contains connectivity functions as well. This class is derived from the standard I/O stream. This means that any object that can be inserted or extracted from a stream can be inserted and extracted from this class. However, sockets do not support seeking, therefore any seek or position request will fail. If you try to send data while the socket is closed, you will get a SocketException
exception, which might be handled by the stream system and translated to a failed status. However, if the read operation fails due to losing the connection, you will receive an EOF notification. Moreover, the connection is closed if the object gets destroyed.
The Buffer
class of this stream has two different buffers for incoming and outgoing data. So both sending and receiving can be performed at the same time. However, Microsoft headers for stream operations uses a single Mutex for both input and output buffers. Because of this, the advantage of sending and receiving at the same time is lost if Microsoft headers are used.
Standard inserters is the preferred method to send data to a recipient. Moreover, the WriteBinary
function is added to the system for convenience. For any simple object, you may use this function to send its entire data to the other end. Data will be sent on an explicit flush request or whenever the buffer is full. The endl
stream modifier also flushes the buffer, so it can be used to terminate commands that you need to send to the recipient. Send requests are always synchronous, but after data is transferred to the Operating System, it is queued for sending; your application will not wait for the entire send operation. There is one important point about TCP sockets: when sending data, data might be needed to be broken into segments. The size of the segments is controlled by the Operating System or the underlying hardware; therefore, there is no way to be sure that every packet is sent in one send request.
Receiving data works in semi-asynchronous mode. In this mode, there is one thread always waiting for data to be read, another thread is used to fire the Received
event. The first thread starts whenever connection is established, and stopped when it's closed. The second thread is started when data is received and no requests are made to receive it. This second thread fires the Received
event and waits for its termination. The Parameter
object of Received
event contains the size of the receive buffer and a reference type boolean variable called shouldrecall
. If this variable is set to true
inside the event handler and there is still data remaining in the buffer, the Received
event is fired again. This method can be employed to read only one frame every time the Received
event is fired, delaying the remaining data to the second call. Inside the Received
event, the programmer should read data using extractors, get
, getline
, read
, or ReadBinary
functions. Extraction operations are synchronous, but since there is data inside the buffer (whenever the Received
event is fired, the buffer definitely contains data) and the size of the data can be determined using the event parameters, this method can be used like an asynchronous mechanism. An important note is the event thread is separate from the main thread and it might be required to synchronize threads.
The following is the list of all methods and variables of TCPSocketStream
:
bool Connect( host, port )
: Resolves and connects to the given host and port; this function works synchronously; the asynchronous version is a future work. If it cannot resolve the host or the connection fails, this function will return false
; if another error occurs, it will throw a SocketException
.bool Connect( addressinfo )
: Connects to the given host using information from an AddressInfo
class. For this system to work, you must specify the port parameter of the Resolve
function in the HostInfo
class.bool isConnected()
: Returns whether this socket is connected.Close()
: Closes the socket, ending the accept thread. This function is safe to be used in the receive event thread; however, you cannot destroy the calling socket in the receive event thread.int Available()
: Amount of data available in the read buffer.Disconnected
Event: Fires whenever the socket is disconnected. Has no specific parameters.
Following is a simple client that connects to the server and displays any received data:
#include <iostream>
#include "SocketLib/TCPSocketStream.h"
using namespace std;
using namespace socketlib;
void received(TCPSocketStream::accept_received_params params,
TCPSocketStream &socket) {
int cnt=params.available;
if(cnt>1024) {
cnt=1024;
}
char data[1024];
socket.read(data, cnt);
cout.write (data, cnt);
params.shouldrecall=true;
}
void disconnected() {
cout<<"Disconnected."<<endl;
}
int main() {
TCPSocketStream client;
client.Received.Register(&received);
client.Disconnected.Register(&disconnected);
client.Connect("localhost", "444");
cin.sync();
cin.ignore(1);
return 0;
}
Points of Interest
This library is care free and easy to use. Writing a simple network application is quite easy. Being inherently multi-threaded, you do not need to worry about blocking modes. Also, the shouldrecall
parameter of the Receive
event helps with concatenated frames.
There is one more fun thing to do with this library; using this system, you can easily write an application that will connect to itself and send data. Effectively being the client and the server at the same time.
History
- 2011-01-25: First public revision.
- 2011-02-01: After testing on Centos 5.5 and GCC 4.1.2, the system requirements were altered to reflect that this system can be compiled on GNU/Linux systems using GCC.