Socket Programming (Programming a Simple Server) in C++

Before going to in depth details about how to program a socket, it is better to have some idea about what is a socket and how it operates when communication happens.
Socket…it is an abstract concept of communication end point. It can be identified by associated IP address and Port. Basically there are two types of internet sockets which we have to worry. One is STREAM SOCKET and other is DATAGRAM SOCKET.
Stream socket operates as it’s name suggests. Create stream of bits at one end of the communication and transfer it to other end to receive as it is. Stream socket data transferring is reliable, connection oriented delivery. Data transmission is controlled by and quality is guaranteed by protocols like Transmission Control Protocol (TCP).
In Datagram Sockets, packet is created by attaching the destination IP address and sends it without establishing physical or logical connection between source and destination. User Datagram Protocol (UDP) governs the communication in this mode.
Also it is better to have some understanding about OSI 7 layer model which I do not try to include here.
OK! That’s more than enough about basic theories. Let’s start programming with famous and simple client-server model. In this model you have to first start up the server process and put it to a loop which never ends (Because web server should function 24*7 and serves to the clients). That server process should wait for client connections and after client connected; request handling need to be done. Generally client initiates the communication by sending request to the server; therefore client process is called as active participant while server is called as passive participant of the communication.
Following diagrams shows what exactly we should do in order to make a client and server.

In C and C++ socket (), bind (), listen (), accept (), etc. kind of system calls are used to make a connection between client and server. Let’s start with server side stuff.

Socket ()
Exact system call is
int socket (int domain, int type, int protocol)
In order to use this system call it is required to include types.h and socket.h header files

#include <sys/types.h>
#include <sys/socket.h>

OK…what are those arguments then?
int domain: - this refers to protocol family which this socket should created on. There are many protocol families available such as AF_INET, AF_UNIX, AF_IPX, etc. If you are programming on IPv4 then use AF_INET or if it is IPv6 then use AF_INET6.
int type: - type of socket (Stream Socket or Datagram Socket) you are going to create. That means whether it is a SOCK_STREAM or SOCK_DGRAM. For now just use SOCK_STREAM as the value for this argument.
int protocol: - use 0 here. For socket type belongs to a particular protocol family has single protocol. You can get it by setting this value to 0 (zero)

If socket function succeeds then it returns the socket descriptor (simply an integer value) or -1 if any error occurred. Therefore check whether return value is -1 (any error occurred) and if it is so then return immediately.

#include <sys/types.h>
#include <sys/socket.h>

int iSocketFD;
iSocketFD = socket(AF_INET, SOCK_STREAM, 0); 
if(iSocketFD == -1)
{
 perror("Error ocurred while crearting socket!");
 exit(1);
}

Bind ()
After creating a socket we have to associate a port for that socket. bind () system call is used for that purpose.
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)

int sockfd :- socket file descriptor which is returned by socket() system call.
struct sockaddr *my_addr :- Pointer to a struct sockaddr.
Int addrlen: - length of sockaddr.

2nd argument may little bit confusing. It is just a pointer to struct sockaddr which is given below.
struct sockaddr
{

     unsigned short sa_family; // address family, AF_xxx

     char sa_data[14]; // 14 bytes of protocol address

};

This structure holds socket address information. To deal with this structure normally we used another structure.

struct sockaddr_in {

     short sin_family; // Address family

     unsigned short sin_port; // Port number

     struct in_addr sin_addr; // Internet address

     char sin_zero[8]; // Same size as struct sockaddr

};


It is important to remember that struct sockaddr_in* can be cast to struct sockaddr* and vice versa. As a programming practice first just set values for sockaddr_in structure and at the time of binding, cast it to sockaddr structure. sin_port and sin_addr should be in network byte order (I’ll give some details about this in later. For now if you supply port number as integer value then use htons() function to convert it to network byte order).
OK..one more structure to go. As you see internet address (sin_addr) should be supply through another structure called in_addr which is given below.

struct in_addr {

     unsigned long s_addr; // that’s a 32-bit long, or 4 bytes

};

Do you remember from where we reached to this point? We were in bind system call’s 2nd argument. In this argument we had to supply address and port number to bind with socket, for that we use above 3 structures. Now it’s time to set values for structures.
/* create a variable (really?) of type struct sockaddr_in.    
Using this variable it is possible to call
to variables inside the struct
(as similar way to create an object of a
class and calling to class members using that object).*/ 
struct sockaddr_in sServerAddress;

/*setting the address family*/
sServerAddress.sin_family = AF_INET;

/*set the address of machine which server runs on.
 If you don’t know about the server address or if
 server is connected and serves through multiple
 network interfaces then use  INADDR_ANY as the
 value (this will automatically fills the IP 
 address which server runs on). Just look how
 it calls to in_addr struct’s s_addr long variable.*/
sServerAddress.sin_addr.s_addr = INADDR_ANY; 
int iPortNumber = 2400;
/*set the port number. htons() function
 used to convert integer port number into
 network byte order.*/
