Skip to content

Gelişmiş Ağ Desenleri

WebSocket Uygulaması

Bağlantı Yönetimi

WebSocket bağlantıları long-running connections oldukları için dikkatli bir connection management gerektirir:

Kalp Atışı Mekanizması

javascript
class WebSocketManager {
  constructor(url) {
    this.url = url;
    this.heartbeatInterval = 30000; // 30 saniye
    this.heartbeatTimer = null;
  }

  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onopen = () => {
      console.log('WebSocket bağlantısı kuruldu');
      this.startHeartbeat();
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.type === 'pong') {
        console.log('Heartbeat yanıtı alındı');
      } else {
        this.handleMessage(message);
      }
    };

    this.ws.onclose = () => {
      console.log('WebSocket bağlantısı kapandı');
      this.stopHeartbeat();
      this.reconnect();
    };
  }

  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: 'ping' }));
      }
    }, this.heartbeatInterval);
  }

  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
}

Reconnection Strategy

javascript
class ReconnectingWebSocket {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      maxReconnectAttempts: 5,
      reconnectInterval: 1000,
      maxReconnectInterval: 30000,
      reconnectDecay: 1.5,
      ...options
    };
    this.reconnectAttempts = 0;
  }

  reconnect() {
    if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
      const timeout = this.options.reconnectInterval * 
        Math.pow(this.options.reconnectDecay, this.reconnectAttempts);
      
      setTimeout(() => {
        console.log(`Yeniden bağlanma denemesi: ${this.reconnectAttempts + 1}`);
        this.reconnectAttempts++;
        this.connect();
      }, Math.min(timeout, this.options.maxReconnectInterval));
    } else {
      console.error('Maksimum yeniden bağlanma denemesi aşıldı');
    }
  }
}

Connection Pooling

javascript
class WebSocketPool {
  constructor(baseUrl, poolSize = 3) {
    this.baseUrl = baseUrl;
    this.poolSize = poolSize;
    this.connections = [];
    this.currentIndex = 0;
  }

  initialize() {
    for (let i = 0; i < this.poolSize; i++) {
      const ws = new ReconnectingWebSocket(`${this.baseUrl}/${i}`);
      this.connections.push(ws);
    }
  }

  getConnection() {
    const connection = this.connections[this.currentIndex];
    this.currentIndex = (this.currentIndex + 1) % this.poolSize;
    return connection;
  }

  broadcast(message) {
    this.connections.forEach(connection => {
      if (connection.readyState === WebSocket.OPEN) {
        connection.send(JSON.stringify(message));
      }
    });
  }
}

Message Handling

Message Queuing

javascript
class MessageQueue {
  constructor() {
    this.queue = [];
    this.processing = false;
  }

  enqueue(message) {
    this.queue.push({
      ...message,
      timestamp: Date.now(),
      retries: 0
    });
    
    if (!this.processing) {
      this.process();
    }
  }

  async process() {
    this.processing = true;
    
    while (this.queue.length > 0) {
      const message = this.queue.shift();
      
      try {
        await this.sendMessage(message);
      } catch (error) {
        console.error('Mesaj gönderimi başarısız:', error);
        
        if (message.retries < 3) {
          message.retries++;
          this.queue.unshift(message); // Tekrar kuyruğun başına ekle
        } else {
          console.error('Mesaj maksimum deneme sayısını aştı:', message);
        }
      }
    }
    
    this.processing = false;
  }

  async sendMessage(message) {
    return new Promise((resolve, reject) => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify(message));
        resolve();
      } else {
        reject(new Error('WebSocket bağlantısı kapalı'));
      }
    });
  }
}

Priority-Based Delivery

javascript
class PriorityMessageQueue {
  constructor() {
    this.highPriorityQueue = [];
    this.normalPriorityQueue = [];
    this.lowPriorityQueue = [];
  }

  enqueue(message, priority = 'normal') {
    const queuedMessage = {
      ...message,
      timestamp: Date.now(),
      priority
    };

    switch (priority) {
      case 'high':
        this.highPriorityQueue.push(queuedMessage);
        break;
      case 'low':
        this.lowPriorityQueue.push(queuedMessage);
        break;
      default:
        this.normalPriorityQueue.push(queuedMessage);
    }

    this.processNext();
  }

  processNext() {
    let nextMessage;
    
    if (this.highPriorityQueue.length > 0) {
      nextMessage = this.highPriorityQueue.shift();
    } else if (this.normalPriorityQueue.length > 0) {
      nextMessage = this.normalPriorityQueue.shift();
    } else if (this.lowPriorityQueue.length > 0) {
      nextMessage = this.lowPriorityQueue.shift();
    }

    if (nextMessage) {
      this.sendMessage(nextMessage);
    }
  }
}

