İçeriğe geç
academia.sh

Ders 01 / 19

Ağ İstemcisi

Oturum yapılandırması ve görev türlerinin anlatımı; ağ katmanının arkasındaki altı işlemin yinelenirse ya da sırası değişirse sonucunun değişip değişmediğinin dört kategoride ölçümü.

Yayın Güncelleme

Bu derste

  • Oturumun, yapılandırmanın ve görevin ağ isteğindeki ayrı rollerini ayırır
  • Veri, yükleme ve indirme görev türlerinin hangi işe uyduğunu söyler
  • Bir işlemin yinelenirse ya da sırası değişirse sonucunun değişip değişmediğini gerçekten ölçer
  • Etkisizliğin yinelemeyi çözdüğünü ama sırayı çözmediğini ölçülmüş bir işlemle gösterir
İçindekiler

Önceki kursta (M30/K04) bir ekranın gösterdiği değeri modelin değeriyle karşılaştırdık ve ekranın bayat kalıp kalmadığına baktık. Bu kurs ekranın arkasına iner: değerin geldiği, saklandığı ve işlendiği katmana. Bu katmanda soru değişir. Ekran değeri gösterir ya da göstermez. Ağ katmanındaki bir işlem ise başka bir şeyle sınanır: aynı işlem iki kez uygulanırsa, ya da iki işlemin sırası değişirse sonuç farklı çıkar mı?

Bu sorunun ağda sık sorulması boşuna değil. İstek yolda kaybolabilir, yanıt geç gelebilir, kullanıcı aynı düğmeye iki kez basabilir, iki yanıt ters sırada ulaşabilir. Hiçbiri hata vermez. Yalnız durum sessizce başka bir değere döner. Bu derste önce ağ istemcisinin yapı taşlarını tanıyacağız: oturum, yapılandırma ve görev. Ardından altı işlemi tek bir çekirdekle ölçeceğiz.

Ölçü ekseni duyarlılıktır. Bir işlemi bir kez ve iki kez uygularız; sonuçlar farklıysa işlem yinelemeye duyarlıdır. İki işlemi iki ayrı sırayla uygularız; sonuçlar farklıysa işlem sıraya duyarlıdır. İkisi birden varsa çift duyarlı, hiçbiri yoksa sağlam denir. Çıktıda dört ad ASCII yazılır: saglam, yinelemeye-duyarli, siraya-duyarli, cift-duyarli.

// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}

Trial dört durumu tutar: once işlemin bir kez, twice iki kez uygulanmasından çıkan durumdur. forward önce a, sonra b; backward önce b, sonra a uygulanmasından çıkar. sensitivity yalnız iki eşitsizliğe bakar. replay bu dört durumu bir başlangıç durumundan üretir. Kural nedeni görmez, eşitsizliği görür: iki durumun neden farklı olduğunu söylemez, yalnız farklı olduklarını söyler. Hangi durumun karşılaştırıldığını ders söyler.

Oturum ve Yapılandırma

Bir ağ isteği üç ayrı nesne ister. Yapılandırma (configuration) oturumun nasıl davranacağını söyler: zaman aşımı, önbellek, ek başlıklar, ağ yoksa beklenip beklenmeyeceği. Oturum (session) bu yapılandırmayla çalışan istemcidir; aynı oturumdan çıkan istekler aynı kuralları paylaşır. Görev (task) tek bir aktarımdır: bir isteği alır, bir yanıt ya da hata döndürür.

URLSessionConfiguration.default kalıcı bir önbellek ve çerez deposu kullanır. .ephemeral bunların hiçbirini diske yazmaz; oturum kapanınca iz kalmaz. Üçüncü yapılandırma arka plan oturumudur ve bu konunun son dersinde işlenir.

import Foundation

let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 15
configuration.waitsForConnectivity = true
configuration.httpAdditionalHeaders = ["Accept": "application/json"]
let session = URLSession(configuration: configuration)

Yapılandırma oturum kurulurken okunur. Oturum kurulduktan sonra yapılandırmayı değiştirmek oturumu etkilemez; değişiklik için yeni bir oturum kurulur. Bu yüzden uygulamalar çoğunlukla az sayıda oturum kurar ve onları her yerde paylaşır.

