Warning: Undefined array key "HTTP_ACCEPT_LANGUAGE" in /var/www/vhosts/bilgigunlugum.net/httpdocs/index.php on line 43
C Programlama

Unreal Engine Oyun Programlama sayfalarımız yayında...

Ana sayfa > Programlama > C Programlama > Ağ (Soket) işlemleri

Ağ (Soket) işlemleri

Bu bölümde, Windows işletim sisteminde C Programlama Dili ile ağ iletişimi hakkında bilgi vermeye çalışacağız.

Soket programlama

Soket

Soket, ağ üzerinde iletişim kuran, aynı veya farklı makinelerdeki, iki programın (process) arasındaki bağlantının her iki ucundaki soyut bir noktadır. İki bilgisayar arasındaki veri alışverişini sağlayan bir "kapı" veya "uç nokta" olarak düşünebilir. Ağ iletişimi için ise bir ağ bağlantısı ve iki soket gerekir.

Soket, bir IP adresi ve bir port numarasının birleşiminden oluşur. Bu sayede, doğru bilgisayardaki (IP adresi) doğru uygulamayı (port numarası) hedefleyebilirsiniz.

Soketlerin çalışması (Sunucu-İstemci Modeli)

Soketler genellikle sunucu (server) ve istemci (client) modeliyle çalışır. Süreç şu şekilde işler:

  1. Sunucu (Server) tarafı:
    • Sunucu programı bir sunucu soketi (server socket) oluşturur.
    • Soket, belirli bir port numarasına (örneğin, web sunucuları için 80) "bağlanır" (bind).
    • Sunucu, bu portu "dinlemeye" (listen) başlar ve bir istemcinin bağlanmasını pasif bir şekilde bekler.
    • Bir istemci bağlantı isteği gönderdiğinde, sunucu bu isteği "kabul eder" (accept). Bu işlem, istemci ile veri alışverişi yapmak için yeni bir soket daha oluşturur.
  2. İstemci (Client) tarafı:
    • İstemci programı kendi istemci soketini (client socket) oluşturur.
    • İstemci, sunucunun IP adresini ve port numarasını kullanarak sunucuya "bağlanır" (connect).
    • Bağlantı kurulduktan sonra, her iki taraf da soketleri kullanarak veri okuyabilir (read) ve yazabilir (write).
  3. İletişim tamamlandığında, her iki taraf da soketlerini kapatır (close).

Soket türleri

Temel olarak iki ana soket türü vardır ve kullanılacak iletişim türüne göre seçilirler:

1. TCP Soketleri (Akış Soketleri - SOCK_STREAM)

  • Bağlantı odaklıdır (Connection-Oriented): İletişim kurulmadan önce "3'lü el sıkışma" (three-way handshake) ile güvenilir bir bağlantı kurulur.
  • Güvenilirdir (Reliable): Gönderilen paketlerin sırası ve iletimi garanti altındadır. Kaybolan paketler yeniden gönderilir.
  • Akış bazlıdır (Stream-Based): Veri, sürekli bir bayt akışı olarak iletilir. Mesaj sınırları korunmaz.
  • Kullanım alanları: Web tarayıcıları (HTTP/HTTPS), e-posta (SMTP/IMAP), dosya transferi (FTP). Yani, veri bütünlüğünün önemli olduğu her yerde.

2. UDP Soketleri (Veri Bloğu Soketleri - SOCK_DGRAM)

  • Bağlantısızdır (Connectionless): Öncesinde bir bağlantı kurulmaz. Veri doğrudan hedefe gönderilir.
  • Güvenilir değildir (Unreliable): Paketlerin hedefe ulaşıp ulaşmayacağı, sıralarının doğru olup olmayacağı garanti edilmez.
  • Mesaj bazlıdır (Datagram-Based): Veri, bir bütün halinde paketler (datagram) halinde gönderilir. Mesaj sınırları korunur.
  • Hızlıdır: TCP'nin bağlantı ve hata kontrolü yükü olmadığı için genellikle daha hızlıdır.
  • Kullanım alanları: Canlı video yayını, online oyunlar, DNS sorguları, sesli/ görüntülü sohbet. Yani, hızın güvenilirlikten daha önemli olduğu, küçük veri kayıplarının sorun olmadığı durumlarda.