Platform-Specific WebSocket Implementation

Android WebSocket

kotlin
class AndroidWebSocketManager {
    private lateinit var webSocket: WebSocket
    private val client = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build()

    fun connect(url: String) {
        val request = Request.Builder()
            .url(url)
            .build()

        webSocket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                Log.d("WebSocket", "Bağlantı kuruldu")
                startHeartbeat()
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                handleMessage(text)
            }

            override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                Log.d("WebSocket", "Bağlantı kapanıyor: $reason")
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                Log.e("WebSocket", "Bağlantı hatası", t)
                scheduleReconnect()
            }
        })
    }

    private fun startHeartbeat() {
        val handler = Handler(Looper.getMainLooper())
        val heartbeatRunnable = object : Runnable {
            override fun run() {
                webSocket.send("""{"type":"ping"}""")
                handler.postDelayed(this, 30000)
            }
        }
        handler.post(heartbeatRunnable)
    }
}

iOS WebSocket

swift
class iOSWebSocketManager: NSObject, URLSessionWebSocketDelegate {
    private var webSocketTask: URLSessionWebSocketTask?
    private var urlSession: URLSession?
    private var heartbeatTimer: Timer?
    
    func connect(to url: URL) {
        urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
        webSocketTask = urlSession?.webSocketTask(with: url)
        webSocketTask?.resume()
        
        startHeartbeat()
        receiveMessage()
    }
    
    private func startHeartbeat() {
        heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { _ in
            self.sendPing()
        }
    }
    
    private func sendPing() {
        let message = URLSessionWebSocketTask.Message.string("""{"type":"ping"}""")
        webSocketTask?.send(message) { error in
            if let error = error {
                print("Ping gönderimi başarısız: \(error)")
            }
        }
    }
    
    private func receiveMessage() {
        webSocketTask?.receive { [weak self] result in
            switch result {
            case .success(let message):
                switch message {
                case .string(let text):
                    self?.handleMessage(text)
                case .data(let data):
                    self?.handleData(data)
                @unknown default:
                    break
                }
                self?.receiveMessage() // Sonraki mesajı dinle
            case .failure(let error):
                print("Mesaj alma hatası: \(error)")
                self?.scheduleReconnect()
            }
        }
    }
}

GraphQL Optimizasyonu

Query Optimization

Field Selection

javascript
// Gereksiz field'ları almak yerine sadece ihtiyaç duyulanları al
const USER_PROFILE_QUERY = gql`
  query GetUserProfile($userId: ID!) {
    user(id: $userId) {
      id
      name
      email
      avatar {
        url
        alt
      }
      # Gereksiz field'ları çıkar
      # createdAt
      # updatedAt
      # permissions
    }
  }
`;

Fragment Usage

javascript
// Ortak field'lar için fragment kullan
const USER_FRAGMENT = gql`
  fragment UserInfo on User {
    id
    name
    email
    avatar {
      url
      alt
    }
  }
`;

const USER_LIST_QUERY = gql`
  query GetUsers {
    users {
      ...UserInfo
      role
    }
  }
  ${USER_FRAGMENT}
`;

const USER_DETAIL_QUERY = gql`
  query GetUserDetail($id: ID!) {
    user(id: $id) {
      ...UserInfo
      bio
      preferences {
        theme
        language
      }
    }
  }
  ${USER_FRAGMENT}
`;

Query Batching

javascript
class GraphQLBatcher {
  constructor(client, batchTimeout = 10) {
    this.client = client;
    this.batchTimeout = batchTimeout;
    this.batchQueue = [];
    this.batchTimer = null;
  }

  query(query, variables = {}) {
    return new Promise((resolve, reject) => {
      this.batchQueue.push({
        query,
        variables,
        resolve,
        reject
      });

      if (!this.batchTimer) {
        this.batchTimer = setTimeout(() => {
          this.executeBatch();
        }, this.batchTimeout);
      }
    });
  }

  async executeBatch() {
    const currentBatch = [...this.batchQueue];
    this.batchQueue = [];
    this.batchTimer = null;

    try {
      const batchQuery = this.createBatchQuery(currentBatch);
      const result = await this.client.query(batchQuery);
      
      this.resolveBatchResults(currentBatch, result);
    } catch (error) {
      currentBatch.forEach(item => item.reject(error));
    }
  }

  createBatchQuery(batch) {
    // Birden fazla query'yi tek bir query'de birleştir
    const queries = batch.map((item, index) => {
      return `query_${index}: ${item.query.loc.source.body}`;
    });

    return gql`
      query BatchQuery {
        ${queries.join('\n')}
      }
    `;
  }
}

