2025-02-03 Sockets from scratch (C)

As my interest in networking has grown I have also started to look into low level network programming, so basically how network devices are programmed and how all of your favorite networking libraries work under the hood.

Part one: The Client

To learn sockets I started by reading this book which gave an amazing look into all of the structs and methods that I needed to create my own sockets.

Step 1.0: figuring it out

Before we look the code I’ve written lets take a look an important struct we will be using.

Here is the first struct we will look at, sockaddr_in``.

struct sockaddr_in {
    short int          sin_family;  // Address family, AF_INET
    unsigned short int sin_port;    // Port number
    struct in_addr     sin_addr;    // Internet address
    unsigned char      sin_zero[8]; // Same size as struct sockaddr
};

sockaddr_in is responsible for the majority of socket building as it contains the following fields:

  • sin_family: this field should contain the AF_INET (AF = Address family, ipv4 / ipv6).

  • sin_port: is the port the socket should bind to.

  • sin_addr: is the address.

  • sin_zero: this is the size of the sockaddr struct for compatibilty however can often be ignored with modern systems.

As you can see the sin_addr is another struct (yes a struct in a struct…), here is what it looks like:

struct in_addr {
    uint32_t s_addr; // that's a 32-bit int (4 bytes)
};

As we can see the address is an unsigned 32 bit integer.

Step 1.1: building the connection

Now lets start to build our socket.

