loader gif

PGLib

July 15, 2012

PGLib is a set of two libraries (PGSocket and PGThread) for C++. These libraries provide some classes and methods to use sockets and threads respectively in a cross-platform environment without having to deal with Windows or Linux native libraries. I developed these libraries to make my Computer Science degree final project (link).

In order to use the library, you must download it, add the files into your project and include the correspoding header file (PGSocket.h, PGThread.h). Below you can find a description of the libraries and how to use them. Some good reasons to use these libraries are their ease of use and the fact that they run natively on Windows and Linux.

PGSocket

PGSocket implements a class that allows you to use Linux sockets (and hence, the POSIX sockets) and Windows sockets (Winsock2) comfortably. Let’s see some examples of how to use PGSocket:

Server

To use it in a server, it’s necessary to define the socket type and its port. We can do it like this as a TCP connection:

#include "PGLib/PGSocket.h"

int main(){
    const char* port = "12345";
    PGSocketLib::PGSocket tcpSocket;
    tcpSocket.setSockType(PGSocketLib::PGS_TCP);
    tcpSocket.setPort(port);
    tcpSocket.setFamily(); // Sets IPv4

    tcpSocket.bindSocket();

    if(tcpSocket.listen(5) == -1){
        cerr << "Error listening petitions" << endl;
        exit(EXIT_FAULIRE);
    }
    int newSocketDescriptor = tcpSocket.acceptConnection();
    PGSocketLib::PGSocket userSocket;
    userSocket.setSocketDescriptor(newSocketDescriptor);
    // communicate using the userSocket
}

In order to make a UDP server instead, we only would need to change the socket type and avoid calling the listen() function.

Client

This is the example code for the TCP client application:

#include "PGLib/PGSocket.h"

int main(){
    PGSocketLib::PGSocket tcpSocket;
    const char* port = "12345";
    const char* host = "myhost.example";

    tcpSocket.setSockType(PGSocketLib::PGS_TCP);
    tcpSocket.setPort(port);
    tcpSocket.setHost(host);
    tcpSocket.setFamily(); // Sets IPv4

    tcpSocket.connectSocket();
}

The only modification needed for a UDP client is changing the socket type.

Communication

The communication differs a little bit whether it’¡s TCP or UDP. Here the example code for communicating using a TCP socket:

char msg[30];
strcpy(msg, "Message we want to send");
tcpSocket.writeSocket(msg, sizeof(msg));

memset(msg, 0, sizeof(msg));
tcpSocket.readSocket(msg, sizeof(msg));
cout << "Message received is: " << msg << endl;

And here a UDP socket:

char msg[50];
socklen_t peer_addr_len;
struct sockaddr_storage peer_addr = sizeof(struct sockaddr_storage);
    
if(udpSocket.recvfromSocket(msg, sizeof(msg), 0, peer_addr, peer_addr_len) < 0){
    cout << "cannot read message" << endl;
} else{
    char host[NI_MAXHOST], service[NI_MAXSERV];
    int s1 = getnameinfo((struct sockaddr *) &peer_addr,peer_addr_len, host, 
					NI_MAXHOST,service, NI_MAXSERV, NI_NUMERICSERV);
    if(s1 == 0){
        strcpy(msg, "response");
        if(udpSocket.sendtoSocket(msg, sizeof(msg), 0, peer_addr,
			peer_addr_len) != sizeof(char [200]))
	{
            fprintf(stderr, "Error sending msg\n");
        }
    }
}

To close the connection, both TCP and UDP, we only need to call the closeSocket() function.

PGThread

In the same way as PGSocket, PGThread implements Linux pthreads and Windows threads through a class. In this case, PGThread is based on a series of posts by Oscar Campos in GenbetaDev (link to posts in Spanish). In addition, there is also implemented a synchronization mechanism: PGMutex.

To use PGThread is as simple as the following example:

#include "PGThread.h"

void function(void *args){
    // Whatever you want to do
    // in the thread goes here.
	// Parameter is received in the args pointer
	yourOwnType *dt = (yourOwnType*) args;
}

yourOwnType sharedVariable;

int main(){
    PGThreadLib::PGThread myThread;
    void *args = &sharedVariable;
    myThread.createThread(function, arghs);
    // Other thread code here
    // ...

    // wait until thread finish
    myThread.waitToEnd();
}

Furthermore, each PGThread instance contains a PGMutex. Therefore, if we want synchronization with a specific PGThread we only need to block its mutex:

void function(void *args){
    PGThread *me = (PGThread*) args;
    me->lockMutex();
    while(){
        me->unlockMutex();
        // code
        ...
        me->lockMutex();
        } 

        me->unlockMutex();
    }

int main(){
    PGThreadLib::PGThread myThread;
    dataType mDt;
    void *args = &myThread;
    myThread.createThread(function, args);

    myThread.lockMutex();
    while(){
        myThread.unlockMutex();
        // code
        ...
        myThread.lockMutex();
    }
    myThread.unlockMutex();
}

And in the case we wanted to use a single PGMutex in different threads we could have it as a global variable, or we can just assign the desired PGMutex to every thread we want to work with it. This way all of the PGThreads will use the same instance of PGMutex:

PGMutex mMutex;
PGThread threads[30];
for(int i = 1; i < 30; i++){
    threads[i].setMutex(&mMutex);
}

PGThreadLib also includes a wrapper to the sleep functions, receiving the number of milliseconds to sleep:

int main(){
    for(int i = 10; i > 0; i--){
        cout << "Seconds left: " << i << endl;
        PGThreadLib::sleepThread(1000);
    }
    cout << "Bye!" << endl;
}