Caching Strategy

Client-Side Caching

javascript
class GraphQLCache {
  constructor() {
    this.cache = new Map();
    this.ttl = new Map();
  }

  set(key, data, ttl = 300000) { // 5 dakika TTL
    this.cache.set(key, data);
    this.ttl.set(key, Date.now() + ttl);
  }

  get(key) {
    if (this.ttl.has(key) && Date.now() > this.ttl.get(key)) {
      this.cache.delete(key);
      this.ttl.delete(key);
      return null;
    }
    return this.cache.get(key);
  }

  invalidate(pattern) {
    const regex = new RegExp(pattern);
    for (const key of this.cache.keys()) {
      if (regex.test(key)) {
        this.cache.delete(key);
        this.ttl.delete(key);
      }
    }
  }
}

Server-Side Caching

javascript
// GraphQL server-side caching with Redis
const redis = require('redis');
const client = redis.createClient();

const typeDefs = gql`
  type Query {
    user(id: ID!): User @cacheControl(maxAge: 300)
    posts(limit: Int): [Post] @cacheControl(maxAge: 60)
  }
`;

const resolvers = {
  Query: {
    user: async (parent, { id }, context) => {
      const cacheKey = `user:${id}`;
      const cached = await client.get(cacheKey);
      
      if (cached) {
        return JSON.parse(cached);
      }

      const user = await getUserById(id);
      await client.setex(cacheKey, 300, JSON.stringify(user));
      
      return user;
    }
  }
};

Flutter GraphQL Implementation

dart
class GraphQLService {
  late GraphQLClient _client;
  
  GraphQLService() {
    final HttpLink httpLink = HttpLink('https://api.example.com/graphql');
    final AuthLink authLink = AuthLink(getToken: () async => 'Bearer $token');
    final Link link = authLink.concat(httpLink);
    
    _client = GraphQLClient(
      cache: GraphQLCache(store: InMemoryStore()),
      link: link,
    );
  }

  Future<QueryResult> query(DocumentNode document, {Map<String, dynamic>? variables}) async {
    final QueryOptions options = QueryOptions(
      document: document,
      variables: variables ?? {},
      fetchPolicy: FetchPolicy.cacheAndNetwork,
    );
    
    return await _client.query(options);
  }

  Future<QueryResult> mutate(DocumentNode document, {Map<String, dynamic>? variables}) async {
    final MutationOptions options = MutationOptions(
      document: document,
      variables: variables ?? {},
    );
    
    return await _client.mutate(options);
  }
}

Gerçek Zamanlı Veri Senkronizasyonu

Conflict Resolution

Last-Write-Wins Strategy

javascript
class LastWriteWinsResolver {
  resolveConflict(localData, remoteData) {
    // Timestamp'e göre son güncellemeyi kabul et
    if (localData.updatedAt > remoteData.updatedAt) {
      return localData;
    } else {
      return remoteData;
    }
  }

  mergeData(local, remote) {
    const resolved = { ...local };
    
    Object.keys(remote).forEach(key => {
      if (remote[key].updatedAt > (local[key]?.updatedAt || 0)) {
        resolved[key] = remote[key];
      }
    });

    return resolved;
  }
}

Merge Strategies

javascript
class MergeStrategy {
  smartMerge(localData, remoteData) {
    const merged = { ...localData };
    
    // Conflict resolution rules
    const resolutionRules = {
      // String fields: Use remote if different
      title: (local, remote) => remote !== local ? remote : local,
      
      // Arrays: Merge unique items
      tags: (local, remote) => [...new Set([...local, ...remote])],
      
      // Objects: Deep merge
      settings: (local, remote) => this.deepMerge(local, remote),
      
      // Numbers: Use higher value
      score: (local, remote) => Math.max(local, remote)
    };

    Object.keys(remoteData).forEach(key => {
      if (resolutionRules[key]) {
        merged[key] = resolutionRules[key](localData[key], remoteData[key]);
      } else {
        merged[key] = remoteData[key];
      }
    });

    return merged;
  }

  deepMerge(obj1, obj2) {
    const result = { ...obj1 };
    
    Object.keys(obj2).forEach(key => {
      if (typeof obj2[key] === 'object' && !Array.isArray(obj2[key])) {
        result[key] = this.deepMerge(result[key] || {}, obj2[key]);
      } else {
        result[key] = obj2[key];
      }
    });

    return result;
  }
}

Version Vectors

