← Kursa Dön
📄 Text · 25 min

C++ ile Ağ Programlama: Socket, TCP/UDP ve HTTP

Bilgisayarlar birbirleriyle nasıl konuşuyor? Her gün kullandığımız web siteleri, mesajlaşma uygulamaları, online oyunlar — hepsi ağ programlamanın (network programming) üzerine inşa edilmiş. C++ bu alanda özel bir yere sahip: dünyanın en performans-kritik ağ yazılımları — web sunucuları (nginx), oyun sunucuları, yüksek frekanslı trading sistemleri — C++ ile yazılmış.

Bu derste POSIX socket API'siyle sıfırdan ağ programlama öğreneceksin. TCP ve UDP'nin farkını elle görecek, kendi client-server uygulamanı yazacak ve modern C++ ile HTTP istekleri atacaksın.


1. Ağ İletişimi Temelleri

Ağ programlamayı anlamak için birkaç temel kavramı netleştirmemiz gerekiyor.

IP Adresi ve Port

Her cihazın ağ üzerinde benzersiz bir kimliği vardır — buna IP adresi (Internet Protocol Address) diyoruz. IPv4 formatında 192.168.1.5 gibi görünür. Kendi bilgisayarına işaret eden özel adres 127.0.0.1 yani localhost'tur.

Bir bilgisayarda aynı anda yüzlerce program çalışabilir. Port numarası gelen verinin hangi programa gideceğini belirler. Bunu bir apartman düşün: IP adresi binanın adresi, port numarası daire numarası. Portlar 0-65535 arasında değer alır. Bilinen portlar: 80 (HTTP), 443 (HTTPS), 22 (SSH), 53 (DNS).

TCP vs UDP

İki temel iletişim protokolü var:

TCP (Transmission Control Protocol) güvenilir iletişim sağlar. Veri paketleri sıralı ulaşır, kayıp varsa tekrar gönderilir. Bir telefon görüşmesi gibi düşün — önce bağlantı kurarsın (three-way handshake), karşılıklı konuşursun, sonra düzgünce kapatırsın.

UDP (User Datagram Protocol) ise hızlı ama garantisiz. Mektup göndermek gibi — postaya atarsın, ulaşıp ulaşmadığını bilemezsin. Ama çok hızlıdır çünkü bağlantı kurma maliyeti yoktur. Online oyunlar, canlı yayın, DNS sorguları UDP kullanır.

#include <iostream>
#include <cstring>

#ifdef _WIN32
  #include <winsock2.h>
  #include <ws2tcpip.h>
  #pragma comment(lib, "ws2_32.lib")
#else
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <unistd.h>
  #include <netdb.h>
#endif

int main() {
    // Hostname çözümleme örneği
    struct addrinfo hints{}, *result;
    hints.ai_family = AF_INET;        // IPv4
    hints.ai_socktype = SOCK_STREAM;  // TCP

    int status = getaddrinfo("www.google.com", "80", &hints, &result);
    if (status == 0) {
        char ip[INET_ADDRSTRLEN];
        auto* addr = reinterpret_cast<sockaddr_in*>(result->ai_addr);
        inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
        std::cout << "Google IP: " << ip << "\n";
        freeaddrinfo(result);
    }
    return 0;
}

💡 İpucu: getaddrinfo() modern ve platform-bağımsız DNS çözümleme fonksiyonudur. Eski gethostbyname() yerine bunu kullan — hem IPv4 hem IPv6 destekler.


2. Socket API — Temel Kavramlar

C++ doğrudan bir Socket sınıfı sunmaz — POSIX (Unix/Linux/macOS) veya Winsock (Windows) API'lerini kullanırız. Ama endişelenme, kavramlar her yerde aynı.

Socket Nedir?

Socket, ağ iletişiminin uç noktasıdır (endpoint). İki bilgisayar arasındaki iletişim kanalının bir tarafı. Bir telefon ahizesi gibi düşün — hem konuşabilirsin hem dinleyebilirsin.

Socket oluşturmak için üç parametre gerekir:

ParametreTCPUDP
DomainAF_INET (IPv4)AF_INET (IPv4)
TypeSOCK_STREAMSOCK_DGRAM
Protocol0 (otomatik)0 (otomatik)
// TCP socket oluştur
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_sock < 0) {
    std::cerr << "TCP socket olusturulamadi!\n";
    return 1;
}

// UDP socket oluştur
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_sock < 0) {
    std::cerr << "UDP socket olusturulamadi!\n";
    return 1;
}

std::cout << "TCP socket fd: " << tcp_sock << "\n";
std::cout << "UDP socket fd: " << udp_sock << "\n";

