🔌 Part 1: Building a Simple TCP Client in C

As engineers, we often work several layers above the network stack—abstracted by frameworks, SDKs, and libraries. Recently, I decided to go back to basics and explore socket programming in C by building a simple TCP client from scratch.

The goal? Connect to a server send a raw HTTP request, and read the response—all using low-level system calls.

🧠 What I Learned (Client Side)

Creating a socket

  • Used socket(AF_INET, SOCK_STREAM, 0) to create a TCP socket with IPv4.

Preparing the server address

  • inet_addr() to convert IP string to binary
  • htons() to convert port to network byte order

Connecting to the server

  • Used connect() with a typecast to struct sockaddr*
  • Passed in a sockaddr_in struct with Server’s IP and port 80

Sending and receiving data

  • Used write() to send an HTTP GET request
  • Used read() to fetch the response from the server

Man pages were essential

  • Especially man 2 socket, man 2 connect, man 2 read, etc.

🧪 Sample Code

/* tcp_client.c */
#include 

#include 
#include 
#include 

#include 

#include 

#define IP "142.250.70.100"
#define PORT 80

int main()
{
        int s;
        struct sockaddr_in sserver;
        char* data;
        char buf[10240];

        data = "GET /webhp HTTP/1.0\n\n";

        s = socket(AF_INET, SOCK_STREAM, 0);
        if(s < 0)
        {
                printf("socket()\n");
                return -1;
        }

        sserver.sin_addr.s_addr = inet_addr(IP);
        sserver.sin_port = htons(PORT);
        sserver.sin_family = AF_INET;

        if(connect(s, (struct sockaddr*)&sserver, sizeof(sserver)) != 0)
        {
                printf("connect()\n");
                close(s);
                return -1;
        }

        write(s, data, strlen(data));
        read(s, buf, 10239);
        close(s);

        printf("\n%s\n", buf);
        return 0;
}

🖥️ Part 2: Writing a Simple TCP Server in C

In Part 1 (link to Part 1), I shared how I built a simple TCP client in C to connect to a server, send an HTTP request, and read the response. This time, I flipped the role—writing a TCP server from scratch that listens on port 8181, accepts client connections, and responds with a minimal HTTP reply.

🔄 What the Server Does

This TCP server:

  • Listens on 0.0.0.0:8181
  • Accepts incoming TCP connections
  • Reads the client’s request
  • Responds with a static HTTP response (hello)
  • Sleeps for 2 minutes (120s) before replying—to simulate a long-running process

🧠 What I Learned (Server Side)

Creating and binding a server socket

  • Used socket(AF_INET, SOCK_STREAM, 0) to create a TCP socket
  • Used a sockaddr_in struct to bind the server to port 8181 on all interfaces using bind()

Listening for incoming connections

  • Called listen(s, 1) to queue one pending connection
  • Interestingly, the backlog queue didn’t behave as expected—still exploring why that might be OS or implementation dependent

Accepting clients using accept()

  • Accepts a new connection and returns a client socket descriptor
  • accept() also fills in the client’s socket address.

Simple client-server interaction

  • Read the incoming request using read()
  • Delayed for 2 minutes to simulate latency
  • Replied with a static HTTP/1.1 200 OK response using write()

🧪 Sample Code

/* tcp_server.c */
#include

#include 
#include 
#include 

#include 

#define PORT 8181

int main()
{
        int c, s;
        struct sockaddr_in cli, srv;
        char buf[1024];
        char* data;
        socklen_t addrlen;

        addrlen = 0;

        data = "HTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length: 5\n\nhello";

        memset(&cli, 0, sizeof(cli));
        memset(&srv, 0, sizeof(srv));

        s = socket(AF_INET, SOCK_STREAM, 0);

        if(s == -1)
        {
                printf("socket()\n");
                return -1;
        }

        srv.sin_family = AF_INET;
        srv.sin_port = htons(PORT);
        srv.sin_addr.s_addr = 0;

        if(bind(s, (struct sockaddr*)&srv, sizeof(srv)))
        {
                printf("bind()\n");
                close(s);
                return -1;
        }

        if(listen(s, 1))
        {
                printf("listen()\n");
                close(s);
                return -1;
        }

        printf("Listening on 0.0.0.0:%d\n", PORT);

        while(1)
        {
                addrlen = 0;
                c = accept(s, (struct sockaddr *)&cli, &addrlen);
                if(c == -1)
                {
                        printf("accept()\n");
                        close(s);
                        return -1;
                }

                printf("client connected\n");

                read(c, buf, 1023);

                sleep(120);

                write(c, data, strlen(data));

                close(c);
        }

        close(s);
        return 0;
}

Thanks to Jonas Birch for his excellent instructional videos. His ability to explain system-level concepts clearly was a huge help during this learning phase.