Görev Türleri

Üç görev türü işi ayırır. Veri görevi (data task) yanıtı belleğe alır; küçük yanıtlar için uygundur. Yükleme görevi (upload task) isteğin gövdesini ayrı bir veri ya da dosya olarak gönderir. İndirme görevi (download task) yanıtı doğrudan bir dosyaya yazar; büyük gövdeler belleği şişirmesin diye kullanılır. Eşzamansız biçimde ilk ikisi data(for:) ve upload(for:from:) çağrılarıdır; ikisi de bir (Data, URLResponse) çifti döndürür.

// profil istegi — KAYNAK
import Foundation

func fetchProfile(session: URLSession) async throws -> Data {
    let request = URLRequest(url: URL(string: "https://api.example.com/profile")!)
    let (data, response) = try await session.data(for: request)
    guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    return data
}

Bu parça çalıştırılmaz; ağa çıkar. data(for:) bir eşzamansız fonksiyondur (asynchronous function): çağrı yanıt gelene kadar duraklama noktasında bekler ve akış sonra kaldığı yerden sürer. Yanıtın gerçek bir yanıt olup olmadığı ayrıca denetlenir; durum kodu HTTPURLResponse üzerinden okunur.

import Foundation

func send(order: Data, session: URLSession) async throws -> Int {
    var request = URLRequest(url: URL(string: "https://api.example.com/orders")!)
    request.httpMethod = "POST"
    let (_, response) = try await session.upload(for: request, from: order)
    return (response as? HTTPURLResponse)?.statusCode ?? 0
}

Yükleme görevinde gövde isteğin içine değil from: parametresine verilir. Bu parça da KAYNAK kalır. Yeni bir sipariş yaratan bir POST isteğinin yinelenince ne olduğu bu dersin altı işleminden biridir.

Altı İşlem

Aşağıdaki model, her işlemin duruma ne yaptığını kurar; ağın ya da diskin kendisi değildir. Altı işlemin her biri ağ katmanının bildik bir işine karşılık gelir: bir önbelleğe yazma, etkisizlik anahtarlı bir gönderim, yeniden denenen bir sipariş, bir rozet sayısını artırma, bir tercihi yazma, bir günlüğe satır ekleme. Çekirdeğin replay işleviyle her biri bir kez, iki kez ve iki sırada uygulanır.

// main.swift — alti islem, dort duyarlilik
func cached(_ key: String) -> ([String]) -> [String] {
    { keys in Array(Set(keys + [key])).sorted() }
}

func submit(_ key: String) -> ([String]) -> [String] {
    { received in received.contains(key) ? received : received + [key] }
}

func placeOrder(_ count: Int) -> Int { count + 1 }

func badge(_ count: Int) -> Int { count + 1 }

func theme(_ value: String) -> (String) -> String {
    { _ in value }
}

func log(_ line: String) -> ([String]) -> [String] {
    { lines in lines + [line] }
}

let cacheWrite = replay("onbellege-yaz", from: [String](), cached("profil"), then: cached("akis")).report()
let keyedSend = replay("anahtarli-gonderim", from: [String](), submit("k-41"), then: submit("k-42")).report()
let retry = replay("yeniden-dene", from: 0, placeOrder, then: placeOrder).report()
let push = replay("rozet-artir", from: 3, badge, then: badge).report()
let preference = replay("tercih-yaz", from: "sistem", theme("koyu"), then: theme("acik")).report()
let journal = replay("gunluge-ekle", from: [String](), log("giris"), then: log("cikis")).report()