close(tcp_sock);
close(udp_sock);

sockaddr_in Yapısı

Bağlantı kurmak için adres bilgisi gerekir. sockaddr_in yapısı IPv4 adresi ve port numarasını tutar:

sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;           // IPv4
server_addr.sin_port = htons(8080);         // Port (network byte order)
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // IP

⚠️ Dikkat: htons() (host to network short) port numarasını network byte order'a çevirir. Farklı bilgisayarlar baytları farklı sırada tutabilir (big-endian vs little-endian). Network byte order her zaman big-endian'dır. Bu dönüşümü unutursan bağlantı kurulamaz!


3. TCP Server — Adım Adım

TCP server yazmak belirli bir sıra takip eder. Bunu bir restoran işletmek gibi düşün:

  1. `socket()` — Restoranı aç (bir mekan edin)

  2. `bind()` — Adres ve kapı numarası belirle

  3. `listen()` — Kapıya "Açığız" tabelası as

  4. `accept()` — Müşteri geldiğinde masaya oturt

  5. `recv()`/`send()` — Sipariş al, yemek sun

  6. `close()` — Hesabı kapat, müşteriyi uğurla

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    // 1. Socket oluştur
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        std::cerr << "Socket olusturulamadi\n";
        return 1;
    }

    // SO_REUSEADDR: Port'u hemen yeniden kullanabilmek için
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 2. Adres ve port'a bağla
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;  // Tüm arayüzlerden dinle
    addr.sin_port = htons(8080);

    if (bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
        std::cerr << "Bind basarisiz\n";
        close(server_fd);
        return 1;
    }

    // 3. Dinlemeye başla (max 5 bekleme kuyruğu)
    listen(server_fd, 5);
    std::cout << "Server 8080 portunda dinliyor...\n";

    // 4. Client bağlantısı kabul et
    sockaddr_in client_addr{};
    socklen_t client_len = sizeof(client_addr);
    int client_fd = accept(server_fd,
        reinterpret_cast<sockaddr*>(&client_addr), &client_len);

    if (client_fd < 0) {
        std::cerr << "Accept basarisiz\n";
        close(server_fd);
        return 1;
    }
    std::cout << "Client baglandi!\n";

    // 5. Veri al ve gönder
    char buffer[1024]{};
    ssize_t bytes = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
    if (bytes > 0) {
        std::cout << "Gelen: " << buffer << "\n";

        const char* response = "Merhaba client!\n";
        send(client_fd, response, strlen(response), 0);
    }

    // 6. Bağlantıları kapat
    close(client_fd);
    close(server_fd);
    return 0;
}

💡 İpucu: SO_REUSEADDR seçeneği çok önemli. Bu olmadan server'ı durdurup hemen yeniden başlatmak istediğinde "Address already in use" hatası alırsın. OS, kapanmış bağlantıların tamamen temizlenmesi için port'u bir süre TIME_WAIT durumunda tutar.


4. TCP Client

Client tarafı çok daha basit — sadece bağlan, gönder, al:

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

int main() {
    // Socket oluştur
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        std::cerr << "Socket olusturulamadi\n";
        return 1;
    }

    // Server adresini ayarla
    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);

    // Bağlan
    if (connect(sock, reinterpret_cast<sockaddr*>(&server_addr),
                sizeof(server_addr)) < 0) {
        std::cerr << "Baglanti basarisiz\n";
        close(sock);
        return 1;
    }
    std::cout << "Server'a baglandi!\n";

    // Mesaj gönder
    const char* msg = "Merhaba server!";
    send(sock, msg, strlen(msg), 0);

    // Yanıt al
    char buffer[1024]{};
    ssize_t bytes = recv(sock, buffer, sizeof(buffer) - 1, 0);
    if (bytes > 0) {
        std::cout << "Server yaniti: " << buffer << "\n";
    }

    close(sock);
    return 0;
}

Client-Server Akışı

Server                          Client
  |                               |
  |  socket()                     |
  |  bind()                       |
  |  listen()                     |
  |  accept() --- bekliyor ---    |
  |                          socket()
  |                          connect() →
  |  ← accept() tamamlandı        |
  |                          send("Merhaba!")
  |  recv() → "Merhaba!"          |
  |  send("Yanıt")                |
  |                          recv() → "Yanıt"
  |  close()                 close()

5. UDP İletişimi

UDP'de bağlantı kurma aşaması yok — doğrudan veri gönder/al. Bir walkie-talkie gibi düşün: butona bas, konuş. Karşı taraf dinliyorsa duyar, dinlemiyorsa mesaj kaybolur.

