Introduction to Socket Programming

Examining the fundamentals of client/server communication in C

Posted by Asa Hess-Matsumoto on Thursday, January 28, 2021

Preamble

I recently completed a warmup for my first project in a Computer Science (CS) graduate school course. As I only have a cursory familiarity with the C programming language, this warm-up took me considerably longer than I had expected. The warm-up called for implementing two C program (which would later be compiled into binaries with gcc): a client and server. Below are some highlights of the lessons learned.

Note: in order to comply with my Graduate School’s Academic Integrity policy, some contents of this post are sanitized so as to prevent plagiarism.

Baby Steps

I knew coming into the course that I needed to brush up on my C programming; the last time I had worked with the language was in 2018, where I had engaged with Harvard’s CS50 course on EdX (available for free to audit). Over the Winter, I took a refresher course through Udemy in order to re-familiarize myself with the fundamentals of the programming language (disclosure: my employer has an established agreement with Udemy, allowing me free access to much of their content).

As others may expect, transitioning from a language belonging to a higher-level of abstraction (Python) to one much closer to the machine © is proving to be painful. However, I am grateful to my past-self for having done the prep work ahead of the course.

Starting Point

Our goal was to use socket programming in order to stand up an HTTP-like server in C that could be connected to by a client of our design. The client and server should be able to message each other, and the connection type should be protocol independent (IPv4 or IPv6).

I began by modeling my client and server off of some existing code linked to by the school:

I also had to read-up on socket programming, to which I found the following resources tremendously useful:

Challenges

After getting my server/client working off of the modeled starter code, I started by working through the server at length. My first challenge was getting the server to accept protocol-independent connections. Fortunately, the above resources helped. In brief: most of the IPv6 programming parameters/functions are backwards compatible, meaning it will accept a connection from either IPv4 (translating it from 127.0.0.1 to ::ffff:7f00:0001).

Then I had to keep the server alive (i.e., allow for clients to connect/disconnect repeatedly); again, the resources indicated this could be accomplished by using SO_REUSEADDR in the setsockopt() function.

Finally, I had to modify the code such that the server would echo back to the client whatever message it received from said client. This proved to be a trivial exercise in knowing how to implement recv() and send() alongwith how C handles strings as char arrays.

The client-side code, by contrast, proved to be much more of a struggle: in order for the client to be protocol independent (i.e. if the user provided an IPv4 or IPv6 address to connect to), I had to learn about the getaddrinfo() function. When invoked, it resolves the hostname to a singly-linked list of IP addresses (both IPv4 and IPv6); the client code then must iterate over the linked list in attempting to establish a socket connection. On succeeding, the code may execute similarly to the modeled example.

In order to implement this, I had to migrate from using sockaddr_in structures to addrinfo ones (as the latter data structures are what getaddrinfo() returns). Then I had to code the loop that went through the linked list in making the connections.

Tangentially, I also had to re-implement the itoa() function; The port parameter of getaddrinfo() is expected to be formatted as a string instead of an int. Unfortunately, gcc doesn’t apparently have itoa() in its standard C library. Therefore, I had to write a helper function to reformat the argument. Fun little exercise; refer to this link here on how it can be implemented.

Finally, I had to implement a method for sanitizing server responses back to the client. This required brushing up on how pointers and buffers worked.