Eğitim Portalı/Java/Socket Programlama
Java01-java/31-socket-programlama

Socket Programlama

Önceki bölümde ağın temel kavramlarını ve TCP/UDP'yi tanıdık. Şimdi TCP soketleriyle gerçek istemci-sunucu uygulamaları yazmayı derinleştiriyoruz. Soket, iki makine (veya aynı makinedeki iki süreç) arasındaki iletişim…

Socket Programlama

Önceki bölümde ağın temel kavramlarını ve TCP/UDP'yi tanıdık. Şimdi TCP soketleriyle gerçek istemci-sunucu uygulamaları yazmayı derinleştiriyoruz. Soket, iki makine (veya aynı makinedeki iki süreç) arasındaki iletişim kanalının iki ucundan biridir. Java'da soketle çalışmak, dosyayla çalışmaya çok benzer: bir giriş akışından okur, bir çıkış akışına yazarsın. Örneklerin hepsi tek bir JVM'de (loopback üzerinde) çalışır; internet gerektirmez.

Soketin anatomisi

İki temel sınıf vardır:

  • ServerSocket (sunucu tarafı): Bir portu dinler. accept() çağrısı bir istemci bağlanana kadar bloklar ve bağlandığında o istemciyle konuşmak için bir Socket döndürür.
  • Socket (istemci tarafı ve sunucunun istemci-başına soketi): İki uç arasındaki bağlantı. getInputStream() ve getOutputStream() ile veri akar.
// Sunucu
ServerSocket sunucu = new ServerSocket(port);
Socket istemci = sunucu.accept();           // bağlantı bekle (bloklar)
// İstemci
Socket soket = new Socket("localhost", port); // bağlan

Akışları metin için BufferedReader/PrintWriter ile sarmak en pratik yoldur (satır satır oku/yaz). Ham byte için doğrudan stream'leri, nesne için ObjectStream'leri kullanırsın.

Bir protokol tanımlamak

Soket sana ham bir kanal verir; üzerine kendi protokolünü (mesaj formatını) tanımlarsın. En basit haliyle satır tabanlı bir istek-cevap olabilir. Örnek 1 (./Ornek1.java) bir "hesap makinesi" sunucusu kurar: istemci "5 + 3" gönderir, sunucu ayrıştırıp "= 8" döner; geçersiz ifade ve sıfıra bölme gibi hataları da yönetir. HTTP bile özünde böyle metin tabanlı bir istek/ cevap protokolüdür — sadece çok daha zengin kuralları vardır.

Çok istemciye aynı anda hizmet: thread'ler

Burada kritik bir gerçek var: accept() ve read() bloklar. Tek thread'le yazarsan, bir istemciye hizmet ederken diğerleri bekler. Çözüm, her bağlantıyı ayrı bir thread'de işlemektir:

while (true) {
    Socket c = sunucu.accept();              // bağlantı bekle
    new Thread(() -> istemciyiHandle(c)).start(); // ayrı thread'de işle
}

Örnek 2 (./Ornek2.java) aynı anda bağlanan 3 istemciyi thread-per-client modeliyle karşılar; her istemci kendi thread'inde yanıt alır.

Üretimde: Her bağlantı için yeni Thread oluşturmak pahalıdır; bunun yerine bir thread havuzu (ExecutorService) kullanılır. Ve on binlerce eşzamanlı bağlantıda Java 21'in sanal thread'leri (her bağlantıya ucuz bir sanal thread) oyunu değiştirir — bloklayan basit kodla yüksek eşzamanlılık.

Nesne aktarımı

Metin dışında, yapılandırılmış Java nesnelerini de gönderebilirsin. ObjectOutputStream nesneyi byte'lara çevirir (serialize), ObjectInputStream karşıda geri kurar (deserialize):

out.writeObject(new SiparisIstegi(101, 3));
SiparisCevabi cevap = (SiparisCevabi) in.readObject();

Örnek 3 (./Ornek3.java) soket üzerinden bir istek/cevap nesne çifti aktarır. Nesnelerin Serializable olması gerekir.

Uyarı: Java'nın yerleşik serileştirmesi güvenlik ve sürüm-uyumluluğu açısından kırılgandır. Farklı sistemler/diller arası iletişimde günümüzde JSON veya Protobuf gibi taşınabilir formatlar tercih edilir (bunu Spring/REST tarafında JSON ile gördük).

Kaynak yönetimi

Soketler ve akışlar birer kaynaktır; mutlaka kapatılmalıdır. try-with-resources bunu otomatik yapar (örneklerde try (Socket s = ...)). Kapatılmayan soketler port/bağlantı sızıntısına yol açar.

Özet