countSensitivities([cacheWrite, keyedSend, retry, push, preference, journal])
onbellege-yaz | bir=["profil"] iki=["profil"] ab=["akis", "profil"] ba=["akis", "profil"] -> saglam
anahtarli-gonderim | bir=["k-41"] iki=["k-41"] ab=["k-41", "k-42"] ba=["k-42", "k-41"] -> siraya-duyarli
yeniden-dene | bir=1 iki=2 ab=2 ba=2 -> yinelemeye-duyarli
rozet-artir | bir=4 iki=5 ab=5 ba=5 -> yinelemeye-duyarli
tercih-yaz | bir=koyu iki=koyu ab=acik ba=koyu -> siraya-duyarli
gunluge-ekle | bir=["giris"] iki=["giris", "giris"] ab=["giris", "cikis"] ba=["cikis", "giris"] -> cift-duyarli
saglam: 1
yinelemeye-duyarli: 2
siraya-duyarli: 2
cift-duyarli: 1

onbellege-yaz sağlam: aynı anahtarı iki kez yazmak anahtar kümesini değiştirmez ve iki anahtarın sırası sonucu değiştirmez, çünkü küme sıralı basılır. anahtarli-gonderim sıraya duyarlı: etkisizlik anahtarı aynı isteğin ikinci gelişini yok sayar, bu yüzden bir ile iki aynıdır; ama k-41 ile k-42 farklı isteklerdir ve varış sırası listenin sırasını belirler. yeniden-dene yinelemeye duyarlı: sayaç her uygulamada artar. rozet-artir de yinelemeye duyarlı, aynı nedenle. tercih-yaz sıraya duyarlı: son yazan kazanır. gunluge-ekle ikisine birden duyarlı: satır iki kez eklenir ve iki satırın sırası değişir.

AG1. Bu ölçüm altı işlemin her birini tek bir başlangıç durumundan kurar. Başka bir başlangıç durumunda bir işlemin sınıfı değişebilir.

AG2. onbellege-yaz anahtarları sıralı küme olarak tutar. Eklenme sırasını koruyan bir önbellek ayrıca ölçülmelidir.

Dağılım bir bulgu değildir. Bir sağlam, iki yinelemeye duyarlı, iki sıraya duyarlı ve bir çift duyarlı işlem çıkması işlemleri biz seçtiğimiz için çıktı. Başka altı işlemle başka bir dağılım çıkardı.

Etkisizlik Neyi Çözer

anahtarli-gonderim bu dersin en öğretici satırıdır, çünkü yinelemeyi çözen şeyin sırayı çözmediğini gösterir. HTTP’de aynı isteği art arda göndermenin sonucu değiştirmediği yöntemlere etkisiz (idempotent) denir; GET ve PUT böyledir, POST değildir. Sunucu bir isteğe kimlik veren bir etkisizlik anahtarı kabul ederse, yeni bir sipariş yaratan POST da yinelemeye dayanıklı olabilir: aynı anahtarla gelen ikinci istek yok sayılır.

Ölçümde bu tam böyle çıktı. bir ile iki aynı: yineleme çözüldü. Ama iki farklı anahtar olduğunda sıra hâlâ sonucu belirliyor: ab ile ba aynı değil. Etkisizlik yinelemeyi çözer, sırayı çözmez. Sırayı çözmek için başka bir araç gerekir: sürüm numarası, birleştirilebilir bir veri yapısı ya da sunucuda sıralama. Sonraki derslerde bunların bir kısmını ölçeceğiz.

Gerçek Uygulamada Nerede Görülür

Ölçülen sıra duyarlılığı yapay bir laboratuvar durumu değildir. Bir ekran açılırken profil isteği gönderir; kullanıcı aşağı çekip yenilediğinde ikinci bir istek daha çıkar. İki yanıt ağdan ters sırayla dönerse ekrana en son gelen yazılır ve bu, eski isteğin yanıtı olabilir. Sonuç, kullanıcının az önce yaptığı bir değişikliğin ekranda sessizce geri alınmış görünmesidir. Hata kaydında bunun izi yoktur: hiçbir istek başarısız olmamıştır, her yanıt kendi başına doğrudur.

Yeniden deneme ise yineleme duyarlılığının tipik örneğidir. Sipariş gönderen istek zaman aşımına uğradığında istemci yanıtın gelmediğini görür; ama sunucu isteği almış ve siparişi kaydetmiş olabilir. İstemci aynı isteği yeniden gönderirse sunucuda ikinci bir sipariş oluşur. Ölçümün iki sütunu tam olarak bu durumu gösterir: yeniden-dene satırında işlem yinelendiğinde sayı bir daha artmıştır.