UDP Server

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);  // SOCK_DGRAM = UDP

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(9090);

    bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    std::cout << "UDP server 9090'da dinliyor...\n";

    char buffer[1024]{};
    sockaddr_in client_addr{};
    socklen_t client_len = sizeof(client_addr);

    // recvfrom: kim gönderdi bilgisiyle birlikte al
    ssize_t bytes = recvfrom(sock, buffer, sizeof(buffer) - 1, 0,
        reinterpret_cast<sockaddr*>(&client_addr), &client_len);

    if (bytes > 0) {
        std::cout << "Gelen (" << bytes << " byte): " << buffer << "\n";

        // Gönderene yanıt
        const char* reply = "UDP yanit!";
        sendto(sock, reply, strlen(reply), 0,
            reinterpret_cast<sockaddr*>(&client_addr), client_len);
    }

    close(sock);
    return 0;
}

UDP Client

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

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

    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(9090);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

    // connect() yok! Doğrudan gönder
    const char* msg = "UDP merhaba!";
    sendto(sock, msg, strlen(msg), 0,
        reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr));

    // Yanıt al
    char buffer[1024]{};
    ssize_t bytes = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, nullptr, nullptr);
    if (bytes > 0) {
        std::cout << "Yanit: " << buffer << "\n";
    }

    close(sock);
    return 0;
}

TCP vs UDP Karşılaştırma

ÖzellikTCPUDP
Bağlantıconnect() + accept() gerekliBağlantısız
GüvenilirlikPaket kaybı otomatik telafiKayıp tolere edilir
SıralamaGarantiliGaranti yok
HızDaha yavaş (overhead)Çok hızlı
Fonksiyonlarsend()/recv()sendto()/recvfrom()
KullanımWeb, dosya transferi, e-postaOyun, video, DNS

6. Çoklu Client Yönetimi

Gerçek dünyada bir server aynı anda birçok client'a hizmet verir. Bunu yapmanın birkaç yolu var:

Yöntem 1: fork() ile Process Tabanlı

Her client için yeni bir process oluşturulur. Basit ama ağır:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>

void handle_client(int client_fd) {
    char buffer[1024]{};
    while (true) {
        ssize_t bytes = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
        if (bytes <= 0) break;
        buffer[bytes] = '\0';
        std::cout << "[PID " << getpid() << "] Gelen: " << buffer << "\n";

        // Echo: aynı veriyi geri gönder
        send(client_fd, buffer, bytes, 0);
        std::memset(buffer, 0, sizeof(buffer));
    }
    close(client_fd);
}

int main() {
    signal(SIGCHLD, SIG_IGN);  // Zombie process'leri önle

    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8080);
    bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    listen(server_fd, 10);

    std::cout << "Fork server 8080'de dinliyor...\n";

    while (true) {
        sockaddr_in client_addr{};
        socklen_t len = sizeof(client_addr);
        int client_fd = accept(server_fd,
            reinterpret_cast<sockaddr*>(&client_addr), &len);

        if (client_fd < 0) continue;

        pid_t pid = fork();
        if (pid == 0) {
            // Çocuk process
            close(server_fd);
            handle_client(client_fd);
            return 0;
        }
        // Ana process
        close(client_fd);
    }
}

Yöntem 2: std::thread ile Thread Tabanlı

Daha hafif — aynı adres alanını paylaşır:

#include <iostream>
#include <cstring>
#include <thread>
#include <vector>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

void handle_client(int client_fd) {
    char buffer[1024]{};
    while (true) {
        ssize_t bytes = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
        if (bytes <= 0) break;
        buffer[bytes] = '\0';
        std::cout << "[Thread " << std::this_thread::get_id()
                  << "] Gelen: " << buffer << "\n";
        send(client_fd, buffer, bytes, 0);
        std::memset(buffer, 0, sizeof(buffer));
    }
    close(client_fd);
}

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

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8080);
    bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    listen(server_fd, 10);

    std::cout << "Thread server 8080'de dinliyor...\n";

    std::vector<std::thread> threads;
    while (true) {
        sockaddr_in client_addr{};
        socklen_t len = sizeof(client_addr);
        int client_fd = accept(server_fd,
            reinterpret_cast<sockaddr*>(&client_addr), &len);

        if (client_fd < 0) continue;

        threads.emplace_back(handle_client, client_fd);
        threads.back().detach();  // Arka planda çalışsın
    }
}

Yöntem 3: poll() / epoll() ile I/O Multiplexing

En verimli yöntem. Tek thread ile binlerce bağlantıyı yönetir. nginx bu yaklaşımı kullanır:

#include <iostream>
#include <cstring>
#include <vector>
#include <poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

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

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8080);
    bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    listen(server_fd, 10);

    // poll fd listesi — ilk eleman server socket
    std::vector<pollfd> fds;
    fds.push_back({server_fd, POLLIN, 0});

    std::cout << "Poll server 8080'de dinliyor...\n";

    while (true) {
        int ready = poll(fds.data(), fds.size(), -1); // -1 = sonsuz bekle
        if (ready < 0) break;

        for (size_t i = 0; i < fds.size(); ++i) {
            if (!(fds[i].revents & POLLIN)) continue;

            if (fds[i].fd == server_fd) {
                // Yeni bağlantı
                int client_fd = accept(server_fd, nullptr, nullptr);
                if (client_fd >= 0) {
                    fds.push_back({client_fd, POLLIN, 0});
                    std::cout << "Yeni client: fd=" << client_fd << "\n";
                }
            } else {
                // Mevcut client'tan veri
                char buffer[1024]{};
                ssize_t bytes = recv(fds[i].fd, buffer, sizeof(buffer) - 1, 0);
                if (bytes <= 0) {
                    std::cout << "Client ayrildi: fd=" << fds[i].fd << "\n";
                    close(fds[i].fd);
                    fds.erase(fds.begin() + i);
                    --i;
                } else {
                    std::cout << "Gelen [fd=" << fds[i].fd << "]: " << buffer;
                    send(fds[i].fd, buffer, bytes, 0); // Echo
                }
            }
        }
    }
}