* bind(), listen() ve accept() fonksiyonları sadece sunucuda kullanılır.

* connect() fonksiyonu sadece istemcide kullanılır.

Winsock hakkında

Windows işletim sistemlerinde ağ iletişimi için Windows Sockets API (Winsock) kullanılır. Winsock, Windows uygulamalarının TCP/IP ve diğer ağ protokollerini kullanarak iletişim kurmasını sağlayan bir programlama arayüzüdür (API).

Temel amacı, uygulama geliştiricilerin, işletim sisteminin çekirdek düzeyindeki karmaşık ağ detaylarına girmeden ağ üzerinden veri gönderip alabilmelerini sağlamaktır.

Winsock, uygulamaların aşağıdakileri yapmasına olanak tanır:

  • Soket oluşturma: Ağ iletişimi için uç noktalar olan soketler oluşturmak.
  • Bağlantı kurma: TCP gibi bağlantı tabanlı protokollerde sunucuya bağlanmak veya bağlantıları kabul etmek.
  • Veri gönderme ve alma: Soketler üzerinden veri paketleri (datagram veya akış şeklinde) göndermek ve almak.
  • Adresleme: IP adresi ve port numarası gibi ağ adreslerini çözümlemek.
  • Protokol seçimi: TCP (Bağlantı tabanlı) veya UDP (Bağlantısız) gibi farklı protokolleri kullanmak.

Server işlemleri

Sunucuda yapılan işlemler aşağıdaki sıra ile gerçekleştirilmektedir:

1. Winsock başlatma

