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. Eskigethostbyname()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:
| Parametre | TCP | UDP |
|---|---|---|
| Domain | AF_INET (IPv4) | AF_INET (IPv4) |
| Type | SOCK_STREAM | SOCK_DGRAM |
| Protocol | 0 (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:
`socket()` — Restoranı aç (bir mekan edin)
`bind()` — Adres ve kapı numarası belirle
`listen()` — Kapıya "Açığız" tabelası as
`accept()` — Müşteri geldiğinde masaya oturt
`recv()`/`send()` — Sipariş al, yemek sun
`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_REUSEADDRseç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üreTIME_WAITdurumunda 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
| Özellik | TCP | UDP |
|---|---|---|
| Bağlantı | connect() + accept() gerekli | Bağlantısız |
| Güvenilirlik | Paket kaybı otomatik telafi | Kayıp tolere edilir |
| Sıralama | Garantili | Garanti yok |
| Hız | Daha yavaş (overhead) | Çok hızlı |
| Fonksiyonlar | send()/recv() | sendto()/recvfrom() |
| Kullanım | Web, dosya transferi, e-posta | Oyun, 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'taepoll, macOS'takqueueç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ızmazMove semantics: Socket sahipliği aktarılabilir, kopyalanamaz (dosya tanıtıcısı tek sahipli olmalı)
Temiz API: C-style
reinterpret_castvesockaddrkarmaşıklığı kapsüllenir
⚠️ Dikkat: Copy constructor ve assignment silinmiş. Socket'lar kopyalanamaz — tıpkı
std::unique_ptrgibi 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 amafd_setboyutu sınırlıdır (genelde 1024). Modern uygulamalarpoll()veya platform-özelepoll/kqueuekullanı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 →
0x12345678→78 56 34 12Big-endian (Network byte order): En yüksek anlamlı byte önce →
0x12345678→12 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ı
| Fonksiyon | Açı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
| Kural | Neden |
|---|---|
Her socket() sonrası fd kontrolü | -1 dönebilir |
SO_REUSEADDR kullan | "Address in use" hatasını önler |
SIGPIPE'ı yoksay | Kapanmış bağlantıya yazınca program çökmez |
send_all() ile gönder | send() verinin tamamını göndermeyebilir |
| Timeout ayarla | Sonsuza kadar beklemeyi önle |
errno kontrol et | Hatanın kaynağını anla |
| RAII veya try-finally | Kaynak 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?
| Özellik | POSIX Socket | Boost.Asio |
|---|---|---|
| Soyutlama | C-level, manual | C++ RAII, type-safe |
| Platform | POSIX only | Windows + POSIX |
| Async | epoll/kqueue elle yaz | Built-in event loop |
| Hata yönetimi | errno kontrol | Exception + error_code |
| Coroutine | ❌ | C++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ı
| Özellik | POSIX (Linux/macOS) | Winsock (Windows) |
|---|---|---|
| Başlatma | Gerekli değil | WSAStartup() zorunlu |
| Socket tipi | int | SOCKET (unsigned) |
| Geçersiz socket | -1 | INVALID_SOCKET |
| Kapatma | close() | closesocket() |
| Hata kodu | errno | WSAGetLastError() |
| Header | <sys/socket.h> | <winsock2.h> |
| Temizlik | Gerekli değil | WSACleanup() 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::threadveyapoll()/epoll()kullanılır — production'da I/O multiplexing tercih edilirRAII 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ı unutmaBoost.Asio platform-bağımsız, asenkron ağ programlama için endüstri standardıdır — C++ Networking TS de bunu temel alır
AI Asistan
Sorularını yanıtlamaya hazır