💡 İpucu: poll() portabl (tüm POSIX'te çalışır) ama büyük ölçeklerde yavaşlayabilir. Linux'ta epoll, macOS'ta kqueue çok daha verimli alternatiflerdir. Production uygulamalar genelde kütüphane (libuv, Boost.Asio) kullanır.


7. RAII Socket Wrapper

C-style socket API'leri hata yapmaya müsait — close() unutulabilir, exception'da kaynak sızabilir. Modern C++ ile RAII prensibiyle güvenli hale getirelim:

#include <iostream>
#include <stdexcept>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

class TcpSocket {
    int fd_ = -1;

public:
    TcpSocket() : fd_(socket(AF_INET, SOCK_STREAM, 0)) {
        if (fd_ < 0) throw std::runtime_error("Socket olusturulamadi");
    }

    // Move constructor
    TcpSocket(TcpSocket&& other) noexcept : fd_(other.fd_) {
        other.fd_ = -1;
    }

    // Move assignment
    TcpSocket& operator=(TcpSocket&& other) noexcept {
        if (this != &other) {
            close_internal();
            fd_ = other.fd_;
            other.fd_ = -1;
        }
        return *this;
    }

    // Copy engelle
    TcpSocket(const TcpSocket&) = delete;
    TcpSocket& operator=(const TcpSocket&) = delete;

    ~TcpSocket() { close_internal(); }

    void connect_to(const std::string& ip, int port) {
        sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);

        if (connect(fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
            throw std::runtime_error("Baglanti basarisiz: " + ip + ":" + std::to_string(port));
        }
    }

    void send_data(const std::string& data) {
        if (send(fd_, data.c_str(), data.size(), 0) < 0) {
            throw std::runtime_error("Gonderme basarisiz");
        }
    }

    std::string receive(size_t max_bytes = 4096) {
        std::string buffer(max_bytes, '\0');
        ssize_t bytes = recv(fd_, buffer.data(), max_bytes, 0);
        if (bytes < 0) throw std::runtime_error("Alma basarisiz");
        buffer.resize(bytes);
        return buffer;
    }

    int raw() const { return fd_; }

private:
    void close_internal() {
        if (fd_ >= 0) {
            close(fd_);
            fd_ = -1;
        }
    }
};

// Kullanım
int main() {
    try {
        TcpSocket sock;
        sock.connect_to("93.184.216.34", 80);  // example.com IP

        // Basit HTTP isteği
        sock.send_data("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n");
        std::string response = sock.receive();
        std::cout << "Ilk 200 karakter:\n"
                  << response.substr(0, 200) << "\n";

    } catch (const std::exception& e) {
        std::cerr << "Hata: " << e.what() << "\n";
        return 1;
    }
    // sock otomatik kapanır — RAII!
    return 0;
}

Bu wrapper'ın avantajları:

  • Kaynak güvenliği: Destructor close() çağırır — exception olsa bile kaynak sızmaz

  • Move semantics: Socket sahipliği aktarılabilir, kopyalanamaz (dosya tanıtıcısı tek sahipli olmalı)

  • Temiz API: C-style reinterpret_cast ve sockaddr karmaşıklığı kapsüllenir

⚠️ Dikkat: Copy constructor ve assignment silinmiş. Socket'lar kopyalanamaz — tıpkı std::unique_ptr gibi tek sahiplilik ilkesi geçerli.


8. Basit HTTP Client

HTTP protokolü TCP üzerine kuruludur. Bir HTTP isteği basitçe özel formatlanmış metin mesajıdır. İşte sıfırdan HTTP GET:

#include <iostream>
#include <sstream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

std::string http_get(const std::string& host, const std::string& path) {
    // DNS çözümle
    struct addrinfo hints{}, *result;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    if (getaddrinfo(host.c_str(), "80", &hints, &result) != 0) {
        throw std::runtime_error("DNS cozumlenemedi: " + host);
    }

    // Socket oluştur ve bağlan
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        freeaddrinfo(result);
        throw std::runtime_error("Socket olusturulamadi");
    }

    if (connect(sock, result->ai_addr, result->ai_addrlen) < 0) {
        freeaddrinfo(result);
        close(sock);
        throw std::runtime_error("Baglanti basarisiz");
    }
    freeaddrinfo(result);

    // HTTP isteği oluştur
    std::string request =
        "GET " + path + " HTTP/1.1\r\n"
        "Host: " + host + "\r\n"
        "Connection: close\r\n"
        "User-Agent: CppClient/1.0\r\n"
        "\r\n";

    send(sock, request.c_str(), request.size(), 0);

    // Yanıtı tamamen oku
    std::ostringstream response;
    char buffer[4096];
    ssize_t bytes;
    while ((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
        buffer[bytes] = '\0';
        response << buffer;
    }

    close(sock);
    return response.str();
}

int main() {
    try {
        std::string resp = http_get("example.com", "/");
        // Header ve body ayır
        auto pos = resp.find("\r\n\r\n");
        if (pos != std::string::npos) {
            std::cout << "=== Header ===\n"
                      << resp.substr(0, pos) << "\n\n"
                      << "=== Body (ilk 300 karakter) ===\n"
                      << resp.substr(pos + 4, 300) << "\n";
        }
    } catch (const std::exception& e) {
        std::cerr << "Hata: " << e.what() << "\n";
    }
    return 0;
}

HTTP isteğinin yapısı:

GET /path HTTP/1.1\r\n          ← İstek satırı (method path version)
Host: example.com\r\n           ← Zorunlu header
Connection: close\r\n           ← Bağlantıyı kapat
User-Agent: CppClient/1.0\r\n  ← Client kimliği
\r\n                            ← Boş satır = header sonu, body başı

9. Non-blocking I/O ve select()

Normal socket çağrıları blocking'tir — veri gelene kadar program durur. Non-blocking modda program durmaz, veri yoksa hemen döner:

#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cerrno>

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

    // Non-blocking yap
    int flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8080);
    bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    listen(sock, 5);

    std::cout << "Non-blocking server baslatildi\n";

    // select() ile bekleme
    fd_set read_fds;
    timeval timeout{};
    timeout.tv_sec = 5;   // 5 saniye bekle
    timeout.tv_usec = 0;

    FD_ZERO(&read_fds);
    FD_SET(sock, &read_fds);

    int ready = select(sock + 1, &read_fds, nullptr, nullptr, &timeout);
    if (ready > 0 && FD_ISSET(sock, &read_fds)) {
        int client = accept(sock, nullptr, nullptr);
        std::cout << "Client baglandi!\n";
        close(client);
    } else if (ready == 0) {
        std::cout << "5 saniye icinde baglanti gelmedi\n";
    } else {
        std::cerr << "select() hatasi\n";
    }

    close(sock);
    return 0;
}

💡 İpucu: select() en eski ve en portabl multiplexing API'dir ama fd_set boyutu sınırlıdır (genelde 1024). Modern uygulamalar poll() veya platform-özel epoll/kqueue kullanır.


10. Byte Order ve Serialization

Ağ üzerinden veri gönderirken en sık yapılan hata: byte order uyumsuzluğu. Farklı mimariler sayıları farklı sırada tutar:

  • Little-endian (Intel/AMD): En düşük anlamlı byte önce → 0x1234567878 56 34 12

  • Big-endian (Network byte order): En yüksek anlamlı byte önce → 0x1234567812 34 56 78

#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <cstdint>

// Basit bir mesaj protokolü
struct Message {
    uint32_t type;
    uint32_t length;
    // data buradan sonra gelir
};

// Network byte order'a çevir (göndermeden önce)
Message to_network(const Message& msg) {
    return {
        htonl(msg.type),    // host to network long (32-bit)
        htonl(msg.length)
    };
}

