Networking and Sockets

So far, your programs have lived alone. They read from files, talk to the keyboard, print to the screen. All by themselves.

But the real power of computers comes from connecting them. The internet is just millions of computers talking to each other.

This lesson teaches you how to write programs that communicate across a network.

What is Networking?

Networking is computers talking to each other. Your web browser talks to web servers. Your game talks to game servers. Your phone talks to your friend’s phone.

Every time you load a website, your computer:

  1. Sends a message asking for the page
  2. Waits for a response
  3. Receives the page data

That’s networking. Messages going back and forth.

IP Addresses: Phone Numbers for Computers

Every computer on a network has an IP address. It’s like a phone number - it tells other computers how to reach you.

An IP address looks like this: 192.168.1.100

Four numbers from 0 to 255, separated by dots.

Some special addresses:

  • 127.0.0.1 - “localhost” - your own computer talking to itself
  • 192.168.x.x - Usually computers on your home network
  • 10.x.x.x - Also local networks

When you type “google.com” in your browser, your computer looks up Google’s IP address (like looking up a phone number) and then connects to it.

Ports: Apartment Numbers

One computer might run many programs that all want to talk on the network. A web server, an email server, a game - all on the same machine.

Ports tell the computer which program should receive each message. Think of them like apartment numbers. The IP address gets you to the building, the port gets you to the right apartment.

Ports are just numbers from 0 to 65535.

Some well-known ports:

  • Port 80 - Web servers (HTTP)
  • Port 443 - Secure web servers (HTTPS)
  • Port 22 - SSH (secure remote login)
  • Port 25 - Email

When your browser connects to google.com, it’s really connecting to google.com:443 (the IP address of Google, port 443).

TCP vs UDP

There are two main ways to send data:

TCP (Transmission Control Protocol):

  • Like a phone call
  • You establish a connection first
  • Messages arrive in order
  • If something gets lost, it gets resent
  • Reliable but slower

UDP (User Datagram Protocol):

  • Like sending postcards
  • No connection needed
  • Messages might arrive out of order
  • If something gets lost, it’s gone
  • Fast but unreliable

Most networking uses TCP because you usually want reliability. UDP is used for things like video streaming and games where speed matters more than getting every single packet.

We’ll use TCP in this lesson.

Sockets: The Network Interface

A socket is your program’s connection to the network. Think of it like a phone - you use it to make calls and receive calls.

To make a network connection, you:

  1. Create a socket
  2. Connect to an address (for clients) or listen for connections (for servers)
  3. Send and receive data
  4. Close the socket when done

Including the Right Headers

Network programming needs special headers. On different systems:

Linux/macOS:

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

Windows:

#include <winsock2.h>
#include <ws2tcpip.h>
// Link with ws2_32.lib

Windows also needs initialization code before using sockets. We’ll show cross-platform examples that work on both.

A Simple Client

Here’s a program that connects to a server and sends a message:

// This example is for Linux/macOS
// See the Windows version below

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

int main(void) {
    // 1. Create a socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("Couldn't create socket");
        return 1;
    }

    // 2. Set up the server address
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(8080);  // Port 8080
    server.sin_addr.s_addr = inet_addr("127.0.0.1");  // localhost

    // 3. Connect to the server
    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
        perror("Couldn't connect");
        close(sock);
        return 1;
    }
    printf("Connected!\n");

    // 4. Send a message
    char *message = "Hello from the client!";
    send(sock, message, strlen(message), 0);
    printf("Sent: %s\n", message);

    // 5. Receive a response
    char buffer[1024] = {0};
    int bytes = recv(sock, buffer, sizeof(buffer) - 1, 0);
    if (bytes > 0) {
        printf("Received: %s\n", buffer);
    }

    // 6. Close the socket
    close(sock);
    return 0;
}

Let’s break down the new parts:

  • socket(AF_INET, SOCK_STREAM, 0) - Create a TCP socket. AF_INET means IPv4, SOCK_STREAM means TCP.
  • struct sockaddr_in - Holds an IP address and port
  • htons(8080) - Converts the port number to network format (computers store numbers differently, this makes it consistent)
  • inet_addr("127.0.0.1") - Converts the IP address string to a number
  • connect() - Connects to the server
  • send() - Sends data
  • recv() - Receives data
  • close() - Closes the socket

A Simple Server

A server waits for clients to connect:

// This example is for Linux/macOS

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

int main(void) {
    // 1. Create a socket
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock < 0) {
        perror("Couldn't create socket");
        return 1;
    }

    // Allow reusing the address (helpful during development)
    int opt = 1;
    setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 2. Set up our address
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(8080);
    server.sin_addr.s_addr = INADDR_ANY;  // Accept connections on any interface

    // 3. Bind to the port
    if (bind(server_sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
        perror("Couldn't bind");
        close(server_sock);
        return 1;
    }

    // 4. Start listening
    listen(server_sock, 5);  // Allow up to 5 waiting connections
    printf("Server listening on port 8080...\n");

    // 5. Accept a connection
    struct sockaddr_in client;
    socklen_t client_len = sizeof(client);
    int client_sock = accept(server_sock, (struct sockaddr *)&client, &client_len);
    if (client_sock < 0) {
        perror("Couldn't accept");
        close(server_sock);
        return 1;
    }
    printf("Client connected!\n");

    // 6. Receive a message
    char buffer[1024] = {0};
    int bytes = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
    if (bytes > 0) {
        printf("Received: %s\n", buffer);

        // 7. Send a response
        char *response = "Hello from the server!";
        send(client_sock, response, strlen(response), 0);
    }

    // 8. Close connections
    close(client_sock);
    close(server_sock);
    return 0;
}

