The Ultimate Guide to TCP Client-Server Programming in C [Code]

TCP client-server programming in C is a critical skill for systems developers, backend engineers, and anyone dealing with low-level networking. It’s the foundation of everything from chat servers and IoT systems to custom network daemons.

This guide will take you from basic theory to a multithreaded TCP server, explaining every step in between.

What is TCP Client-Server Programming?

In TCP client-server architecture, one program (server) waits for connections, and others (clients) initiate communication.

  • TCP ensures reliable, ordered delivery of data.
  • Server runs continuously, listens on a port.
  • Client connects to the server, sends and receives data.

This is connection-oriented programming—both sides perform a “handshake” to establish a session.

TCP 3-way handshake
TCP 3-way handshake

Understanding TCP/IP and Sockets

Before writing code, let’s understand what happens under the hood.

LayerProtocolPurpose
ApplicationHTTP, FTP, CustomYou write this!
TransportTCPReliable byte stream
NetworkIPRouting and addressing
LinkEthernet, Wi-FiHardware transmission

Sockets are programming abstractions to communicate between these layers.

In C, we use system calls like:

  • socket(): create a socket
  • bind(): assign IP/port
  • listen(): wait for connections
  • accept(): accept a connection
  • connect(): client connects to server
  • send()/recv() or read()/write(): data exchange

ALSO READ:


Socket API in C (Cheat Sheet)

FunctionRole
socket()Create socket
bind()Bind socket to IP/port
listen()Listen for incoming connections
accept()Accept a new client
connect()Initiate a connection
send()/recv()Send/receive data
close()Close socket

All of them return -1 on failure and set errno.

Simple TCP Server Code

// tcp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 9090
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    char buffer[BUFFER_SIZE] = {0};
    int opt = 1;
    socklen_t addrlen = sizeof(address);

    // 1. Create socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. Set socket options (optional but recommended)
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 3. Bind
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    bind(server_fd, (struct sockaddr *)&address, sizeof(address));

    // 4. Listen
    listen(server_fd, 5);
    printf("Server is listening on port %d...\n", PORT);

    // 5. Accept
    new_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen);
    read(new_socket, buffer, BUFFER_SIZE);
    printf("Client says: %s\n", buffer);

    // 6. Reply
    send(new_socket, "Welcome to the server!", 23, 0);

    // 7. Close
    close(new_socket);
    close(server_fd);
    return 0;
}

Simple TCP Client Code

// tcp_client.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 9090

int main() {
    int sock;
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};
    char *msg = "Hello, Server!";

    sock = socket(AF_INET, SOCK_STREAM, 0);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);

    connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    send(sock, msg, strlen(msg), 0);
    read(sock, buffer, 1024);
    printf("Server reply: %s\n", buffer);

    close(sock);
    return 0;
}

How to Compile and Run

gcc tcp_server.c -o server
gcc tcp_client.c -o client

Open two terminals and run the following commands:

  1. Run the server: ./server
  2. Run the client: ./client
The terminals running both the client and server program
The terminals running both the client and server program

Handling Multiple Clients with Threads

Let’s make it a true server that can handle many clients simultaneously using POSIX threads.

// multi_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>

#define PORT 9090

void* handle_client(void* socket_desc) {
    int sock = *(int*)socket_desc;
    char buffer[1024];
    read(sock, buffer, 1024);
    printf("Client: %s\n", buffer);
    send(sock, "Response from threaded server", 30, 0);
    close(sock);
    free(socket_desc);
    return NULL;
}

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    socklen_t addrlen = sizeof(address);
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    bind(server_fd, (struct sockaddr*)&address, sizeof(address));
    listen(server_fd, 10);
    printf("Threaded server listening on port %d...\n", PORT);

    while (1) {
        new_socket = accept(server_fd, (struct sockaddr*)&address, &addrlen);
        int *new_sock = malloc(sizeof(int));
        *new_sock = new_socket;

        pthread_t thread_id;
        pthread_create(&thread_id, NULL, handle_client, (void*)new_sock);
        pthread_detach(thread_id);  // auto-free thread
    }

    close(server_fd);
    return 0;
}
The terminal shows a multi-threaded server with two client terminals.
The terminal shows a multi-threaded server with two client terminals.

Best Practices & Error Handling

  • Always check return values of socket functions.
  • Use perror() or strerror(errno) for diagnostics.
  • Use setsockopt() to avoid “address already in use” errors.
  • Gracefully close sockets using shutdown() + close().

Real-World Applications

Use CaseHow
Chat appsSocket server with message loop
IoT gatewayClients are sensors reporting data
Multiplayer gamesGame loop over TCP
Remote shellsUse bidirectional send/recv
File transfer toolsRead and write byte streams

Conclusion

You’ve just completed the ultimate guide to TCP client-server programming in C. From theory to a real-world multithreaded server, you’ve seen how TCP sockets are used to build reliable communication systems.

Now It’s Your Turn:

  • Modify the client to send user input
  • Make the server broadcast to all connected clients
  • Add logging, message timestamps, and authentication

Read More

Leave a Reply

Your email address will not be published. Required fields are marked *