// Host byte order'a çevir (aldıktan sonra)
Message to_host(const Message& msg) {
    return {
        ntohl(msg.type),    // network to host long
        ntohl(msg.length)
    };
}

int main() {
    Message msg{1, 256};
    std::cout << "Orijinal - type: " << msg.type
              << ", length: " << msg.length << "\n";

    Message net = to_network(msg);
    // Bu byte'ları ağ üzerinden gönderirsin...

    Message restored = to_host(net);
    std::cout << "Geri cevrilmis - type: " << restored.type
              << ", length: " << restored.length << "\n";

    // Endianness kontrol
    uint16_t test = 0x0102;
    auto* bytes = reinterpret_cast<uint8_t*>(&test);
    if (bytes[0] == 0x01) {
        std::cout << "Bu makine: Big-endian\n";
    } else {
        std::cout << "Bu makine: Little-endian\n";
    }
    return 0;
}

Dönüşüm Fonksiyonları

FonksiyonAçıklama
htons()Host to network — 16-bit (port numaraları)
htonl()Host to network — 32-bit (IP adresleri, genel veriler)
ntohs()Network to host — 16-bit
ntohl()Network to host — 32-bit

⚠️ Dikkat: Byte order dönüşümünü sadece port ve custom protocol alanları için yapman gerekir. send() ile gönderdiğin raw text (string) zaten byte-byte iletilir, dönüşüm gerekmez.


11. Hata Yönetimi ve Best Practices

Socket programlamada hatalar kaçınılmaz. Network kesintisi, timeout, bağlantı reddi... bunları düzgün yönetmek profesyonel kod yazmanın temelidir.

Yaygın Hatalar ve Çözümleri

#include <iostream>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>

// SIGPIPE'ı yoksay (karşı taraf bağlantıyı kapatmışsa)
void setup_signal_handlers() {
    signal(SIGPIPE, SIG_IGN);
}

// Güvenli send — kısmi gönderim handle eder
ssize_t send_all(int fd, const char* data, size_t len) {
    size_t sent = 0;
    while (sent < len) {
        ssize_t n = send(fd, data + sent, len - sent, 0);
        if (n < 0) {
            if (errno == EINTR) continue;  // Sinyal kesilmesi — tekrar dene
            return -1;                      // Gerçek hata
        }
        if (n == 0) return sent;            // Bağlantı kapandı
        sent += n;
    }
    return sent;
}

// Güvenli recv — belirtilen miktarda veri oku
ssize_t recv_exact(int fd, char* buffer, size_t len) {
    size_t received = 0;
    while (received < len) {
        ssize_t n = recv(fd, buffer + received, len - received, 0);
        if (n < 0) {
            if (errno == EINTR) continue;
            return -1;
        }
        if (n == 0) return received;  // Bağlantı kapandı
        received += n;
    }
    return received;
}

int main() {
    setup_signal_handlers();

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        std::cerr << "Socket hatasi: " << strerror(errno) << "\n";
        return 1;
    }

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(12345);
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

    if (connect(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
        switch (errno) {
            case ECONNREFUSED:
                std::cerr << "Baglanti reddedildi — server calisiyor mu?\n";
                break;
            case ETIMEDOUT:
                std::cerr << "Baglanti zaman asimina ugradi\n";
                break;
            case ENETUNREACH:
                std::cerr << "Ag ulasilamaz\n";
                break;
            default:
                std::cerr << "Baglanti hatasi: " << strerror(errno) << "\n";
        }
        close(sock);
        return 1;
    }

    // Timeout ayarla
    timeval tv{};
    tv.tv_sec = 5;  // 5 saniye
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

    std::string msg = "Test mesaji";
    if (send_all(sock, msg.c_str(), msg.size()) < 0) {
        std::cerr << "Gonderme basarisiz: " << strerror(errno) << "\n";
    }

    close(sock);
    return 0;
}

Best Practices Kontrol Listesi

KuralNeden
Her socket() sonrası fd kontrolü-1 dönebilir
SO_REUSEADDR kullan"Address in use" hatasını önler
SIGPIPE'ı yoksayKapanmış bağlantıya yazınca program çökmez
send_all() ile göndersend() verinin tamamını göndermeyebilir
Timeout ayarlaSonsuza kadar beklemeyi önle
errno kontrol etHatanın kaynağını anla
RAII veya try-finallyKaynak sızıntısını önle
shutdown() sonra close()Düzgün bağlantı sonlandırma (graceful shutdown)

12. Boost.Asio — Modern C++ Ağ Kütüphanesi

POSIX socket API'si güçlü ama low-level ve hata yapmaya müsait. Boost.Asio modern C++ ile asenkron ağ programlama sunar. C++20/23 networking TS (teknik spesifikasyonu) da Asio'yu temel alır.

