Cliente & Servidor TCP Simples em C

Construa os programas mínimos que trocam texto por um socket TCP — lado servidor e lado cliente.

O que é cliente & servidor?

Programas de rede assumem um de dois papéis: o servidor aguarda conexões, e o cliente busca se conectar. Vamos começar rodando ambos no mesmo computador e fazer os dois conversarem.
Cliente
echo_client

TCP:12345
Servidor
echo_server

O que vamos construir (um servidor de eco)

Por que eco primeiro? É o programa mais curto que prova que envio e recebimento funcionam — um primeiro programa clássico para iniciantes em rede.

Fluxo da API (os dois lados)

Servidor e cliente usam chamadas ligeiramente diferentes. Ajuda aprender os dois lado a lado.
Lado servidor
  1. socket() — cria socket
  2. bind() — associa a uma porta
  3. listen() — começa a escutar
  4. accept() — aceita uma conexão
  5. recv() / send()
  6. close()
Lado cliente
  1. socket() — cria socket
  2. connect() — alcança o servidor
  3. send() / recv()
  4. close()
Ideia chave: bind/listen/accept são exclusivos do servidor; connect é exclusivo do cliente. Todo o resto é compartilhado.

Programa servidor (echo_server.c)

Um servidor mínimo que devolve o que o cliente enviar. Atende um cliente, depois encerra.
#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. cria o socket
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) { perror("socket"); return 1; }

    // (recomendado) permite reutilizar a porta rapidamente
    int yes = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

    // 2. prepara o endereço em que vai escutar
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  // qualquer IP local
    addr.sin_port = htons(PORT);

    // 3. bind — associa o socket à porta
    if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind"); return 1;
    }

    // 4. listen — começa a aceitar conexões
    if (listen(listen_fd, 1) < 0) {
        perror("listen"); return 1;
    }
    printf("Escutando na porta %d...\n", PORT);

    // 5. aceita um cliente
    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("Cliente conectado: %s\n", inet_ntoa(client_addr.sin_addr));

    // 6. laço de eco — lê o que chega e envia de volta
    char buf[BUF_SIZE];
    ssize_t n;
    while ((n = recv(conn_fd, buf, BUF_SIZE - 1, 0)) > 0) {
        buf[n] = '\0';
        printf("recebido: %s", buf);
        send(conn_fd, buf, n, 0);  // envia os mesmos bytes de volta
    }

    // 7. limpeza
    close(conn_fd);
    close(listen_fd);
    printf("Conexão fechada\n");
    return 0;
}
INADDR_ANY significa "aceite em qualquer IP local meu" — serve para desenvolvimento. Para restringir só ao localhost, use inet_addr("127.0.0.1").

Programa cliente (echo_client.c)

Lê uma linha do stdin, envia para o servidor e imprime a resposta ecoada.
#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. cria o socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) { perror("socket"); return 1; }

    // 2. endereço alvo: 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 — alcança o servidor
    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect"); return 1;
    }
    printf("Conectado. Digite uma mensagem e Enter:\n");

    // 4. lê uma linha do stdin e envia
    char msg[BUF_SIZE];
    if (fgets(msg, BUF_SIZE, stdin) == NULL) return 1;
    send(sockfd, msg, strlen(msg), 0);

    // 5. recebe a resposta
    char buf[BUF_SIZE];
    ssize_t n = recv(sockfd, buf, BUF_SIZE - 1, 0);
    if (n > 0) {
        buf[n] = '\0';
        printf("Servidor respondeu: %s", buf);
    }

    // 6. fecha
    close(sockfd);
    return 0;
}
Nota: estamos conectando em 127.0.0.1 (localhost), então rode o servidor na mesma máquina. Para alcançar uma máquina diferente, use o IP dela.

Compilar & executar

Abra dois terminais. Inicie o servidor em um e o cliente no outro.

Terminal 1 (servidor)

$ gcc echo_server.c -o echo_server $ ./echo_server Escutando na porta 12345... Cliente conectado: 127.0.0.1 recebido: hello Conexão fechada

Terminal 2 (cliente)

$ gcc echo_client.c -o echo_client $ ./echo_client Conectado. Digite uma mensagem e Enter: hello Servidor respondeu: hello
Solução de problemas:
bind: Address already in use — outro processo ainda segura a porta. Espere um pouco ou escolha outra porta.
connect: Connection refused — o servidor não está rodando, ou a porta/IP está errado.
• Conecta mas sem resposta — certifique-se de que iniciou o cliente depois que o servidor chegou no accept.

Desafios

Desafio 1: Servidor que converte para maiúsculas
Em vez de ecoar, converta a string recebida para maiúsculas antes de enviar de volta. Use toupper de <ctype.h>.
Desafio 2: Trocas repetidas
Modifique os dois lados para continuar trocando mensagens até o cliente enviar "quit".
Desafio 3: Múltiplos clientes
Depois do accept, chame fork() para cada cliente ser atendido por um processo filho enquanto o pai volta ao accept.
Desafio 4: Porta como argumento
Deixe o servidor aceitar a porta como argumento de linha de comando: ./echo_server 8080. Leia de argv[1]. Veja a aula de argc/argv.
Próximo: Construa um cliente HTTP →

Quiz de Revisão

Teste seu entendimento desta aula!

Q1. Qual combinação de funções um servidor TCP usa para escutar conexões?

bind() / listen() / accept()
send() / recv()
open() / close()

bind associa o socket a um endereço/porta, listen o coloca em modo de escuta e accept completa uma conexão entrante.

Q2. Qual função um cliente usa para conectar a um servidor?

connect()
bind()
accept()

Depois de criar um socket, o cliente chama connect para alcançar o servidor. bind e accept são chamadas do lado servidor.

Q3. Qual é o nome da struct da API de socket que guarda endereço IP e porta juntos?

struct sockaddr_in
struct ip_addr
struct host

Para IPv4 é sockaddr_in. IPv6 usa sockaddr_in6, e sockaddr é o tipo genérico.

Compartilhe este artigo
Compartilhar no X