TCP server e client do zero
1. Conceitos Fundamentais de Sockets TCP
O modelo cliente-servidor é a base da comunicação em rede. No protocolo TCP/IP, o servidor oferece um serviço (como ecoar mensagens) e o cliente consome esse serviço. O TCP garante que os dados cheguem na ordem correta e sem perdas, estabelecendo uma conexão confiável entre as partes.
Cada processo na rede é identificado por um endereço IP (identifica o host) e uma porta (identifica o processo específico naquele host). O socket é a interface de comunicação que permite a um programa enviar e receber dados através da rede. Em C, um socket é representado por um descritor de arquivo (file descriptor), similar a um arquivo aberto.
2. Criando um Socket e Configurando o Endereço
A função socket() cria um novo socket:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
AF_INET: domínio IPv4SOCK_STREAM: socket orientado a conexão (TCP)0: protocolo padrão (TCP para SOCK_STREAM)
Para configurar o endereço, usamos a estrutura sockaddr_in:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // porta em network byte order
addr.sin_addr.s_addr = INADDR_ANY; // aceita conexões de qualquer interface
htons() converte o número da porta de host byte order para network byte order (big-endian). inet_pton() converte string de IP para formato binário:
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
3. Implementando o Lado Servidor
O servidor segue quatro passos principais: criar socket, bind, listen e accept.
bind() - associa o socket a um endereço local:
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen() - coloca o socket em modo de escuta, definindo o tamanho da fila de conexões pendentes:
listen(sockfd, 5); // máximo de 5 conexões na fila
accept() - bloqueia até que um cliente se conecte, retornando um novo socket para comunicação:
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
4. Implementando o Lado Cliente
O cliente cria um socket e se conecta ao servidor com connect():
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
Para enviar e receber dados:
char buffer[1024];
send(sockfd, "Hello", 5, 0); // envia dados
recv(sockfd, buffer, sizeof(buffer), 0); // recebe dados
O fechamento é feito com close():
close(sockfd);
5. Comunicação Bidirecional e Loop de Mensagens
Tanto servidor quanto cliente podem usar loops para comunicação contínua. O servidor ecoa dados:
char buffer[1024];
int n;
while ((n = recv(client_fd, buffer, sizeof(buffer), 0)) > 0) {
send(client_fd, buffer, n, 0); // ecoa os dados recebidos
}
O cliente envia mensagens do terminal:
char buffer[1024];
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
send(sockfd, buffer, strlen(buffer), 0);
n = recv(sockfd, buffer, sizeof(buffer), 0);
buffer[n] = '\0';
printf("Eco: %s", buffer);
}
É importante tratar envios parciais: send() pode não enviar todos os bytes de uma vez. Um loop garante o envio completo:
int send_all(int fd, char *data, int len) {
int total = 0;
while (total < len) {
int n = send(fd, data + total, len - total, 0);
if (n == -1) return -1;
total += n;
}
return total;
}
6. Tratamento de Erros e Boas Práticas
Toda chamada de sistema deve ter seu retorno verificado:
if (bind(sockfd, ...) == -1) {
perror("bind failed");
exit(1);
}
Recursos devem ser liberados: feche sockets com close() ao final. Para evitar vazamento de descritores em caso de erro, use goto cleanup ou funções de limpeza.
O sinal SIGPIPE ocorre ao tentar escrever em um socket já fechado pelo outro lado. Ignore-o ou trate-o:
signal(SIGPIPE, SIG_IGN); // ignora SIGPIPE
Timeouts podem ser configurados com setsockopt():
struct timeval tv = {5, 0}; // 5 segundos
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
7. Exemplo Completo: Servidor Eco e Cliente Interativo
Servidor (server.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int server_fd, client_fd;
struct sockaddr_in addr;
char buffer[1024];
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) { perror("socket"); exit(1); }
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind"); close(server_fd); exit(1);
}
if (listen(server_fd, 5) == -1) {
perror("listen"); close(server_fd); exit(1);
}
printf("Servidor aguardando conexões na porta 8080...\n");
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) { perror("accept"); close(server_fd); exit(1); }
printf("Cliente conectado!\n");
int n;
while ((n = recv(client_fd, buffer, sizeof(buffer), 0)) > 0) {
send(client_fd, buffer, n, 0);
}
printf("Cliente desconectado.\n");
close(client_fd);
close(server_fd);
return 0;
}
Cliente (client.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int sockfd;
struct sockaddr_in addr;
char buffer[1024];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket"); exit(1); }
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("connect"); close(sockfd); exit(1);
}
printf("Conectado ao servidor. Digite mensagens (Ctrl+D para sair):\n");
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
send(sockfd, buffer, strlen(buffer), 0);
int n = recv(sockfd, buffer, sizeof(buffer), 0);
if (n <= 0) break;
buffer[n] = '\0';
printf("Eco: %s", buffer);
}
close(sockfd);
return 0;
}
Compilação e teste:
gcc -o server server.c
gcc -o client client.c
./server & # inicia servidor em background
./client # inicia cliente
Digite mensagens no cliente e veja o eco retornado pelo servidor. Pressione Ctrl+D para encerrar.
Referências
- Beej's Guide to Network Programming — Guia clássico e completo sobre programação de sockets em C, com exemplos práticos de TCP e UDP.
- Manual POSIX: socket() — Documentação oficial da chamada de sistema
socket()no Linux. - Manual POSIX: connect() — Documentação oficial da função
connect()para estabelecer conexões TCP. - Tutorial de Sockets em C no GeeksforGeeks — Tutorial passo a passo com código comentado explicando cada etapa do socket TCP.
- Implementação de Servidor TCP em C no LinuxHint — Exemplo prático de servidor e cliente TCP com tratamento de erros e boas práticas.
- RFC 793 - Transmission Control Protocol — Especificação oficial do protocolo TCP, útil para entender o funcionamento interno do protocolo.