🎯 Analoji: POSIX sockets = manuel vites araba sürmek, Boost.Asio = otomatik vites. İkisi de aynı yere götürür ama biri çok daha rahat.

Senkron TCP Echo Server (Boost.Asio)

// Derleme: g++ -std=c++17 echo_server.cpp -lboost_system -lpthread
#include <boost/asio.hpp>
#include <iostream>
#include <thread>

using boost::asio::ip::tcp;

void session(tcp::socket sock) {
    try {
        while (true) {
            char buffer[1024];
            boost::system::error_code ec;
            size_t len = sock.read_some(boost::asio::buffer(buffer), ec);

            if (ec == boost::asio::error::eof) break;  // Bağlantı kapandı
            if (ec) throw boost::system::system_error(ec);

            boost::asio::write(sock, boost::asio::buffer(buffer, len));
        }
    } catch (std::exception& e) {
        std::cerr << "Session hatasi: " << e.what() << "\n";
    }
}

int main() {
    boost::asio::io_context io;
    tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 8080));

    std::cout << "Asio echo server 8080'de dinliyor...\n";

    while (true) {
        tcp::socket sock(io);
        acceptor.accept(sock);  // Bağlantı bekle
        std::thread(session, std::move(sock)).detach();
    }
}

Asenkron TCP Echo Server

Asenkron programlama, tek thread ile binlerce bağlantıyı yönetmeyi sağlar:

#include <boost/asio.hpp>
#include <iostream>
#include <memory>

using boost::asio::ip::tcp;

class Session : public std::enable_shared_from_this<Session> {
    tcp::socket socket_;
    char buffer_[1024];

public:
    Session(tcp::socket sock) : socket_(std::move(sock)) {}

    void start() { do_read(); }

private:
    void do_read() {
        auto self = shared_from_this();
        socket_.async_read_some(
            boost::asio::buffer(buffer_),
            [this, self](boost::system::error_code ec, size_t len) {
                if (!ec) do_write(len);
            });
    }

    void do_write(size_t len) {
        auto self = shared_from_this();
        boost::asio::async_write(
            socket_,
            boost::asio::buffer(buffer_, len),
            [this, self](boost::system::error_code ec, size_t) {
                if (!ec) do_read();
            });
    }
};

class Server {
    tcp::acceptor acceptor_;

public:
    Server(boost::asio::io_context& io, short port)
        : acceptor_(io, tcp::endpoint(tcp::v4(), port)) {
        do_accept();
    }

private:
    void do_accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket sock) {
                if (!ec) {
                    std::make_shared<Session>(std::move(sock))->start();
                }
                do_accept();  // Sonraki bağlantıyı bekle
            });
    }
};

int main() {
    boost::asio::io_context io;
    Server server(io, 8080);
    std::cout << "Async echo server 8080'de dinliyor...\n";
    io.run();  // Event loop başlat
}

Neden Asio?

ÖzellikPOSIX SocketBoost.Asio
SoyutlamaC-level, manualC++ RAII, type-safe
PlatformPOSIX onlyWindows + POSIX
Asyncepoll/kqueue elle yazBuilt-in event loop
Hata yönetimierrno kontrolException + error_code
CoroutineC++20 co_await desteği

13. Basit Mesaj Protokolü Tasarımı

Gerçek uygulamalarda raw TCP üzerinden mesaj gönderirken bir protokol tanımlamak zorunludur. TCP stream-based olduğu için mesaj sınırlarını kendin belirlemelisin.

Sorun: TCP Mesaj Sınırı Yok

TCP byte stream'dir — send("Merhaba") + send("Dünya") dediğinde karşı taraf recv() ile "MerhabaDünya" alabilir veya "Merh" + "abaDünya" alabilir. Mesaj sınırı garanti edilmez!

Çözüm: Length-Prefix Protocol

Her mesajın başına uzunluğunu ekle:

#include <iostream>
#include <cstring>
#include <cstdint>
#include <vector>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

// Mesaj gönder (length-prefix)
bool send_message(int fd, const std::string& msg) {
    uint32_t len = htonl(msg.size());  // Network byte order

    // Önce uzunluğu gönder (4 byte)
    if (send(fd, &len, sizeof(len), 0) != sizeof(len)) return false;

    // Sonra mesajın kendisini gönder
    size_t sent = 0;
    while (sent < msg.size()) {
        ssize_t n = send(fd, msg.c_str() + sent, msg.size() - sent, 0);
        if (n <= 0) return false;
        sent += n;
    }
    return true;
}

