This post serves as a explanation of the basics of a TCP socket server, which
serves as the base to a future post explaining the basics of a web server.
Note: the complete code of an older version is fully posted in the end.
Check my github repo for an updated version. Link.
There are good tutorials out there that explain each of the functions of the
sockets POSIX API in detail so this post will serve mostly to understand the
overall basics.
As we all know, a TCP connection is also know a connection oriented protocol
which handles acknoledgment of bytes when they are received and sent. So it
makes sure that all packets are ordered and complete for each transaction.
How do we start a TCP socket connection? Below is a graph of the transition
between sockets system calls to have a TCP server up and running.
1234567
Connection Establishment --------
TCP (Stream Sockets) | |
Server (Passive Socket) v
socket() --> bind() --> listen() --> accept() --> read()/write() --> close()
| | ^
Client (Active Socket) ^ v v |
socket() ----> connect() ---------|-------------> read()/write() --> close()
A server starts by creating a socket descriptor, this particular socket is only
used by the server to listen for incoming connections. It then binds a
particular socket descriptor with an IP address and then sets to listen on that
socket and IP address. Then the server sits on the accept function call. When
the server gets a connection request with the accept function call, it creates
another socket descriptor which is used for I/O with the client. When the I/O
with the client is done, the server closes the socket descriptor used for I/O
and uses the listening descriptor once again to listen for a new client.
On the client side is much easier, you create socket descriptor for the server
you are going to connect to and fill int some properties for this particular
host you are after and then call connect(). If the call was successful, your
program is ready for I/O with the server.
So now that we get an idea of what the system calls do, I’m going to show you
some actual code. This code belongs to a TCP server implementation in my
github account.
I am going to analyze it in pieces.
12345
/* server socket variables */intsockfd,new_sockfd;structsockaddr_inhost_addr,client_addr;socklen_tsin_size=sizeof(structsockaddr_in);intrecv_length=1,yes=1;
This section declares and defines our basic variables for both of server and
clients. sockfd is used to establish socket connections and new_sockfd is
used for I/O. struct sockaddr_in is the structure holding everything regarding
IP addressing for both the server and client in particular for IPv4. The rest of
the variables are mostly to enable options for other socket system calls.
Here we finally create our socket descriptor to accept connections from clients.
And use setsockoptions to enable some beneficial options for our sockets.
Explaining this options will take us out of scope, but to get an idea, they help
in re-using a previously use address when binding and restarting the tcp server.
You can see that for socket we specify that we are using TCP and IPv4.
1234
/* initializing server address socket structure */host_addr.sin_family=AF_INET;/* host byte order */host_addr.sin_port=htons(S_PORT);/* port, network byte order */host_addr.sin_addr.s_addr=0;/* use host address */
This piece fills some values for our server address structure. We are specifying
the use of IPv4 and port number to use. See that we are not specifying an IP
address. We are telling the kernel to automatically fill it with the current
address of our active interface.
1234
/* bind socket to IP address */if(bind(sockfd,(structsockaddr*)&host_addr,sizeof(structsockaddr))==-1)perror("binding to socket");
After we specify our address structure and socket descriptor we bind the socket
descriptor to the current IP on the given port.
123
/* listen on socket */if(listen(sockfd,5)==-1)perror("listening on socket");
The call to listen() opens the socket to accept incoming connections up to a
maximum of 5 pending connections. All connections are sent into a queue until a
call to accept() consumes a connection.
This last piece accepts an incoming connection returning a new socket
descriptor. This is the descriptor that needs to be use for the read()/write()
system calls. These are the ones that handle back and forth communication between
client and server.
All the piece of code presented so far is the basic structure of implementing an
TCP server. After this, pretty much anything can be done. A web server
is a good idea. It teaches how to go from RFC details to an actual working
program. It is just amazing to see when you get your own web page and server
handling requests from a web browser. Other examples are a proxy server, network
scanners, and sniffers.
Check the code, edit, and improve on it. Let me know if you have any question in
the comments section.
Here is the complete code. The dump() function was taken from Jon Erickson’s book
/* * A simple server to demonstrate TCP sockets implementation. The server * takes input from server and shows dump of the data. */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define DATA 50#define S_PORT 7890voiddump(char*,constunsignedint);intmain(void){/* server socket variables */intsockfd,new_sockfd;structsockaddr_inhost_addr,client_addr;socklen_tsin_size=sizeof(structsockaddr_in);intrecv_length=1,yes=1;/* data holding */charbuffer[1024];/* defining listening socket descriptor */if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)perror("in socket");if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int))==-1)perror("setting socket option SO_REUSEADDR");/* initializing server address socket structure */host_addr.sin_family=AF_INET;/* host byte order */host_addr.sin_port=htons(S_PORT);/* port, network byte order */host_addr.sin_addr.s_addr=0;/* use host address *//* zero the rest of struct */memset(&(host_addr.sin_zero),'\0',sizeof(host_addr.sin_zero));/* bind socket to IP address */if(bind(sockfd,(structsockaddr*)&host_addr,sizeof(structsockaddr))==-1)perror("binding to socket");/* listen on socket */if(listen(sockfd,5)==-1)perror("listening on socket");/* main procedure */while(1){new_sockfd=accept(sockfd,(structsockaddr*)&client_addr,&sin_size);if(new_sockfd==-1)perror("accepting connection");printf("server: got connection from %s port %d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));recv_length=recv(new_sockfd,&buffer,DATA,0);while(recv_length>0){printf("RECV: %d bytes\n",recv_length);dump(buffer,recv_length);recv_length=recv(new_sockfd,&buffer,DATA,0);}close(new_sockfd);}return0;}/* dumps raw memory in hex byte and printable split format */voiddump(char*data_buffer,constunsignedintlength){unsignedcharbyte;unsignedinti,j;for(i=0;i<length;i++){byte=data_buffer[i];printf("%02x ",data_buffer[i]);/* displays byte in hex */if(((i%16)==15)||(i==length-1)){for(j=0;j<15-(i%16);j++)printf(" ");printf("| ");/* display printable bytes from line */for(j=(i-(i%16));j<=i;j++){byte=data_buffer[j];/* outside printable char range */if((byte>31)&&(byte<127))printf("%c",byte);elseprintf(".");}/* end of the dump line (each line 16 bytes) */printf("\n");}}}