Winsock fonksiyonlarını çağıran tüm işlemler (uygulamalar veya DLL'ler), diğer Winsock fonksiyonlarını çağırmadan önce Windows Sockets DLL'sini başlatmalıdır. Bu aynı zamanda Winsock'un sistemde desteklendiğinden de emin olmamızı sağlar.

Winsock'u başlatmak için, wsaData adında bir WSADATA nesnesi oluşturmalı ve WSAStartup fonksiyonunu bu nesne ile çağırarak geri dönüş değerini bir hata durumu için kontrol etmeliyiz.


WSADATA wsaData; // Windows Sockets uygulaması hakkında bilgi içerir.
int iResult;

// Winsock başlatma
iResult = WSAStartup(MAKEWORD(2,2), &wsaData); // WS2_32.dll'nin kullanımını başlatmak için çağrılır.
if (iResult != 0) {
    printf("WSAStartup hatası: %d\n", iResult);
    return 1;
}

2. Sunucu için soket oluşturma

Sunucuda kullanılmak üzere bir soket oluşturmak için,

  • getaddrinfo() fonksiyonu ile sockaddr yapısındaki değerleri belirlenir:
    • AF_INET, IPv4 adres ailesini belirtmek için kullanılır.
    • SOCK_STREAM, bir akış soketini belirtmek için kullanılır.
    • IPPROTO_TCP, TCP protokolünü belirtmek için kullanılır.
    • AI_PASSIVE bayrağı, çağıranın döndürülen soket adres yapısını bind() fonksiyonuna yapılan bir çağrıda kullanmayı amaçladığını gösterir. AI_PASSIVE bayrağı ayarlandığında ve getaddrinfo() fonksiyonunun nodename parametresi NULL işaretçisi olduğunda, soket adres yapısının IP adresi kısmı IPv4 adresleri için INADDR_ANY veya IPv6 adresleri için IN6ADDR_ANY_INIT olarak ayarlanır.
    • 8080, istemcinin bağlanacağı sunucuyla ilişkili bağlantı noktası numarasıdır.
    
    #define DEFAULT_PORT "8080"
    
    struct addrinfo *result = NULL, *ptr = NULL, hints;
    
    ZeroMemory(&hints, sizeof (hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;
    
    // Sunucu tarafından kullanılan lokal adres ve bağlantı noktası bilgilerini alır.
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo hatası: %d\n", iResult);
        WSACleanup();
        return 1;
    }		
    
    
  • Soket oluşturmak ve kontrol etmek için,
    • Sunucunun istemci bağlantılarını dinlemesi için ListenSocket adında bir SOCKET nesnesi oluşturalım.
    • Socket fonksiyonunu çağırarak geri döndürülen değeri ListenSocket değişkenine atayalım.
    • Soketin geçerli bir soket olduğundan emin olmak için hataları kontrol edelim.
    
    SOCKET ListenSocket = INVALID_SOCKET;
    
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    
    

3. Soketi bağlama

Bir sunucunun istemci bağlantılarını kabul edebilmesi için sistem içindeki bir ağ adresine bağlı olması gerekir. İstemci uygulamaları, ana bilgisayar ağına bağlanmak için IP adresini ve bağlantı noktasını kullanır.

Bir soketi bağlamak için,

  • Oluşturulan soketi ve getaddrinfo() fonksiyonundan dönen sockaddr yapısını parametre olarak geçirerek bind fonksiyonunu çağırın ve genel hataları kontrol edin.
  • bind() fonksiyonu çağrıldıktan sonra, getaddrinfo() fonksiyonu tarafından döndürülen adres bilgilerine artık ihtiyaç duyulmaz. freeaddrinfo() fonksiyonu, getaddrinfo() fonksiyonu tarafından bu adres bilgileri için ayrılan belleği boşaltmak için çağrılır.

// TCP dinleme soketini oluşturma
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
	printf("bind hatası: %d\n", WSAGetLastError());
	freeaddrinfo(result);
	closesocket(ListenSocket);
	WSACleanup();
	return 1;
}

freeaddrinfo(result);

4. Soketi dinleme

Soket, sistemdeki bir IP adresine ve bağlantı noktasına bağlandıktan sonra, sunucu gelen bağlantı istekleri için bu IP adresini ve bağlantı noktasını dinlemelidir.

Bir soketi dinlemek için, Listen() fonksiyonunu çağırın ve parametre olarak oluşturulan soketi ve bekleme listesi değerini, kabul edilecek bekleyen bağlantıların maksimum uzunluğunu belirtin.


if (listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR) {
    printf("Listen hatası: %ld\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

5. Bağlantı kabul etme

Soket bir bağlantıyı dinlemeye başladıktan sonra, program o soket üzerindeki bağlantı isteklerine işlem yapmalıdır.

Normalde bir sunucu uygulaması, birden fazla istemciden gelen bağlantıları dinleyecek şekilde tasarlanır. Yüksek performanslı sunucularda, birden fazla istemci bağlantısını işlemek için genellikle birden fazla iş parçacığı (thread) kullanılır.

Bir sokete bağlantı kabul etmek için,

  • İstemcilerden gelen bağlantıları kabul etmek için ClientSocket adında geçici bir SOCKET nesnesi oluşturun.
  • accept() fonksiyonu çağırarak sokete gelen bağlantı isteğini kabul edin ve genel hataları kontrol edin.
  • accept() fonksiyonu çağrıldıktan sonra, ListenSocket'a artık ihtiyaç duyulmağından, closesocket() fonksiyonu çağrılarak soket kapatılır.

SOCKET ClientSocket;

ClientSocket = INVALID_SOCKET;

// Bir istemci soketini kabul etme
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
    printf("accept hatası: %d\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

closesocket(ListenSocket);

accept() fonksiyonu bloke edici (blocking) bir fonksiyondur. Bir istemci bağlantısı gelene kadar programın akışını durdurur ve bekler. Bu süre boyunca program accept() satırında "takılır" ve sonraki satırlara geçmez.

6. Sunucuda veri gönderme ve alma

Sunucu veri gönderip almak için send() ve recv() fonksiyonlarını kullanır. Her iki fonksiyon, gönderilen veya alınan bayt sayısını veya bir hatayı tam sayı olarak döndürür.


#define DEFAULT_BUFLEN 512

char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;

// İstemci bağlantıyı kapatana kadar al
do {
    iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0) {
        printf("Alınan byte sayısı: %d\n", iResult);

        // Tampon belleği gönderene yollar. 
        iSendResult = send(ClientSocket, recvbuf, iResult, 0);
        if (iSendResult == SOCKET_ERROR) {
            printf("send hatası: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
        printf("Gönderilen byte sayısı: %d\n", iSendResult);
    } 
	else if (iResult == 0)
        printf("Bağlantı kapanıyor...\n");
    else {
        printf("recv hatası: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }
} while (iResult > 0);

7. Sunucu bağlantısını kesme

Sunucu, istemciden veri almayı ve istemciye veri göndermeyi tamamladıktan sonra, istemciden bağlantıyı keser ve soketi kapatır.

Bir soketi ayırmak ve kapatmak için,

  • Sunucu istemciye veri göndermeyi tamamladığında, soketin gönderen tarafını kapatmak için SD_SEND belirterek shutdown() fonksiyonu çağrılabilir. Bu, istemcinin bu soket için bazı kaynakları serbest bırakmasına olanak tanır. Sunucu uygulaması soket üzerinden veri almaya devam edebilir.
  • İstemci uygulaması veri almayı tamamladığında, soketi kapatmak için closesocket() fonksiyonu çağrılır.
  • İstemci uygulaması Windows Sockets DLL kullanımını tamamlandığında, kaynakları serbest bırakmak için WSACleanup() fonksiyonu çağrılır.

iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ClientSocket);
    WSACleanup();
    return 1;
}

closesocket(ClientSocket);
WSACleanup();

return 0;

Client işlemleri

İstemcide yapılan işlemler aşağıdaki sıra ile gerçekleştirilmektedir:

1. İstemci için bir soket oluşturma

Başlatma işleminden sonra, istemci tarafından kullanılmak üzere bir SOCKET nesnesi oluşturulmalıdır.

Bir soket oluşturmak için,

  • Bir sockaddr yapısı içeren bir addrinfo nesnesi bildirin ve ilk değer ataması yapın. Uygulama, soket türünün TCP protokolü için bir akış soketi olmasını ister.
    
    struct addrinfo *result = NULL,
                    *ptr = NULL,
                    hints;
    
    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;	
    
    
  • Komut satırına iletilen sunucu adı için IP adresini isteyen getaddrinfo() fonksiyonunu çağırın. İstemcinin bağlanacağı sunucudaki TCP portu, bu örnekte DEFAULT_PORT tarafından 8080 olarak tanımlanmıştır. getaddrinfo() fonksiyonu hatalara karşı kontrol edilen bir tam sayı geri döndürür.
    
    #define DEFAULT_PORT "8080"
    
    // Sunucu adresini ve bağlantı noktası bilgilerini alır.
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }	
    
    
  • ConnectSocket adında bir SOCKET nesnesi oluşturun.
    
    SOCKET ConnectSocket = INVALID_SOCKET;
    
    
  • socket() fonksiyonunu çağırın ve geri döndürdüğü değeri ConnectSocket değişkenine atayın.
    
    ptr = result;
    
    // Sunucuya bağlanmak için bir SOKET oluşturur.
    ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
    
    
  • Soketin geçerli bir soket olduğundan emin olmak için hataları kontrol edin.
    
    if (ConnectSocket == INVALID_SOCKET) {
        printf("socket hatası: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }	
    
    

2. Bir sokete bağlanma

Bir istemcinin ağ üzerinde iletişim kurabilmesi için bir sunucuya bağlanması gerekir.

Bir sokete bağlanmak için, oluşturulan soketi ve sockaddr yapısını parametre olarak geçirerek connect() fonksiyonunu çağırın. Genel hataları kontrol edin.


// Sunucuya bağlan
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    closesocket(ConnectSocket);
    ConnectSocket = INVALID_SOCKET;
}

freeaddrinfo(result);

if (ConnectSocket == INVALID_SOCKET) {
    printf("Sunucuya bağlanamıyor!\n");
    WSACleanup();
    return 1;
}	

getaddrinfo() fonksiyonu, sockaddr yapısındaki değerleri belirlemek için kullanılır. sockaddr yapısında istemcinin bağlanmaya çalışacağı sunucunun IP adresi ile istemcinin bağlanacağı sunucudaki port numarası yer alır.

3. İstemcide veri gönderme ve alma

İstemci veri gönderip almak için send() ve recv() fonksiyonlarını kullanır. Her iki fonksiyon, gönderilen veya alınan bayt sayısını veya bir hatayı tam sayı olarak döndürür.


#define DEFAULT_BUFLEN 512

int recvbuflen = DEFAULT_BUFLEN;

const char *sendbuf = "Bu bir test mesajıdır!";
char recvbuf[DEFAULT_BUFLEN];

int iResult;

iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
    printf("send hatası: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("Gönderilen byte sayısı: %ld\n", iResult);

iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown hatası: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

do {
    iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0)
        printf("Alınan byte sayısı: %d\n", iResult);
    else if (iResult == 0)
        printf("Bağlantı kapandı!\n");
    else
        printf("recv hatası: %d\n", WSAGetLastError());
} while (iResult > 0);

4. İstemci bağlantısını kesme

İstemci veri gönderme ve alma işlemini tamamladığında sunucudan bağlantısını keser ve soketi kapatır.

Bir soketi ayırmak ve kapatmak için,

  • İstemci sunucuya veri göndermeyi tamamladığında, soketin gönderen tarafını kapatmak için SD_SEND belirterek shutdown() fonksiyonu çağrılabilir. Bu, istemcinin bu soket için bazı kaynakları serbest bırakmasına olanak tanır. İstemciu uygulaması soket üzerinden veri almaya devam edebilir.
  • İstemci uygulaması veri almayı tamamladığında, soketi kapatmak için closesocket() fonksiyonu çağrılır.
  • İstemci uygulaması Windows Sockets DLL kullanımını tamamlandığında, kaynakları serbest bırakmak için WSACleanup() fonksiyonu çağrılır.

iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown hatası: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

closesocket(ConnectSocket);
WSACleanup();

return 0;

Sunucu ve İstemci kodları

Aşağıda, TCP/IP Sunucu ve İstemci uygulamalarının kaynak kodunu yer almaktadır.

Sunucu uygulaması

  • İstemci uygulaması başlatılmadan önce sunucu uygulaması başlatılmalıdır.
  • Sunucuyu çalıştırmak için, sunucu kaynak kodunu derleyin ve çalıştırın. Kodu CodeBlocks IDE ortamında derlemek için, "Project - Build Options..." menüsünden açılan "Project Buid Options" penceresinin "Linker settings" sekmesinin "Other linker options" bölümüne -lWs2_32 değerini ekleyerek ws2_32.lib kütüphanesini yüklememiz gerekir.
  • Sunucu uygulaması, bir istemcinin bağlanmasını beklemek için 8080 numaralı TCP portunu dinler.
  • Bir istemci bağlandığında, sunucu istemciden veri alır ve alınan verileri istemciye geri gönderir (gönderir).
  • İstemci bağlantıyı kapattığında, sunucu istemci soketini kapatır, soketi kapatır ve çıkar.

İstemci uygulaması

  • İstemciyi çalıştırmak için, istemci kaynak kodunu derleyin ve çalıştırın. Kodu CodeBlocks IDE ortamında derlemek için, "Project - Build Options..." menüsünden açılan "Project Buid Options" penceresinin "Linker settings" sekmesinin "Other linker options" bölümüne -lWs2_32, -lMswsock ve -lAdvApi32 değerlerini ekleyerek ws2_32.lib, Mswsock.lib ve AdvApi32.lib kütüphanelerini yüklememiz gerekir.
  • İstemci uygulaması, istemci çalıştırıldığında sunucu uygulamasının çalıştığı bilgisayarın adının veya IP adresinin komut satırı parametresi olarak iletilmesini gerektirir. İstemci ve sunucu örnek bilgisayarda çalıştırılırsa, istemci aşağıdaki şekilde başlatılabilir:

    client localhost

  • İstemci, sunucuya 8080 numaralı TCP portundan bağlanmaya çalışır.
  • İstemci bağlandıktan sonra, sunucuya veri gönderir ve sunucudan gönderilen tüm verileri alır.
  • İstemci daha sonra soketi kapatır ve çıkar.

#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "8080"

int main(void)
{
    WSADATA wsaData;
    int iResult;

    // Yerel ayarları Türkçe'ye çevirme
    setlocale(LC_ALL, "Turkish");
	system("chcp 1254 >nul");

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int iSendResult;
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;

    // Winsock başlatma
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup hatası: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Sunucu adresi ve bağlantı noktası bilgilerini alma
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo hatası: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Sunucunun istemci bağlantılarını dinlemesi için bir SOCKET oluşturma
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket hatası: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // TCP dinleme soketi oluşturma
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind hatası: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen hatası: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    printf("=== Sunucusu başlatıldı... ===\n");
    printf("Port: %s\n", DEFAULT_PORT);
    printf("Bağlantı bekleniyor...\n");

    // İstemci soketini kabul etme
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept hatası: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // Sunucu soketini kapatma
    closesocket(ListenSocket);

    // İstemci bağlantıyı kapatana kadar alma işlemi devam eder.
    do {
        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);

        if (iResult > 0) {
            printf("Alınan byte sayısı: %d\n", iResult);

            // Alınan veriyi istemciye geri yollama
            iSendResult = send(ClientSocket, recvbuf, iResult, 0);
            if (iSendResult == SOCKET_ERROR) {
                printf("send hatası: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            printf("Gönderilen byte sayısı: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("Bağlantı kapanıyor...\n");
        else {
            printf("recv hatası: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
    } while (iResult > 0);

    // İstemci soketi üzerindeki gönderme ve alma işlemlerini devre dışı bırakır.
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown hatası: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

    // Temizleme işlemi
    closesocket(ClientSocket);
    WSACleanup();

    return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar ve istemciden bağlantı gerçekleşene kadar bekler:

=== Sunucusu başlatıldı... ===
Port: 8080
Bağlantı bekleniyor..

İstemci bağlandıktan sonra, aşağıdaki ifadeleri ekrana yazar ve program sona erer.

Alınan byte sayısı: 24
Gönderilen byte sayısı: 24
Bağlantı kapanıyor...
  • Program accept() fonksiyonunun yer aldığı satıra kadar çalışır.
    • Windows Sockets uygulaması hakkında bilgi içeren WSADATA yapısından wsaData adlı bir değişken oluşturur.
    • Konsolda Türkçe karakterlerin düzgün bir şekilde görünmesi için setlocale() ve system() fonksiyonlarını kullanır.
    • Bir dinleme (ListenSocket) ve istemci (ClientSocket) için olmak üzere 2 adet SOKET oluşturur.
    • WSAStartup() fonksiyonu ile Winsock DLL'nin kullanımını başlatır.
    • getaddrinfo() fonksiyonu ile sunucu adresi ve bağlantı noktası bilgilerini alır.
    • socket() fonksiyonu ile sunucunun istemci bağlantılarını dinlemesi için bir SOCKET oluşturur.
    • bind() fonksiyonu ile TCP dinleme soketi oluşturur.
    • listen() fonksiyonu ile soketi gelen bir bağlantıyı dinleme konumuna getirir.
  • accept() fonksiyonu bloke edici (blocking) bir fonksiyon olduğundan, bir istemci bağlantısı gelene kadar programın akışı durur ve bekler.
  • İstemci bağlantısı geldiğinde,
    • do while döngüsü içinde,
      • recv() fonksiyonu ile istemci soketinden gönderilen veriyi alır ve alınan byte sayısını ekrana yazar.
      • send() fonksiyonu ile aldığı veriyi istemci soketinden geri gönderir ve gönderilen byte sayısını ekrana yazar.
    • shutdown() fonksiyonu ile istemci soketi üzerindeki gönderme ve alma işlemlerini devre dışı bırakır.
    • closesocket() fonksiyonu ile istemci soketini kapatır.
    • WSACleanup() fonksiyonu ile Winsock 2 DLL'nin (Ws2_32.dll) kullanımını sonlandırır.

#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "8080"

int main(int argc, char **argv)
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo *result = NULL, *ptr = NULL, hints;
    const char *sendbuf = "Bu bir deneme mesajıdır!";
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    setlocale(LC_ALL, "Turkish");
	system("chcp 1254 >nul");

    // Validate the parameters
    if (argc != 2) {
        // Client.exe localhost veya Client.exe 127.0.0.1
        printf("Kullanım: %s server-name (Client 127.0.0.1)\n", argv[0]);
        return 1;
    }

    // Winsock başlatma
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup hatası: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Sunucu adresi ve bağlantı noktası bilgilerini alma
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo hatası: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Başarılı olana kadar bir adrese bağlanmaya çalışır.
    for(ptr=result; ptr != NULL; ptr=ptr->ai_next) {
        // Sunucuya bağlanmak için bir SOKET oluşturma
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket hatası: %d\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Sunucuya bağlanma
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Sunucuya bağlanamıyor!\n");
        WSACleanup();
        return 1;
    }

    // İlk bellek içeriğini gönderme
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send hatası: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    printf("Gönderilen byte sayısı: %d\n", iResult);

    // Bağlantıyı kapatma
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown hatası: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // Karşı taraf bağlantıyı kapatana kadar alma işlemi
    do {
        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0)
            printf("Alınan byte sayısı: %d\n", iResult);
        else if (iResult == 0)
            printf("Bağlantı kapatıldı\n");
        else
            printf("recv hatası: %d\n", WSAGetLastError());
    } while(iResult > 0);

    // Temizleme işlemi
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda (client localhost), aşağıdaki ifadeleri ekrana yazar ve program sona erer.

Gönderilen byte sayısı: 24
Alınan byte sayısı: 24
Bağlantı kapatıldı
  • Windows Sockets uygulaması hakkında bilgi içeren WSADATA yapısından wsaData adlı bir değişken oluşturur.
  • Bir bağlantı (ConnectSocket) SOKETİ oluşturur.
  • Konsolda Türkçe karakterlerin düzgün bir şekilde görünmesi için setlocale() ve system() fonksiyonlarını kullanır.
  • WSAStartup() fonksiyonu ile Winsock DLL'nin kullanımını başlatır.
  • getaddrinfo() fonksiyonu ile sunucu adresi ve bağlantı noktası bilgilerini alır.
  • socket() fonksiyonu ile sunucuya bağlanmak için bir SOCKET oluşturur.
  • connect() fonksiyonu ile sunucuya bağlanır.
  • send() fonksiyonu ile bir karakter dizisini sunucuya gönderir ve gönderilen byte sayısını ekrana yazar..
  • shutdown() fonksiyonu ile bağlantı soketi üzerindeki gönderme ve alma işlemlerini devre dışı bırakır.
  • Sunucudan mesaj geldiğinde,
    • do while döngüsü içinde,
      • recv() fonksiyonu ile sunucudan gönderilen veriyi alır ve alınan byte sayısını ekrana yazar.
      • Sunucu bağlantıyı kapattığında, "Bağlantı kapatıldı" ifadesini ekrana yazar.
    • closesocket() fonksiyonu ile bağlantı soketini kapatır.
    • WSACleanup() fonksiyonu ile Winsock 2 DLL'nin (Ws2_32.dll) kullanımını sonlandırır.