Skip to content

C Network Sockets — TCP/IP Socket Programming with Berkeley Sockets

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C Network Sockets. We cover key concepts, practical examples, and best practices to help you master this topic.

C network socket programming using the Berkeley sockets API enables TCP/IP communication between processes over a network, with system calls for creating sockets (socket), binding addresses (bind), listening for connections (listen), accepting clients (accept), and connecting to servers (connect).

What You Will Learn

  • Creating TCP sockets with socket()
  • Binding and listening with bind() and listen()
  • Accepting client connections with accept()
  • Connecting to servers with connect()
  • Sending and receiving data with send() and recv()
  • Handling multiple connections sequentially
  • IPv4 and IPv6 address structures

Why It Matters

Network programming is the backbone of the internet. Every web server, database client, chat application, and IoT device communicates over sockets. Understanding the socket API lets you build network services, custom protocols, and Distributed Systems. Durga Antivirus Pro uses sockets to download signature updates from the update server and to communicate between the scan daemon and the GUI over localhost.

Real-World Use

A system monitoring agent runs on 100 servers. It opens a TCP connection to the central monitoring server every 60 seconds, sends CPU/memory/disk metrics as a JSON string, and closes the connection. The central server runs a socket server that accepts all 100 agents, processes their data, and stores it.

Learning Path

flowchart LR
  A[Multithreading] --> B[Network Sockets\nYou are here]
  B --> C[I/O Multiplexing]
  style B fill:#f90,color:#fff

TCP Echo Server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define BUFFER_SIZE 1024

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

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

    // 2. Allow port reuse (avoids "Address already in use")
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(1);
    }

    // 3. Bind to address and port
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(1);
    }

    // 4. Listen for connections
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(1);
    }

    printf("Echo server listening on port %d\n", PORT);

    // 5. Accept and handle connections
    while (1) {
        client_fd = accept(server_fd, (struct sockaddr*)&address, &addrlen);
        if (client_fd < 0) {
            perror("accept");
            continue;
        }

        printf("Client connected\n");

        // Receive and echo back
        int valread = read(client_fd, buffer, BUFFER_SIZE - 1);
        if (valread > 0) {
            buffer[valread] = '\0';
            printf("Received: %s\n", buffer);
            send(client_fd, buffer, valread, 0);
            printf("Echoed back\n");
        }

        close(client_fd);
    }

    close(server_fd);
    return 0;
}

TCP Echo Client

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

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE] = {0};

    // 1. Create socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    // 2. Configure server address
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);

    if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
        perror("Invalid address");
        return 1;
    }

    // 3. Connect to server
    if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect failed");
        return 1;
    }

    // 4. Send and receive
    const char *message = "Hello, server!";
    send(sock, message, strlen(message), 0);
    printf("Sent: %s\n", message);

    int valread = read(sock, buffer, BUFFER_SIZE - 1);
    buffer[valread] = '\0';
    printf("Received: %s\n", buffer);

    close(sock);
    return 0;
}

Compile and run:

gcc -o echo_server echo_server.c
gcc -o echo_client echo_client.c
./echo_server &
./echo_client

Output (client):

Sent: Hello, server!
Received: Hello, server!

HTTP Request Client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define BUFFER_SIZE 4096

int http_get(const char *host, const char *path) {
    int sock;
    struct hostent *server;
    struct sockaddr_in addr;
    char request[1024];
    char response[BUFFER_SIZE];

    // Resolve hostname
    server = gethostbyname(host);
    if (server == NULL) {
        fprintf(stderr, "No such host: %s\n", host);
        return -1;
    }

    // Create socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(80);
    memcpy(&addr.sin_addr, server->h_addr_list[0], server->h_length);

    // Connect
    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect");
        close(sock);
        return -1;
    }

    // Send HTTP GET request
    snprintf(request, sizeof(request),
             "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
             path, host);
    send(sock, request, strlen(request), 0);

    // Read response
    int total = 0;
    int n;
    while ((n = read(sock, response + total, sizeof(response) - total - 1)) > 0) {
        total += n;
    }
    response[total] = '\0';

    printf("%s\n", response);
    close(sock);
    return 0;
}

int main() {
    http_get("example.com", "/");
    return 0;
}

Non-blocking Sockets

Set a socket to non-blocking mode for async operations:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main() {
    int server_fd;
    struct sockaddr_in addr;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    set_nonblocking(server_fd);

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8080);
    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(server_fd, 5);

    printf("Non-blocking server on port 8080\n");

    while (1) {
        struct sockaddr_in client;
        socklen_t client_len = sizeof(client);
        int client_fd = accept(server_fd, (struct sockaddr*)&client, &client_len);

        if (client_fd >= 0) {
            printf("Client accepted\n");
            close(client_fd);
        } else if (errno == EWOULDBLOCK || errno == EAGAIN) {
            // No pending connections -- do other work
            printf("No client yet, doing other work...\n");
            usleep(500000);
        } else {
            perror("accept error");
        }
    }

    close(server_fd);
    return 0;
}

