Master Linux TCP: System Calls, Handshakes, and Real‑World Code

This article provides a comprehensive guide to Linux TCP development, explaining the role of system calls, the three‑way handshake and four‑way termination, detailing core socket functions such as socket, bind, listen, accept, connect, read/write, recv/send, and includes complete example code for building a simple TCP server and client with troubleshooting tips.

Deepin Linux
Deepin Linux
Deepin Linux
Master Linux TCP: System Calls, Handshakes, and Real‑World Code

Linux TCP Development and System Calls

System calls provide the bridge between user space and the kernel, exposing standardized interfaces for creating sockets, binding, listening, accepting, connecting, and transferring data. TCP relies on these calls to establish reliable, connection‑oriented communication.

Key System Call Functions

socket Creates a network endpoint.

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

/* Example */
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // returns a non‑negative descriptor or -1 on error (e.g., EACCES)

bind Associates a socket with a local address and port.

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

/* sockaddr_in layout */
struct sockaddr_in {
    sa_family_t   sin_family;   // AF_INET
    in_port_t    sin_port;    // htons(port)
    struct in_addr sin_addr;   // inet_pton(...)
    char          sin_zero[8];
};

listen Marks a bound socket as passive, ready to accept connections.

#include <sys/socket.h>
int listen(int sockfd, int backlog); // backlog commonly 128 or SOMAXCONN

accept Accepts an incoming connection and returns a new descriptor for the client.

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

/* Returns a new fd; original sockfd continues listening */

connect Initiates a connection from the client side (performs the TCP three‑way handshake internally).

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

read / write Generic I/O functions that also work on sockets.

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

recv / send Socket‑specific I/O with flags (e.g., MSG_DONTWAIT, MSG_NOSIGNAL).

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

close Releases a file descriptor.

#include <unistd.h>
int close(int fd);

For half‑close, shutdown(fd, SHUT_WR) disables further writes while still allowing reads.

TCP Handshake Overview

The three‑way handshake consists of:

Client sends SYN (random Seq=x).

Server replies with SYN+ACK (Seq=y, Ack=x+1).

Client sends ACK (Ack=y+1). Both sides enter ESTABLISHED state.

The four‑step termination (FIN/ACK exchange) ensures each direction is closed independently, preventing data loss.

Practical Example – Simple TCP Server

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

#define PORT 8888
#define MAX_BUFFER_SIZE 1024

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) { perror("socket creation failed"); return 1; }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        perror("bind failed"); close(sockfd); return 1; }

    if (listen(sockfd, 128) == -1) { perror("listen failed"); close(sockfd); return 1; }

    struct sockaddr_in cli_addr; socklen_t cli_len = sizeof(cli_addr);
    int connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
    if (connfd == -1) { perror("accept failed"); close(sockfd); return 1; }

    char buffer[MAX_BUFFER_SIZE] = {0};
    ssize_t n = recv(connfd, buffer, MAX_BUFFER_SIZE-1, 0);
    if (n <= 0) { perror("recv failed"); close(connfd); close(sockfd); return 1; }
    buffer[n] = '\0';
    printf("Received from client: %s
", buffer);

    const char *resp = "Message received successfully!";
    if (send(connfd, resp, strlen(resp), 0) == -1) { perror("send failed"); }

    close(connfd);
    close(sockfd);
    return 0;
}

Practical Example – Simple TCP Client

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

#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define MAX_BUFFER_SIZE 1024

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) { perror("socket creation failed"); return 1; }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
        perror("invalid server IP"); close(sockfd); return 1; }

    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        perror("connect failed"); close(sockfd); return 1; }

    const char *msg = "Hello, server!";
    if (send(sockfd, msg, strlen(msg), 0) == -1) { perror("send failed"); close(sockfd); return 1; }

    char buf[MAX_BUFFER_SIZE] = {0};
    ssize_t n = recv(sockfd, buf, MAX_BUFFER_SIZE-1, 0);
    if (n <= 0) { perror("recv failed"); close(sockfd); return 1; }
    buf[n] = '\0';
    printf("Received from server: %s
", buf);

    close(sockfd);
    return 0;
}

Compilation and Execution

Compile with:

gcc -o server server.c
gcc -o client client.c

Run the server first ( ./server) and then the client ( ./client). The output confirms successful connection, data exchange, and proper shutdown.

Common Issues and Solutions

Port conflicts Identify the owning process with netstat -tulnp | grep PORT or lsof -i:PORT , then terminate it ( kill PID ) or choose a different port.

Connection timeouts Adjust timeout values based on workload (e.g., 100 ms for high‑throughput services, a few seconds for typical apps). Verify network health with ping , check firewall rules, and ensure proper routing.

Data transmission anomalies Always check the return values of send and recv . Implement loops that continue until the expected byte count is transferred (see send_all / recv_all patterns). Resize buffers as needed and consider zero‑copy techniques for large payloads.

Robust Send/Receive Helpers

#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#define MAX_BUFFER_SIZE 1024

ssize_t send_all(int sockfd, const void *buf, size_t len) {
    size_t total = 0;
    const char *ptr = (const char *)buf;
    while (total < len) {
        ssize_t sent = send(sockfd, ptr + total, len - total, 0);
        if (sent == -1) { perror("send failed"); return -1; }
        total += sent;
    }
    return total;
}

ssize_t recv_all(int sockfd, void *buf, size_t len) {
    size_t total = 0;
    char *ptr = (char *)buf;
    while (total < len) {
        ssize_t recvd = recv(sockfd, ptr + total, len - total, 0);
        if (recvd == -1) { perror("recv failed"); return -1; }
        if (recvd == 0) break; // peer closed
        total += recvd;
    }
    return total;
}

These helpers guarantee that all bytes are transmitted or received, which is essential because a single send or recv may process fewer bytes than requested.

Half‑Close Example

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

int main() {
    int sockfd = /* obtained via accept or connect */;
    const char *msg = "Server finished sending data.";
    if (send(sockfd, msg, strlen(msg), 0) == -1) { perror("send"); close(sockfd); return 1; }
    if (shutdown(sockfd, SHUT_WR) == -1) { perror("shutdown"); close(sockfd); return 1; }
    printf("Write side closed, waiting for client data...
");
    char buf[1024] = {0};
    ssize_t n = recv(sockfd, buf, sizeof(buf)-1, 0);
    if (n > 0) {
        buf[n] = '\0';
        printf("Received after shutdown: %s
", buf);
    }
    close(sockfd);
    return 0;
}

Using shutdown(sockfd, SHUT_WR) closes only the write direction, allowing the server to continue reading client data—a useful pattern for request/response protocols.

Summary

Linux TCP development hinges on a small set of system calls ( socket, bind, listen, accept, connect, read/write or recv/send, close / shutdown). Understanding their prototypes, error handling, and the TCP state machine (three‑way handshake, four‑step termination) enables developers to build reliable network services. Proper buffer management, looped I/O, and handling of common pitfalls such as port conflicts, timeouts, and partial transfers are essential for production‑grade applications.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CtcpLinuxsocket programmingsystem calls
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.