Başlangıç durumu meselesi daha da sinsidir. Aynı işlem, boş bir önbellekle açılan bir uygulamada ve önceki oturumdan dolu kalmış bir önbellekle açılan bir uygulamada farklı davranabilir. Aşağıdaki sınırlayıcı ölçüm bunu gösterir: aynı işlem çifti, başlangıç durumuna göre farklı sınıfa düşer. Bu yüzden bir isteğin yinelemeye dayanıklı olup olmadığı yalnız isteğe bakılarak söylenemez; işlemin hangi durumdan koştuğu da sorulur.

Yanlış Kurulum ve Düzeltmesi

En yaygın yanlış kurulum, yeniden denemeyi isteğin türüne bakmadan her başarısız isteğe uygulamaktır. Profili GET ile okumak yinelenince bir şey bozmaz; ama aynı kural sipariş yaratan bir POST isteğine de uygulanırsa her zaman aşımı ikinci bir sipariş riski taşır.

Düzeltme iki adımdır. Birincisi, yinelemeye duyarlı isteğe bir etkisizlik anahtarı eklemektir: istemci anahtarı ilk denemede üretir ve her yeniden denemede aynısını gönderir, sunucu da aynı anahtarla gelen ikinci isteği yok sayar. Ölçümdeki anahtarli-gonderim satırında bir ile iki sütunlarının aynı çıkması bunun karşılığıdır. İkincisi, sıraya duyarlı yanıtlarda ekranın yalnız en son gönderilen isteğin yanıtını kabul etmesidir; eski bir isteğin geç gelen yanıtı yok sayılır. Bu, sıra sorununu ortadan kaldırmaz ama sırayı tek bir yerde görünür kılar; sıraya duyarlı bir işlemin güvenli hâle gelmesinin yolu çoğu zaman sıranın artık değişememesidir.

Rozet için düzeltme farklıdır. Sayıyı yerelde artırmak yerine sunucunun bildirdiği mutlak değeri yazmak, işlemi yinelemeye karşı korur: aynı değer ikinci kez yazılsa da sonuç değişmez. Mutlak değerin de kendi sorusu vardır, iki değer ters sırayla gelirse eskisi kalır; bunu bu derste ayrıca ölçmedik, yalnız yönünü söylüyoruz.

KAYNAK Parçayı Okumak

Dersteki iki KAYNAK parça çalıştırılmaz, çünkü ikisi de ağa çıkar ve sonuçları sunucuya bağlıdır. Parçalar yine de okunmaya değerdir. fetchProfile bir okumadır: aynı isteği iki kez göndermek sunucudaki durumu değiştirmez ve modeldeki karşılığı onbellege-yaz satırıdır. send(order:session:) ise yeni bir kayıt yaratan bir POST isteğidir ve modeldeki placeOrder işlevine denk düşer. Model ile gerçek arasındaki fark, gerçek istekte yinelemenin istemcinin bilgisi dışında da olabilmesidir: bağlantı koptuğunda istemci isteğin sunucuya ulaşıp ulaşmadığını bilemez. Bu fark ölçülen sınıfı değiştirmez; yalnız yinelemenin nereden çıkabileceğini söyler.

Parçalardaki durum kodu denetimi de önemlidir. fetchProfile yalnız 200’ü kabul eder; send(order:session:) ise durum kodunu olduğu gibi döndürür ve kararı çağırana bırakır. Zaman aşımında hiç yanıt gelmez ve istemci isteğin işlenip işlenmediğini bilmez. Bu durumu sunucunun açıkça reddettiği bir istekle aynı hata saymak, yeniden denemenin yinelemeye duyarlı bir isteği ikinci kez göndermesine yol açar.