The server flow is:

  1. Create a socket
  2. Bind to a port (claim that port number)
  3. Listen for connections
  4. Accept a connection (this creates a new socket just for that client)
  5. Send/receive data
  6. Close

Windows Version

Windows uses slightly different functions:

#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

int main(void) {
    // Windows needs initialization
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        printf("WSAStartup failed\n");
        return 1;
    }

    // Create socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        printf("Couldn't create socket\n");
        WSACleanup();
        return 1;
    }

    // Set up server address
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(8080);
    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    // Connect
    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
        printf("Couldn't connect\n");
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // Send and receive...

    // Clean up
    closesocket(sock);  // Windows uses closesocket, not close
    WSACleanup();       // Clean up Winsock
    return 0;
}

Key differences:

  • Need WSAStartup() at the beginning
  • Need WSACleanup() at the end
  • Use closesocket() instead of close()
  • Use SOCKET type instead of int

A Simple Chat Program

Let’s put it together into a chat program. Run the server first, then the client:

Server (server.c):

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

int main(void) {
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(8080),
        .sin_addr.s_addr = INADDR_ANY
    };

    bind(server_sock, (struct sockaddr *)&server, sizeof(server));
    listen(server_sock, 1);
    printf("Waiting for connection...\n");

    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int client_sock = accept(server_sock, (struct sockaddr *)&client, &len);
    printf("Client connected!\n\n");

    char buffer[1024];
    while (1) {
        // Wait for client message
        memset(buffer, 0, sizeof(buffer));
        int bytes = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
        if (bytes <= 0) break;
        printf("Client: %s\n", buffer);

        // Send our response
        printf("You: ");
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strcspn(buffer, "\n")] = 0;  // Remove newline

        if (strcmp(buffer, "quit") == 0) break;
        send(client_sock, buffer, strlen(buffer), 0);
    }

    close(client_sock);
    close(server_sock);
    printf("Chat ended.\n");
    return 0;
}

Client (client.c):

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

int main(void) {
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(8080),
        .sin_addr.s_addr = inet_addr("127.0.0.1")
    };

    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
        perror("Couldn't connect");
        return 1;
    }
    printf("Connected to server!\n\n");

    char buffer[1024];
    while (1) {
        // Send our message
        printf("You: ");
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strcspn(buffer, "\n")] = 0;

        if (strcmp(buffer, "quit") == 0) break;
        send(sock, buffer, strlen(buffer), 0);

        // Wait for response
        memset(buffer, 0, sizeof(buffer));
        int bytes = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (bytes <= 0) break;
        printf("Server: %s\n", buffer);
    }

    close(sock);
    printf("Chat ended.\n");
    return 0;
}

To use:

  1. Compile both: gcc -o server server.c and gcc -o client client.c
  2. Run the server in one terminal: ./server
  3. Run the client in another terminal: ./client
  4. Type messages! Type “quit” to exit.

Error Handling

Real network code needs lots of error checking. Connections drop, servers go away, data gets corrupted.

int bytes = recv(sock, buffer, sizeof(buffer), 0);
if (bytes < 0) {
    perror("Error receiving");
} else if (bytes == 0) {
    printf("Connection closed by other side\n");
} else {
    // Process the data
}

Always check return values. Network operations fail more often than file operations.

What You’ve Learned

  • IP addresses identify computers
  • Ports identify programs on a computer
  • TCP gives reliable, ordered delivery
  • Sockets are your interface to the network
  • Servers listen and accept connections
  • Clients connect to servers
  • send() and recv() transfer data

The Bigger Picture

The internet is built on these simple ideas. Every website, every online game, every chat app - they all use sockets underneath.

HTTP (the web) is just text messages sent over TCP connections. When your browser loads a page, it’s doing exactly what we did: connect, send a request, receive a response.

Now you know how it works.

Try It Yourself

  1. Modify the chat program so the server can talk to multiple clients
  2. Make a program that downloads a webpage (connect to port 80, send an HTTP request)
  3. Create a simple file transfer program
  4. Add timestamps to the chat messages

Common Mistakes

  • Forgetting to call htons() on port numbers
  • Not checking return values (network operations fail!)
  • Forgetting Windows needs WSAStartup() and WSACleanup()
  • Using close() on Windows (need closesocket())
  • Assuming data arrives all at once (it might come in chunks)

Next Up

In Part 24, we’ll learn about command line arguments - how to make your programs accept input when they start, like real command line tools.


Enjoyed This?

If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.