// Mesaj al (length-prefix)
std::string recv_message(int fd) {
    // Önce 4 byte uzunluk oku
    uint32_t net_len;
    ssize_t n = recv(fd, &net_len, sizeof(net_len), MSG_WAITALL);
    if (n != sizeof(net_len)) return "";

    uint32_t len = ntohl(net_len);
    if (len > 1024 * 1024) return "";  // Max 1MB güvenlik sınırı

    // Mesajı oku
    std::vector<char> buffer(len);
    size_t received = 0;
    while (received < len) {
        n = recv(fd, buffer.data() + received, len - received, 0);
        if (n <= 0) return "";
        received += n;
    }

    return std::string(buffer.begin(), buffer.end());
}

int main() {
    std::cout << "Length-prefix protocol ornegi\n";
    std::cout << "send_message: 4 byte uzunluk + N byte veri\n";
    std::cout << "recv_message: once uzunluk oku, sonra tam veri oku\n";

    // Demo: uzunluk hesaplama
    std::string msg = "Merhaba Dunya!";
    uint32_t len = msg.size();
    std::cout << "Mesaj: \"" << msg << "\" (" << len << " byte)\n";
    std::cout << "Gonderilecek: [" << len << "][" << msg << "]\n";
    return 0;
}

Protokol Formatı

┌─────────────┬──────────────────────────┐
│  4 byte     │  N byte                  │
│  uzunluk    │  mesaj verisi            │
│  (uint32)   │  (raw bytes)            │
└─────────────┴──────────────────────────┘

⚠️ Dikkat: Uzunluk alanını her zaman network byte order'da gönder (htonl/ntohl). Ayrıca max uzunluk kontrolü koy — kötü niyetli bir client devasa bir uzunluk gönderip belleğini şişirebilir.


14. Platform Bağımsız Kod Yazma

C++ socket kodu POSIX (Linux, macOS) ve Windows (Winsock) arasında farklılık gösterir. Portabl kod yazmak için platform makrolarını kullanabilirsin:

#include <iostream>
#include <cstring>

// Platform-bağımsız header'lar
#ifdef _WIN32
  #include <winsock2.h>
  #include <ws2tcpip.h>
  #pragma comment(lib, "ws2_32.lib")
  using socket_t = SOCKET;
  constexpr socket_t INVALID_SOCK = INVALID_SOCKET;
  inline int close_socket(socket_t s) { return closesocket(s); }
#else
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <unistd.h>
  #include <netdb.h>
  using socket_t = int;
  constexpr socket_t INVALID_SOCK = -1;
  inline int close_socket(socket_t s) { return close(s); }
#endif

// Platform-bağımsız Winsock başlatma/temizleme
class NetworkInit {
public:
    NetworkInit() {
#ifdef _WIN32
        WSADATA wsa;
        if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
            throw std::runtime_error("WSAStartup basarisiz");
        }
#endif
    }
    ~NetworkInit() {
#ifdef _WIN32
        WSACleanup();
#endif
    }
    NetworkInit(const NetworkInit&) = delete;
    NetworkInit& operator=(const NetworkInit&) = delete;
};

int main() {
    NetworkInit net;  // Windows'ta WSAStartup, Linux'ta no-op

    socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCK) {
        std::cerr << "Socket olusturulamadi\n";
        return 1;
    }

    std::cout << "Platform-bagimsiz socket olusturuldu: " << sock << "\n";

    close_socket(sock);
    return 0;
}

Platform Farkları

ÖzellikPOSIX (Linux/macOS)Winsock (Windows)
BaşlatmaGerekli değilWSAStartup() zorunlu
Socket tipiintSOCKET (unsigned)
Geçersiz socket-1INVALID_SOCKET
Kapatmaclose()closesocket()
Hata koduerrnoWSAGetLastError()
Header<sys/socket.h><winsock2.h>
TemizlikGerekli değilWSACleanup() zorunlu

Özet

  • Socket, ağ iletişiminin temel yapı taşıdır — TCP (güvenilir, bağlantı-tabanlı) ve UDP (hızlı, bağlantısız) olarak iki ana türü vardır

  • TCP server akışı: socket()bind()listen()accept()recv()/send()close()

  • Çoklu client yönetimi için fork(), std::thread veya poll()/epoll() kullanılır — production'da I/O multiplexing tercih edilir

  • RAII wrapper yazarak C-style socket API'sini modern C++'a uygun, kaynak-güvenli hale getirebilirsin

  • Byte order (endianness) ağ programlamanın en sinsi hatalarından biridir — htons()/htonl() kullanmayı unutma

  • Boost.Asio platform-bağımsız, asenkron ağ programlama için endüstri standardıdır — C++ Networking TS de bunu temel alır