ServerSocket/Socket ile istemci-sunucu kurmayı; soket üzerine kendi metin protokolünü tanımlamayı (Örnek 1); çok istemciye thread'lerle eşzamanlı hizmeti (Örnek 2) ve nesne aktarımını (Örnek 3) öğrendik; thread havuzu/sanal thread ve serileştirme uyarılarına değindik. Bu, tüm ağ uygulamalarının (web sunucuları dahil) altında yatan mekanizmadır. Sırada, daha yüksek seviyeli bir soyutlama: adreslerle çalışmak için URL ve URL işlemleri.

Kod Örnekleri(3)

Ornek1

çalıştırılabilir
Ornek1.java
1// Ornek1: İstek-cevap protokolü — basit bir "hesap makinesi" TCP sunucusu.
2// İstemci "a op b" gönderir, sunucu hesaplayıp sonucu döner. (Tek JVM.)
3// Çalıştırma: java Ornek1.java
4import java.io.BufferedReader;
5import java.io.IOException;
6import java.io.InputStreamReader;
7import java.io.PrintWriter;
8import java.net.ServerSocket;
9import java.net.Socket;
10
11public class Ornek1 {
12
13    public static void main(String[] args) throws Exception {
14        ServerSocket sunucu = new ServerSocket(0);
15        int port = sunucu.getLocalPort();
16        System.out.println("Hesap sunucusu, port: " + port);
17
18        // Sunucu: her satırı "a op b" olarak ayrıştırıp sonucu geri yazar (basit bir PROTOKOL).
19        Thread st = new Thread(() -> {
20            try (Socket c = sunucu.accept();
21                 BufferedReader in = new BufferedReader(new InputStreamReader(c.getInputStream()));
22                 PrintWriter out = new PrintWriter(c.getOutputStream(), true)) {
23                String satir;
24                while ((satir = in.readLine()) != null) {
25                    if (satir.equals("KAPAT")) break;
26                    out.println(hesapla(satir));
27                }
28            } catch (IOException e) { System.out.println("sunucu: " + e.getMessage()); }
29        });
30        st.start();
31
32        // İstemci: birkaç istek gönderir, cevapları okur.
33        try (Socket s = new Socket("localhost", port);
34             PrintWriter out = new PrintWriter(s.getOutputStream(), true);
35             BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()))) {
36            for (String istek : new String[]{"5 + 3", "10 * 4", "20 / 0", "9 - 12", "KAPAT"}) {
37                if (istek.equals("KAPAT")) { out.println(istek); break; }
38                out.println(istek);
39                System.out.println("  " + istek + "  ->  " + in.readLine());
40            }
41        }
42
43        st.join();
44        sunucu.close();
45        System.out.println("""
46
47                --- İstek-cevap protokolü ---
48                Soketin üstünde KENDİ metin protokolünü tanımladık: "sayı op sayı" -> sonuç.
49                Sunucu satır satır okur (readLine), işler, cevabı yazar (println). İstemci tersini yapar.
50                HTTP de aslında bunun gelişmişidir: metin tabanlı istek/cevap satırları.""");
51    }
52
53    static String hesapla(String ifade) {
54        try {
55            String[] p = ifade.split(" ");
56            double a = Double.parseDouble(p[0]), b = Double.parseDouble(p[2]);
57            return switch (p[1]) {
58                case "+" -> "= " + (a + b);
59                case "-" -> "= " + (a - b);
60                case "*" -> "= " + (a * b);
61                case "/" -> b == 0 ? "HATA: sıfıra bölme" : "= " + (a / b);
62                default -> "HATA: bilinmeyen işlem";
63            };
64        } catch (Exception e) { return "HATA: geçersiz ifade"; }
65    }
66}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// Ornek2: Çok istemcili sunucu — her bağlantı ayrı thread'de (thread-per-client).
2// Bloklayan accept()/read(), birden çok istemciyi aynı anda karşılamak için thread ister.
3// Çalıştırma: java Ornek2.java
4import java.io.BufferedReader;
5import java.io.IOException;
6import java.io.InputStreamReader;
7import java.io.PrintWriter;
8import java.net.ServerSocket;
9import java.net.Socket;
10import java.util.concurrent.CountDownLatch;
11
12public class Ornek2 {
13
14    public static void main(String[] args) throws Exception {
15        ServerSocket sunucu = new ServerSocket(0);
16        int port = sunucu.getLocalPort();
17        int istemciSayisi = 3;
18        System.out.println("Çok istemcili sunucu, port: " + port);
19
20        // Sunucu döngüsü: her accept'te yeni bir thread başlat -> istemciler paralel hizmet alır.
21        Thread kabulDongusu = new Thread(() -> {
22            try {
23                for (int i = 0; i < istemciSayisi; i++) {
24                    Socket c = sunucu.accept();         // bağlantı bekle
25                    new Thread(() -> istemciyiHandle(c)).start(); // ayrı thread'de işle
26                }
27            } catch (IOException ignored) {}
28        });
29        kabulDongusu.start();
30
31        // 3 istemciyi AYNI ANDA başlat; her biri kendi sayısının karesini ister.
32        CountDownLatch bitti = new CountDownLatch(istemciSayisi);
33        for (int i = 1; i <= istemciSayisi; i++) {
34            final int sayi = i;
35            new Thread(() -> {
36                try (Socket s = new Socket("localhost", port);
37                     PrintWriter out = new PrintWriter(s.getOutputStream(), true);
38                     BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()))) {
39                    out.println(sayi);
40                    System.out.println("  istemci-" + sayi + " cevap: " + in.readLine());
41                } catch (IOException e) {
42                    System.out.println("istemci-" + sayi + " hata: " + e.getMessage());
43                } finally { bitti.countDown(); }
44            }).start();
45        }
46
47        bitti.await();
48        sunucu.close();
49        System.out.println("""
50
51                --- Çok istemcili sunucu ---
52                accept() ve read() BLOKLAR; tek thread'le aynı anda yalnızca bir istemciye hizmet edilir.
53                Çözüm: her bağlantıyı ayrı bir thread'de (veya thread havuzunda) işlemek.
54                Üretimde 'thread-per-client' yerine genelde thread havuzu (ExecutorService) kullanılır;
55                ON BİNLERCE eşzamanlı bağlantı için Java 21 SANAL THREAD'leri idealdir.""");
56    }
57
58    static void istemciyiHandle(Socket c) {
59        try (c;
60             BufferedReader in = new BufferedReader(new InputStreamReader(c.getInputStream()));
61             PrintWriter out = new PrintWriter(c.getOutputStream(), true)) {
62            String satir = in.readLine();
63            int n = Integer.parseInt(satir.trim());
64            out.println(n + "^2 = " + (n * n) + " (thread: " + Thread.currentThread().getName() + ")");
65        } catch (Exception e) {
66            System.out.println("handle hatası: " + e.getMessage());
67        }
68    }
69}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek3

