Click here to Skip to main content
15,881,424 members
Articles / Programming Languages / C++

Event Based Socket Streams: SocketLib

Rate me:
Please Sign up or sign in to vote.
4.86/5 (14 votes)
30 Apr 2012LGPL39 min read 56K   2K   79   14
A C++ TCP socket stream library.

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.

C++
#include "SocketLib/HostInfo.h"
#include <iostream>

using namespace socketlib;
using namespace std;

void resolved(HostInfo &info) {
    if(!info) { //HostInfo can be converted to bool to check result
        cout<<"Cannot resolve host"<<endl;
        return;
    }

    foreach(AddressInfo, ai, info) { //Collection iteration
        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_params 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 operations
    • Listening: Server is listening to the specified port, but not accepting any connections
    • Accepting: Server is asynchronously accepting connections
    • BlockingAccept: Server is blocked in an Accept function
  • StopListening( ): Closes the server socket, effectively unbinding the port and stopping the asynchronous accept thread, if running
  • CloseAll( ): Closes all connections
  • int LiveConnections( ): Returns the total number of live connections

Following is a simple server that sends a "Hello" to every connected client and disconnects:

C++
#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:

C++
#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;
    //If data in the buffer is larger than 1k
    //this event handler will be called again
}

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.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Instructor / Trainer Eastern Mediterranian University
Turkey Turkey
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionClient example not compiling Pin
Fab1can15-Jan-22 1:19
Fab1can15-Jan-22 1:19 
Questioncompilation OK sous Qt Creator Pin
professor tourneseul13-Apr-20 0:45
professor tourneseul13-Apr-20 0:45 
GeneralMy vote of 5 Pin
professor tourneseul13-Apr-20 0:38
professor tourneseul13-Apr-20 0:38 
QuestionTCPSocketStreamBuffer, Exception when the buffer is full Pin
Member 1002491230-Jun-17 5:00
Member 1002491230-Jun-17 5:00 
QuestionNon working server example Pin
Member 125977526-Apr-12 1:14
Member 125977526-Apr-12 1:14 
AnswerRe: Non working server example Pin
Cem Kalyoncu28-Apr-12 8:48
Cem Kalyoncu28-Apr-12 8:48 
QuestionHow about a UDP Server? Pin
maplewang22-Dec-11 18:14
maplewang22-Dec-11 18:14 
QuestionGetting alot of warnings when buidling Pin
DanneK26-Sep-11 23:30
DanneK26-Sep-11 23:30 
AnswerRe: Getting alot of warnings when buidling Pin
Cem Kalyoncu27-Sep-11 6:13
Cem Kalyoncu27-Sep-11 6:13 
QuestionWhat about a secure version? Pin
tomsmaily2-Feb-11 0:19
tomsmaily2-Feb-11 0:19 
AnswerRe: What about a secure version? Pin
Cem Kalyoncu5-Feb-11 3:20
Cem Kalyoncu5-Feb-11 3:20 
GeneralRe: What about a secure version? Pin
tomsmaily6-Feb-11 5:34
tomsmaily6-Feb-11 5:34 
GeneralHave 5 Pin
Igor Kushnarev25-Jan-11 9:10
professionalIgor Kushnarev25-Jan-11 9:10 
GeneralRe: Have 5 Pin
Cem Kalyoncu25-Jan-11 22:44
Cem Kalyoncu25-Jan-11 22:44 
Thanks, if you have any problems let me know.
May the bug killer be with you...
Cem Kalyoncu

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.