UDP Soketleri

UDP, TCP'nin aksine, veri güvenliğinin garanti altına alınamadığı mesaj temelli bir iletişim protokolüdür. Mesajlaşacak taraflar arasında öncesinde bir iletişim kanalının oluşturulması gerekmemektedir. UDP protokolü datagram soketleri üzerinden sağlanmaktadır. Bu amaçla socket fonksiyonuna ilk argüman olarak SOCK_DGRAM sembolik sabitini geçireceğiz.

UDP protokülündeki haberleşmeyi posta sistemine benzetebiliriz. Bu benzetim üzerinden gidecek olursak sırasıyla sunucu ve istemci tarafında gerçekleşen olaylar aşağıdaki gibi olacaktır.

  1. socket ile haberleşme alt yapısı oluşturulur. Bu durumu fiziksel bir posta kutusuna sahip olmaya benzetebiliriz.
  2. bind ile soket bir IP adresi ve port ile ilişkilendirilir. Bu durumu, evimizin adresinin posta işletmesi tarafından biliniyor olmasına benzetebiliriz.
  3. recvfrom ile gelen mesaj, bloklamalı modda, okunur. Bu durumu posta kutumuzun başında postacıyı beklemeye ve mektup ulaştığında okumaya benzetebiliriz.
  4. sendto ile karşı tarafa mesaj gönderilir. Bu durumu alıcının adresini yazdığımız bir mektubu postalamaya benzetebiliriz.
  5. close ile soket sonlandırılır. Artık bir posta kutunuz olmadığından mektup alamayacaksınız.

Tipik bir istemci tarafında gerçekleşen olaylar ise bind işlemi hariç aynıdır.

  1. Sunucu tarafında olduğu gibi ilk olarak socket ile haberleşme alt yapısı oluşturulur.
  2. recvfrom ile gelen mesaj, bloklamalı modda, okunur.
  3. sendto ile karşı tarafa mesaj gönderilir.
  4. close ile soket sonlandırılır.

Gerçekte istemci tarafında da bind yapılabilir. Bu durumu gönderdiğiniz bir mektubun üzerine kendi adresinizi yazmanıza benzetebiliriz. Bu durumda alıcı taraf başka bir ön bilgiye ihtiyaç duymadan size cevap verebilir. Ayrıca sunucu tarafta accept kullanılabilmektedir, bu duruma değineceğiz.

UDP istemci sunucu haberleşmesini en basit haliyle görsel olarak aşağıdaki gibi gösterebiliriz.

UDP'de veri alışverişi, genellikle, recvfrom ve sendto isimli özel fonksiyonlarla yapılır. Bu fonksiyonlara daha yakından bakalım.

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags,
                const struct sockaddr *dest_addr, socklen_t addrlen);

Her iki fonksiyonun da geri dönüş değeri ve ilk 3 argümanı read ve write fonksiyonlarıyla aynıdır. flag argümanı soket spesifik G/Ç özelliklerini değiştirmek için kullanılmaktadır. Diğer iki argüman ise karşıdaki tarafın adresini belirtmek veya elde etmek için kullanılmaktadır.

recvfrom fonksiyonundaki src_addr uzaktaki soketin adresini elde etmek için kullanılmaktadır. Karşı taraf bind işlemini yapmışsa gönderen tarafın adresine bu şekilde erişilebilir. Bu argümanların kullanımı accept fonksiyonundaki kullanıma benzemektedir. Gönderen tarafın adresiyle ilgilenilmiyorsa bu argümanlara NULL değeri geçilebilir.

sento fonksiyonundaki dest_addr ve addrlen parameterleri karşı tarafın adresini belirtmek için kullanılmaktadır. Bu argümanların kullanımı connect fonksiyonundaki kullanıma benzemektedir.