çalıştırılabilir
Ornek3.java
1// Ornek3: Soket üzerinden NESNE aktarımı — ObjectOutputStream / ObjectInputStream.
2// Metin yerine yapılandırılmış Java nesneleri gönderip almak.
3// Çalıştırma: java Ornek3.java
4import java.io.ObjectInputStream;
5import java.io.ObjectOutputStream;
6import java.io.Serializable;
7import java.net.ServerSocket;
8import java.net.Socket;
9
10public class Ornek3 {
11
12    // Ağdan geçecek nesneler Serializable olmalı (byte dizisine çevrilebilmeli).
13    record SiparisIstegi(int urunId, int adet) implements Serializable {}
14    record SiparisCevabi(boolean basarili, String mesaj, double tutar) implements Serializable {}
15
16    public static void main(String[] args) throws Exception {
17        ServerSocket sunucu = new ServerSocket(0);
18        int port = sunucu.getLocalPort();
19        System.out.println("Nesne sunucusu, port: " + port);
20
21        Thread st = new Thread(() -> {
22            try (Socket c = sunucu.accept();
23                 ObjectInputStream in = new ObjectInputStream(c.getInputStream());
24                 ObjectOutputStream out = new ObjectOutputStream(c.getOutputStream())) {
25                // İstek nesnesini doğrudan oku (deserialize).
26                SiparisIstegi istek = (SiparisIstegi) in.readObject();
27                System.out.println("  [sunucu] istek: ürün=" + istek.urunId() + ", adet=" + istek.adet());
28                double tutar = istek.adet() * 450.0;
29                // Cevap nesnesini gönder (serialize).
30                out.writeObject(new SiparisCevabi(true, "sipariş alındı", tutar));
31            } catch (Exception e) { System.out.println("sunucu: " + e.getMessage()); }
32        });
33        st.start();
34
35        try (Socket s = new Socket("localhost", port);
36             ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
37             ObjectInputStream in = new ObjectInputStream(s.getInputStream())) {
38            out.writeObject(new SiparisIstegi(101, 3));      // nesne gönder
39            SiparisCevabi cevap = (SiparisCevabi) in.readObject(); // nesne al
40            System.out.println("[istemci] cevap: " + cevap.mesaj() + ", tutar: " + cevap.tutar() + " TL");
41        }
42
43        st.join();
44        sunucu.close();
45        System.out.println("""
46
47                --- Nesne aktarımı (object serialization over socket) ---
48                ObjectOutputStream nesneyi byte'lara çevirip (serialize) gönderir; ObjectInputStream
49                karşıda nesneye geri çevirir (deserialize). Taraflar yapılandırılmış veriyle konuşur.
50                Uyarı: Java serileştirme güvenlik/uyumluluk açısından kırılgandır; servisler arası
51                iletişimde günümüzde genelde JSON/Protobuf gibi taşınabilir formatlar tercih edilir.""");
52    }
53}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.