javascript
class VectorClock {
  constructor(nodeId) {
    this.nodeId = nodeId;
    this.vector = new Map();
    this.vector.set(nodeId, 0);
  }

  tick() {
    const currentValue = this.vector.get(this.nodeId) || 0;
    this.vector.set(this.nodeId, currentValue + 1);
    return this.getVector();
  }

  update(otherVector) {
    // Merge vector clocks
    Object.keys(otherVector).forEach(nodeId => {
      const currentValue = this.vector.get(nodeId) || 0;
      const otherValue = otherVector[nodeId];
      this.vector.set(nodeId, Math.max(currentValue, otherValue));
    });
    
    // Increment own counter
    this.tick();
  }

  compare(otherVector) {
    const thisKeys = [...this.vector.keys()];
    const otherKeys = Object.keys(otherVector);
    const allKeys = [...new Set([...thisKeys, ...otherKeys])];
    
    let thisGreater = false;
    let otherGreater = false;
    
    allKeys.forEach(key => {
      const thisValue = this.vector.get(key) || 0;
      const otherValue = otherVector[key] || 0;
      
      if (thisValue > otherValue) {
        thisGreater = true;
      } else if (otherValue > thisValue) {
        otherGreater = true;
      }
    });
    
    if (thisGreater && !otherGreater) return 1;  // This is newer
    if (otherGreater && !thisGreater) return -1; // Other is newer
    return 0; // Concurrent/conflicting
  }

  getVector() {
    return Object.fromEntries(this.vector);
  }
}

State Management

Optimistic Updates

javascript
class OptimisticUpdateManager {
  constructor() {
    this.pendingUpdates = new Map();
    this.rollbackStack = [];
  }

  async optimisticUpdate(id, updateFn, serverUpdateFn) {
    // Local state'i hemen güncelle
    const previousState = this.getCurrentState(id);
    const optimisticState = updateFn(previousState);
    
    // Rollback için önceki state'i sakla
    this.rollbackStack.push({ id, previousState });
    this.pendingUpdates.set(id, { optimisticState, timestamp: Date.now() });
    
    // UI'ı güncelle
    this.updateUI(id, optimisticState);
    
    try {
      // Server'a asenkron gönder
      const serverResult = await serverUpdateFn(optimisticState);
      
      // Başarılı olursa optimistic update'i confirm et
      this.confirmUpdate(id, serverResult);
    } catch (error) {
      // Hata durumunda rollback yap
      this.rollbackUpdate(id);
      throw error;
    }
  }

  confirmUpdate(id, serverResult) {
    this.pendingUpdates.delete(id);
    this.updateUI(id, serverResult);
    
    // Rollback stack'ten ilgili item'ı kaldır
    this.rollbackStack = this.rollbackStack.filter(item => item.id !== id);
  }

  rollbackUpdate(id) {
    const rollbackItem = this.rollbackStack.find(item => item.id === id);
    if (rollbackItem) {
      this.updateUI(id, rollbackItem.previousState);
      this.pendingUpdates.delete(id);
      this.rollbackStack = this.rollbackStack.filter(item => item.id !== id);
    }
  }

  isPending(id) {
    return this.pendingUpdates.has(id);
  }
}

State Reconciliation

javascript
class StateReconciler {
  constructor() {
    this.localState = new Map();
    this.remoteState = new Map();
    this.conflictResolver = new MergeStrategy();
  }

  async reconcile(entityId) {
    const local = this.localState.get(entityId);
    const remote = this.remoteState.get(entityId);
    
    if (!local && !remote) {
      return null;
    }
    
    if (!local) {
      this.localState.set(entityId, remote);
      return remote;
    }
    
    if (!remote) {
      // Local changes need to be synced to server
      await this.syncToServer(entityId, local);
      return local;
    }
    
    // Both exist, need to resolve conflicts
    const resolved = this.conflictResolver.smartMerge(local, remote);
    
    // Update both local and remote states
    this.localState.set(entityId, resolved);
    await this.syncToServer(entityId, resolved);
    
    return resolved;
  }

  async syncToServer(entityId, data) {
    try {
      const response = await fetch(`/api/entities/${entityId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });
      
      if (response.ok) {
        const serverData = await response.json();
        this.remoteState.set(entityId, serverData);
      }
    } catch (error) {
      console.error('Server sync failed:', error);
      throw error;
    }
  }
}

Bu gelişmiş network patterns, modern mobile uygulamalarda real-time communication, efficient data transfer ve robust state management için kritik öneme sahiptir. WebSocket, GraphQL ve state synchronization teknikleri, kullanıcı deneyimini önemli ölçüde iyileştirir.

Eren Demir tarafından oluşturulmuştur.