/*
** socket.c -- show IP addresses for a host given on the command line
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main(){

    struct sockaddr_in network_in;

    int network_socket;

    network_socket = socket(AF_INET, SOCK_STREAM, 0);

    network_in.sin_family = AF_INET;
    network_in.sin_port = htons(9089);
    network_in.sin_addr.s_addr = INADDR_ANY;

We also create int network_socket, this will hold the response for our socket.

We also create int network_socket, this will hold the response for our socket.

network_socket = socket(AF_INET, SOCK_STREAM, 0); Here we are creating the actual socket() connection and passing a few paramaters.

  • AF_INET: this is the IPV4 Address Family (For IPV6 I would pass AF_INET6).

  • SOCK_STREAM: this defines the type of socket. SOCK_STREAM is for TCP (hence the stream), and SOCK_DGRAM is for UDP based connections.

Next we will add paramaters to our network_in struct.

network_in.sin_family = AF_INET;
network_in.sin_port = htons(9089);
network_in.sin_addr.s_addr = INADDR_ANY;

Again, AF_INET is IPV4. We set our port sin_port to 9089. And we set our address sin_addr.s_addr (remember this is the struct inside the other struct) to INADDR_ANY which just means 0.0.0.0 (or any address).

int connection_status = connect(network_socket, (struct sockaddr * ) &network_in, sizeof(network_in));

Here we make our connection, lets take a look at each paramater.

  • network_socket: This is the socket() we made earlier, we passed AF_INET and SOCK_STREAM to set it to TCP.

  • (struct sockaddr *): This is a bit confusing, we must pass a sockaddr struct to this paramater (the address we want to connect to), but if you remember we used sockaddr_in not sockaddr, so we must cast a reference to network_in to a sockaddr.

  • sizeof(network_in): The final paramter is just the size of our socket struct.

Step 1.3. Receiving connections

Now that we’ve connection to our server, well we need to receive some information.

recv(network_socket, &server_response,sizeof(server_response) ,0);

printf("Server response %s\n", server_response);

close(network_socket);

return 0;

Lets break this down.

recv is the function used to retrieve data sent to us through the socket.

  • network_socket: We first just pass our network_socket which we created in Step 1.1: building the connection.

  • &server_response: This is a reference to the expected response.

  • sizeof(server_response): This is the size of the response.

Now we can print the message from the server:

printf("Server response %s\n", server_response);

And finally we can close our socket with.

close(network_socket);

Part 2: The Server

Step 2.0

Luckily our server code is not much differant than the client code above.

Step 2.1: creating a server

The first thing we should do is create our socket like in our client, we can write

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>


int main() {

    char server_message[255] = "hello from server!";

    int server_socket;

    struct sockaddr_in serv_in;

    server_socket = socket(AF_INET, SOCK_STREAM, 0);

    serv_in.sin_family = AF_INET;
    serv_in.sin_port = htons(9089);
    serv_in.sin_addr.s_addr = INADDR_ANY;

here we create a serv_in struct similar to our network_in in our client.

we also create a server_socket to hold the response of our socket, as we did in the client.

Note

make sure the port for the client and server are the same.

Step 2.2: bind and listen

To make sure our sockets… actually work we need to bind it to an IP. We can do that with the following line:

bind(server_socket, (struct sockaddr *) &serv_in, sizeof(serv_in));
listen(server_socket, 1);

The bind function takes a few params:

server_socket: This is the servers endpoint. (struct sockaddr *) &serv_in: This casting the reference to our sockaddr_in struct which contains IP and port information. sizeof(serv_in): The size of our struct to ensure bind works properly.

Once the socket is bound, we call listen(server_socket, 1); to start listening for incoming connections. The 1 specifies the backlog, meaning the number of pending connections that can wait before being accepted.

Step 2.3: accept and send

Now, as the server we need to ensure we can accept a connection with our client, we can use the accept() function for this, here is what it looks like:

int client_socket;

client_socket = accept(server_socket, NULL, NULL);

First we create an int client_socket.

Then we assign this integer to our accept() function, lets break that down.

  • server_socket: this is the socket we opened to listen.

  • NULL: for this project we ignore the next 2 paramaters.

Next, we can finally send our message to the client and print it out.

send(client_socket, server_message, sizeof(server_message), 0);

here we call send with some paramaters, lets break them down real quick:

  • client_socket: The client we are accepting from.

  • server_message: This is the message we want to send

and finally close our socket close(server_socket);

Part 3: Intercommunications

Right now our serve can send information to the client perfectly fine, however lets add some functionality to allow communition from and to both endpoints.

Step 3.1: the server

In serv_socket.c

First we alocate 255 bits for our clients message:

char client_response[255];

Now we need to add recv to our server to let us receive communication:

recv(client_socket, &client_response, sizeof(client_response), 0);

printf("%s", client_response);

close(server_socket); // remember to close

We passed our clients socket, a buffer to store the response and the size of it.

Step 3.2: the client

This is just as easy as the server.

In socket.c

char server_response[255];

recv(network_socket, &server_response,sizeof(server_response) ,0);

printf("Server response %s\n", server_response);

First we allocate 255 bits for our servers message.

Then we use recv to receive the message from our server, we pass our servers socket, a reference to the buffer for storing the response, and the size of the response.

We can then print the response.

Here are both files with these changes:

Client socket.c

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main(){

   struct sockaddr_in network_in;

   int network_socket;

   network_socket = socket(AF_INET, SOCK_STREAM, 0);

   network_in.sin_family = AF_INET;
   network_in.sin_port = htons(9089);
   network_in.sin_addr.s_addr = INADDR_ANY;

   int connection_status = connect(network_socket, (struct sockaddr * ) &network_in, sizeof(network_in));

   if (connection_status == 0) {
       printf("connection successful...\n");

   } else {
       perror("connection error...\n");
   }

   if (connection_status == -1) {
       printf("Error [-1] could not connect to the server.\n");
   }

   char server_response[255];

   recv(network_socket, &server_response,sizeof(server_response) ,0);

   printf("Server response %s\n", server_response);

   char client_message[255] = "hello from client\n";

   send(network_socket, client_message, sizeof(client_message), 0);

   close(network_socket);

   return 0;

   }

Server serv_socket.c

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>


int main() {

   char server_message[255] = "hello from server!";

   int server_socket;

   struct sockaddr_in serv_in;

   server_socket = socket(AF_INET, SOCK_STREAM, 0);

   serv_in.sin_family = AF_INET;
   serv_in.sin_port = htons(9089);
   serv_in.sin_addr.s_addr = INADDR_ANY;

   bind(server_socket, (struct sockaddr *) &serv_in, sizeof(serv_in));
   listen(server_socket, 1);


   int client_socket;
   char client_response[255];

   client_socket = accept(server_socket, NULL, NULL);

   if (client_socket > 0) {
       printf("successfully accepted connection\n");
   } else{
       perror("could not accept connection\n");
   }

   send(client_socket, server_message, sizeof(server_message), 0);

   recv(client_socket, &client_response, sizeof(client_response), 0);

   printf("%s", client_response);

   close(server_socket);

   return 0;

}

Usage example

(Ran with 2 separate terminals)

make socket serv_socket

certcli ~/C__practice/Sockets  ./socket
connection successful...
Server response hello from server!
certcli ~/C__practice/Sockets  ./serv_socket
successfully accepted connection
hello from client