sServerAddress.sin_port = htons(iPortNumber);

Those set of arguments are enough for normal usage of bind system call. Wait..we have one argument to fill in bind system call. Length of the sockaddr struct. Nothing to worry just use sizeof() operator.
sizeof(sServerAddress);

Following code segment shows the entire coding for bind system call.
struct sockaddr_in sServerAddress;
iPortNumber = 2500;
sServerAddress.sin_family = AF_INET;
sServerAddress.sin_addr.s_addr = INADDR_ANY;
sServerAddress.sin_port = htons(iPortNumber);

int iReturn = bind(iSocketFD, (struct sockaddr*) &sServerAddress,  sizeof(sServerAddress));
if(iReturn == -1)
{
 perror("Error ocurred while binding socket!");
 exit(1);
}


So far we have created a socket and bind it to an IP address and port. As a server what it should do next? Any guesses? Waiting…waiting…waiting……..yes that’s the point. It should wait for incoming connections. In programming terms, it should listen to client connections and as soon as client connects, that connection should be accepted and serve to the client. For listening purpose listen () system call is used.

listen()
Exact listen () system call is as follows.
int listen(int sockfd, int backlog);
int sockfd :- file descriptor returned by socket() system call
int backlog :- size of incoming connections queue. Incoming connections are waiting on this queue until those are accepted.
Listen will return -1 if any error occurred.
int iListenReturn = listen(iSocketFD, 5);
if(iListenReturn == -1)
{
 perror("Error ocurred while going to listen!");
 exit(1);
}

Accept()
Oh…..Client connection received to the server, what should I do now? Hey…you have to accept it and start your communication.
To accept client incoming connections at server side, accept () system call is used.
int accept(int sockfd, void *addr, int *addrlen);

sockfd :- file descriptor returned by socket() system call
addr :- pointer to the struct sockaddr_in of the client. This will contains the client address and port number.
Addrlen :- Pointer to the size of client’s sockaddr_in struct.

Accept system call will return new file descriptor for accepted connection. That file descriptor should used for further operations of a particular connection (i.e. sending and receiving information). At this point server has two file descriptors. One is which returned by socket () system call which is now listening to incoming connections and other one is which returned by accept () system call.
struct sockaddr_in sClientAddress; //struct variable to hold client information
socklen_t iClientStructLength; // variable to hold client strcuct sockaddr_in’s length
iClientStructLength = sizeof(sClientAddress); 
int iNewFileDesciptor  = accept(iSocket, (struct sockaddr *) &sClientAddress,  &iClientStructLength);

Exact coding of accept system call is as follows.
int iNewFileDesciptor;
struct sockaddr_in sClientAddress;
socklen_t iClientStructLength;
iClientStructLength = sizeof(sClientAddress);
iNewFileDesciptor = accept(iSocketFD, (struct sockaddr*) &sClientAddress, &iClientStructLength);
if(iNewFileDesciptor == -1)
{
 perror("Error ocurred while accepting the connection!");
 exit(1);
}

Read and Write
Now it’s time to read something from socket and write something to socket. After accepting client connection, client can send message/ information/ request to the server (which server reads from socket) and server can send message/ information/ response to the client (which server writes to the socket). For reading and writing purposes file descriptor which is returned by accept system call is used.

#include <sys/unistd.>

ssize_t read(int fd, void *buf, size_t count)

int fd :- file descriptor returned by accept system call
void* buf :- pointer to buffer to store read information
size_t count :- this number of bytes are read and putted to the buffer
On success read () will return number of bytes read from the socket and -1 will return if any error occurred.

#include <sys/unistd.>

ssize_t write(int fd, const void *buf, size_t count)
int fd :- file descriptor returned by accept system call
void* buf :- pointer to buffer to store information to be write to the socket.
size_t count :- this number of bytes write to the socket from buffer
On success write () will return the number of bytes written to the socket and -1 will return if any error occurred.
Following code segment shows the exact usage of read () and write () functions.

char zBuffer[256]; //create a buffer to store information read and write from/to socket
bzero(zBuffer, 256);//Empty the buffer
int iReadReturn = read(iNewFileDesciptor, zBuffer, 255);
if(iReadReturn < 0)
{
 perror("Error ocurred while reading from socket!");
}
int iWriteReturn = write(iNewFileDesciptor, "Hi there, welcome to the simple server", 38);
if(iWriteReturn < 0)
{
 perror("Error ocurred while writing to the socket!");
}
After everything is done just called to close () function to close the connection. Remember to close both file descriptors.

close(iNewSocket); close(iSocket);

Complete server code can be download from here . Client side programming will be available in next post. Just try to understand the server code, compile and run it. Good luck…..

1 comment:

  1. Very good article, but the source code is missing. When we click on the link ("here"), all we can see is an empty page.

    ReplyDelete