Datagram soketleri bağlantısız olmalarına karşın connect fonksiyonu bu soketler için de kullanılabilir. Bu durumda bağlantılı datagram soketleri (connected datagram socket) oluşturulmuş olur. connect işleminden sonra sendto fonksiyonundaki dest_addr ve addrlen argümanlarına gerek kalmamaktadır, bu sebeple sendto yerine write fonksiyonu kullanılabilir. İşletim sistemi karşıdaki soketin adresini bizim için saklamaktadır. Ayrıca bu şekilde yalnız bağlantı yapılan taraftan gelen paketler ulaşmaktadır. Bu durumu mektubumuzun gideceği adresi mektuba yazmak yerine postacımıza söylememize benzetebiliriz. Ayrıca postacımız ilgilendiğimiz dışındaki mektupları bize ulaştırmayacaktır.

Basit bir örnek üzerinden UDP istemci sunucu haberleşmesine bakalım. Sunucu ve istemci kodları aşağıdaki gibidir. client uygulaması bağlanacağı tarafın IP adresini ve göndereceği mesajı komut satırından almaktadır. Sunucu istemciden gelen mesajı geri göndermekte ve sonrasında sonlanmaktadır. Her iki tarafında bilmesi gereken port numarasını yine 8080 olarak seçtik. Testi yine kendi makinamızda gerçekleştireceğimizden istemciye loopback adresini (127.0.0.1) geçireceğiz.

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#define BUF_SIZE 1024
#define PORT 8080

int main() {
    char buf[BUF_SIZE];
    struct sockaddr_in self, other;
    int len = sizeof(struct sockaddr_in);
    int n, s, port;

    if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
        perror("socket");
        return 1;
    }

    memset((char *) &self, 0, sizeof(struct sockaddr_in));
    self.sin_family = AF_INET;
    self.sin_port = htons(PORT);
    self.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(s, (struct sockaddr *) &self, sizeof(self)) == -1) {
        perror("bind");
        return 1;
    }

    while ((n = recvfrom(s, buf, BUF_SIZE, 0, (struct sockaddr *) &other, &len)) != -1) {
        printf("Received from %s:%d: ", inet_ntoa(other.sin_addr), ntohs(other.sin_port));
        fflush(stdout);
        write(1, buf, n);
        write(1, "\n", 1);

        sendto(s, buf, n, 0, (struct sockaddr *) &other, len);
    }

    close(s);
    return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>

#define BUF_SIZE 1024
#define PORT 8080

int main(int argc, char *argv[]) {
    struct sockaddr_in server;
    int len = sizeof(struct sockaddr_in);
    char buf[BUF_SIZE];
    int n, s;

    if (argc != 3) {
        printf("kullanım: client <IP adresi> <mesaj>\n");
        return 1;
    }

    if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
        perror("socket");
        return 1;
    }

    memset((char *) &server, 0, sizeof(struct sockaddr_in));
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr.s_addr = inet_addr(argv[1]);

    if (sendto(s, argv[2], strlen(argv[2]), 0, (struct sockaddr *) &server, len) == -1) {
        perror("sendto()");
        return 1;
    }

    n = recvfrom(s, buf, BUF_SIZE, 0, (struct sockaddr *) &server, &len);
    buf[n] = 0;
    printf("Received from %s:%d: ", inet_ntoa(server.sin_addr), ntohs(server.sin_port));
    fflush(stdout);
    write(1, buf, n);
    write(1, "\n", 1);

    close(s);
    return 0;
}

Kodları aşağıdaki gibi derleyebiliriz.

$ gcc -oserver server.c
$ gcc -oclient client.c

Sırasıyla sunucu ve istemci taraflarını aşağıdaki gibi çalıştırabiliriz.

$ ./server
$ ./client 127.0.0.1 "Mary had a little lamb"
Received from 127.0.0.1:8080: Mary had a little lamb

$ ./client 127.0.0.1 "All the place you have been trying..."
Received from 127.0.0.1:8080: All the place you have been trying...

Sunucu uygulamasının çıktısı ise aşağıdaki gibi olmaktadır.

$ ./server
Received from 127.0.0.1:50593: Mary had a little lamb
Received from 127.0.0.1:47827: All the place you have been trying...

İstemci tarafa ilişkin 50593 ve 47827 olarak gösterilen port numaraları daha önce bahsettiğimiz işletim sistemi tarafından atanan geçici port numaralarıdır.

results matching ""

    No results matching ""