πŸ‡―πŸ‡΅ ζ—₯本θͺž | πŸ‡ΊπŸ‡Έ English
Advertisement

Simple TCP Client & Server in C

Build the minimal programs that exchange text over a TCP socket β€” server side and client side.

What is client & server?

Network programs come in two roles: the server waits for connections, and the client reaches out to connect. We first run both on the same computer and make them talk.
Client
echo_client
⇄
TCP:12345
Server
echo_server

What we will build (echo server)

Why echo first? It is the shortest way to prove that both send and receive work. A classic first program for network beginners.

API flow (both sides)

The server and client use slightly different calls. Memorize them side by side.
Server side
  1. socket() β€” create socket
  2. bind() β€” attach to a port
  3. listen() β€” start listening
  4. accept() β€” accept a connection
  5. recv() / send()
  6. close()
Client side
  1. socket() β€” create socket
  2. connect() β€” reach the server
  3. send() / recv()
  4. close()
Key idea: bind/listen/accept are server-only, connect is client-only. Everything else is shared.

Server program (echo_server.c)

A minimal server that echoes back whatever the client sends. It serves one client and then exits.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 12345
#define BUF_SIZE 1024

int main(void) {
    // 1. create socket
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) { perror("socket"); return 1; }

    // (recommended) allow quick reuse of the port
    int yes = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

    // 2. prepare address to listen on
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  // any local IP
    addr.sin_port = htons(PORT);

    // 3. bind β€” attach the socket to the port
    if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind"); return 1;
    }

    // 4. listen β€” start accepting connections
    if (listen(listen_fd, 1) < 0) {
        perror("listen"); return 1;
    }
    printf("Listening on port %d...\n", PORT);

    // 5. accept one client
    struct sockaddr_in client_addr;
    socklen_t clen = sizeof(client_addr);
    int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &clen);
    if (conn_fd < 0) { perror("accept"); return 1; }
    printf("Client connected: %s\n", inet_ntoa(client_addr.sin_addr));

    // 6. echo loop β€” read what arrives, send it back
    char buf[BUF_SIZE];
    ssize_t n;
    while ((n = recv(conn_fd, buf, BUF_SIZE - 1, 0)) > 0) {
        buf[n] = '\0';
        printf("received: %s", buf);
        send(conn_fd, buf, n, 0);  // send the same bytes back
    }

    // 7. clean up
    close(conn_fd);
    close(listen_fd);
    printf("Connection closed\n");
    return 0;
}
INADDR_ANY means "accept on any of my local IPs". Fine for development. To restrict to localhost only, use inet_addr("127.0.0.1").

Client program (echo_client.c)

Reads one line from stdin, sends it to the server, and prints the echoed reply.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 12345
#define BUF_SIZE 1024

int main(void) {
    // 1. create socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) { perror("socket"); return 1; }

    // 2. target address: localhost:12345
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 3. connect β€” reach the server
    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect"); return 1;
    }
    printf("Connected. Type a message then Enter:\n");

    // 4. read one line from stdin and send it
    char msg[BUF_SIZE];
    if (fgets(msg, BUF_SIZE, stdin) == NULL) return 1;
    send(sockfd, msg, strlen(msg), 0);

    // 5. receive the reply
    char buf[BUF_SIZE];
    ssize_t n = recv(sockfd, buf, BUF_SIZE - 1, 0);
    if (n > 0) {
        buf[n] = '\0';
        printf("Server replied: %s", buf);
    }

    // 6. close
    close(sockfd);
    return 0;
}
Note: we connect to 127.0.0.1 (localhost), so run the server on the same machine. To reach a different machine, use its IP address.

Build & run

Open two terminals. Start the server in one, the client in the other.

Terminal 1 (server)

$ gcc echo_server.c -o echo_server $ ./echo_server Listening on port 12345... Client connected: 127.0.0.1 received: hello Connection closed

Terminal 2 (client)

$ gcc echo_client.c -o echo_client $ ./echo_client Connected. Type a message then Enter: hello Server replied: hello
Troubleshooting:
β€’ bind: Address already in use β€” another process holds the port. Wait a moment, or change the port.
β€’ connect: Connection refused β€” the server is not running, or the port/IP is wrong.
β€’ Connects but no reply β€” make sure you started the client after the server reached accept.

Challenges

Challenge 1: Uppercase server
Instead of echoing, convert the received string to uppercase before sending it back. Use toupper from <ctype.h>.
Challenge 2: Repeated exchanges
Modify both sides so they keep exchanging messages until the client sends "quit".
Challenge 3: Multiple clients
After accept, call fork() so each client is handled by a child process while the parent loops back to accept.
Challenge 4: Port as argument
Let the server take the port as a command-line argument: ./echo_server 8080. Use argv[1]. See the argc/argv lesson.
Next: Build an HTTP client β†’
Share this article
Share on X