Address Resolution with getaddrinfo

The modern way to resolve hostnames and create sockets (IPv4/IPv6 agnostic):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>

int connect_to_host(const char *host, const char *port) {
    struct addrinfo hints, *result, *rp;
    int sock;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;    // IPv4 or IPv6
    hints.ai_socktype = SOCK_STREAM; // TCP

    int s = getaddrinfo(host, port, &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        return -1;
    }

    // Try each address until one works
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sock == -1) continue;

        if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) {
            break;  // Success
        }

        close(sock);
    }

    if (rp == NULL) {
        fprintf(stderr, "Could not connect\n");
        freeaddrinfo(result);
        return -1;
    }

    freeaddrinfo(result);
    return sock;
}

int main() {
    int sock = connect_to_host("example.com", "80");
    if (sock >= 0) {
        printf("Connected to example.com:80\n");
        close(sock);
    }
    return 0;
}

Common Mistakes

  1. Forgetting htons/htonl: Network byte order is big-endian. Port numbers and IP addresses must be converted with htons() and htonl(). Forgetting this causes connections to the wrong port.

  2. Not checking all return values: socket(), bind(), listen(), accept(), connect(), send(), recv() can all fail. Every call must be checked for errors.

  3. Ignoring partial sends/receives: send() and recv() may not send/receive all bytes in one call. Always loop until all data is transferred.

  4. Not setting SO_REUSEADDR: Without this, restarting the server after a crash gives "Address already in use" for several minutes while the port is in TIME_WAIT state.

  5. Assuming getaddrinfo succeeds: DNS can fail (network down, host not found). Always check the return value of getaddrinfo and use gai_strerror for diagnostics.

  6. Blocking on accept with no clients: The accept() call blocks until a client connects. Use non-blocking sockets or I/O multiplexing (select, poll, epoll) for servers that must do other work.

  7. Not closing sockets: Every open socket consumes a file descriptor. There is a system-wide limit (typically 1024). Close sockets when done.

Practice Questions

  1. What is the purpose of htons() and ntohs()?
  2. What does SO_REUSEADDR do and why is it important for servers?
  3. How does the three-way TCP handshake relate to connect() and accept()?
  4. Why might recv() return fewer bytes than requested?
  5. Challenge: Write a simple HTTP server that serves static files from a directory. Support GET requests for .html, .css, and .js files. Return proper Content-Type headers and 404 for missing files. Use non-blocking sockets to handle multiple connections.

Mini Project

Build a chat room server and client:

  • Server: Accepts multiple clients (using pthreads or fork), maintains a list of connected users, broadcasts messages to all clients, handles disconnect gracefully
  • Client: Connects to the server, sends messages from stdin, receives and displays messages from other users in real time
  • Protocol: Each message starts with a 4-byte length prefix (network byte order), followed by the message text
  • Commands: /nick <name> to set nickname, /list to list users, /quit to disconnect
  • Handle: client disconnect detection (recv returns 0), message fragmentation, and long messages (up to 64 KB)
  • Test with 3+ clients simultaneously

FAQ

What is the difference between TCP and UDP sockets?

TCP (SOCK_STREAM) provides reliable, ordered, connection-oriented delivery. UDP (SOCK_DGRAM) provides unreliable, unordered, connectionless delivery. Use TCP for web, email, file transfer. Use UDP for real-time audio/video, DNS, games.

What is the maximum size of a TCP message?

TCP has no message boundaries -- it is a stream protocol. You can send any amount and it arrives as a stream of bytes. If you need message boundaries, implement them yourself with length prefixes or delimiters.

How do I handle multiple clients?

Three approaches: (1) fork a child process per client, (2) create a thread per client with pthreads, (3) use I/O multiplexing (select/poll/epoll) in a single thread.

What is the purpose of the backlog parameter in listen()?

It sets the maximum length of the queue of pending connections. If multiple clients connect simultaneously while the server is busy, they wait in this queue. Typical values are 5-128.

Why do I get 'Connection refused'?

Either the server is not running, the port is wrong, a firewall is blocking the connection, or the server's backlog queue is full. Use telnet or netcat to test basic connectivity.

What is Next

Proceed to I/O Multiplexing to learn how to handle multiple sockets efficiently with select, poll, and epoll. Then build project applications starting with Project Calculator.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C