2025-02-03 Sockets from scratch (C) =================================== .. post:: Feb 03, 2025 :category: Coding, Networks :tags: Programming, low level, C, sockets, networks, network programming :author: ccli :language: en 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````. .. code:: c 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: .. code:: c 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. .. _building_connection: Step 1.1: building the connection ................................. Now lets start to build our socket. .. code:: c /* ** socket.c -- show IP addresses for a host given on the command line */ #include #include #include #include #include #include #include #include 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. .. _network_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. .. code:: c 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. .. code:: c 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 :ref:`building_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 .. code:: c #include #include #include #include #include #include #include #include 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: .. code:: c 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: .. code:: c 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. .. code:: 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: .. code:: c char client_response[255]; Now we need to add ``recv`` to our server to let us receive communication: .. code:: c 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` .. code:: 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` .. code:: c #include #include #include #include #include #include #include #include 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` .. code:: c #include #include #include #include #include #include #include #include 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`` .. code:: clojure certcli ~/C__practice/Sockets ./socket connection successful... Server response hello from server! .. code:: clojure certcli ~/C__practice/Sockets ./serv_socket successfully accepted connection hello from client