Sık Yapılan Hatalar

  • Her istek için yeni bir oturum kurmak. Yapılandırma ve bağlantılar paylaşılmaz.
  • Yanıtın HTTPURLResponse olup olmadığını ve durum kodunu denetlememek. Sunucu hata kodu döndürse de çağrı hata fırlatmaz.
  • Büyük bir gövdeyi veri görevi ile belleğe almak. İndirme görevi dosyaya yazar.
  • Yinelenen bir kullanıcı eylemini ağ katmanında yok saymak. Düğmeye iki kez basmak iki istek doğurur; yineleme sorusu istemcide ve sunucuda ayrı ayrı cevaplanır.
  • Etkisizlik anahtarını sıra sorununu da çözdüğünü varsayarak kullanmak.

Ölçülen Sonucun Yorumu

Altı işlemin altısı da bir ağ katmanında karşımıza çıkabilecek işlerdi ve dört kategoriye dağıldı. Önemli olan dağılım değil, bir işlemin sınıfının iki sorudan çıktığıdır: yinelenirse ne olur, sırası değişirse ne olur. Bu iki sorunun cevabı bir işlemden öbürüne değişir ve çoğu zaman işlemin adından çıkarılamaz. placeOrder ile badge aynı gövdeye sahip olsa da biri bir sipariş, öbürü bir sayaçtır; kullanıcı için sonuçları çok farklıdır, kural için aynıdır.

Ağ katmanında bu ayrım üç yerde işe yarar. Yeniden denemede: yinelemeye duyarlı bir işlem denenince ikinci kayıt doğurur. Yanıt sırasında: sıraya duyarlı bir işlem iki yanıt ters gelirse bayat değer bırakır. Önbellekte: sağlam bir işlem hata riski taşımadan tekrar edilebilir. Sonraki beş ders bu üç yeri sırayla ölçer.

Sınırlayıcı Ölçüm

Bir işlemin sınıfı yalnız işleme değil başlangıç durumuna da bağlıdır. Aşağıda aynı iki işlem, x * 2 ve x * 3, iki başlangıçtan çalıştırılır.

// main.swift — ayni iki islem, iki baslangic
let doubled = replay("sifirdan", from: 0, { $0 * 2 }, then: { $0 * 3 }).report()
let fromOne = replay("birden", from: 1, { $0 * 2 }, then: { $0 * 3 }).report()
countSensitivities([doubled, fromOne])
sifirdan | bir=0 iki=0 ab=0 ba=0 -> saglam
birden | bir=2 iki=4 ab=6 ba=6 -> yinelemeye-duyarli
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 0

Sıfırdan başlayınca iki işlem de sıfırı sıfır bırakır ve satır sağlamdır. Birden başlayınca ikinci uygulama değeri değiştirir ve satır yinelemeye duyarlıdır. İki sıra bu işlem çiftinde her iki başlangıçta da aynı sonucu verir, çünkü çarpmalar yer değiştirir. Aynı işlem, başlangıç durumu değişince başka sınıfa düştü.

AG3. Bu ölçüm yalnız sıfır ve bir başlangıçlarını karşılaştırır. Başka başlangıçlar ölçülmedi ve sınıf onlarda da değişebilir.

Özet

  • Ağ istemcisi üç parçadan kurulur: yapılandırma davranışı söyler, oturum o davranışla çalışır, görev tek bir aktarımı yürütür.
  • Veri görevi yanıtı belleğe alır, yükleme görevi gövdeyi ayrı gönderir, indirme görevi yanıtı dosyaya yazar.
  • Duyarlılık iki soruyla ölçülür: yinelenirse ve sırası değişirse sonuç değişir mi.
  • Altı işlem dört kategoriye ayrıldı: bir sağlam, iki yinelemeye duyarlı, iki sıraya duyarlı, bir çift duyarlı.
  • Etkisizlik yinelemeyi çözer, sırayı çözmez; aynı işlem başlangıç durumu değişince başka sınıfa düşebilir.
  • Bu dersin duyarlılığı: dördü de.

Sonraki Adım

Ağdan dönen yanıt bir bayt dizisidir; uygulamanın ihtiyacı ise tipli bir kayıttır. Sıradaki derste JSON Kodlama ve Çözme, bir yanıtı kayda çevirmenin kendisinin de sıraya duyarlı olabileceğini ölçecek: iki sürümlü bir kaydın hangi sırayla yazıldığı depodaki değeri belirler.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat