Ozan Şelte
Mühendis Adayı


C ile Soket Programlama Rehberi
7 Eylül 2015 - ProgramlamaC ve C++

Bu yazıda hem temel hem de orta düzeyde soket programlamayı göstereceğim. Baştan belirteyim winsock kullanmayacağım. Çünkü Windows kullanmıyorum ve taşınabilirlik birçok şeyden önemli.

Malzeme listesi:

  • Bilgisayar(mümkünse Linux ya da Unix kurulu)
  • gcc[GNU(Gnu’s Not Unix) C Compiler]
  • Editör(Notepad, vim, nano, Sublime Text <3)
  • Siz

Soket türleri

Burada kendi kullandığım iki soket türünü anlatacağım. SOCK_DGRAM ve SOCK_STREAM. Bilinen isimleriyle sırasıyla UDP ve TCP.

SOCK_DGRAM - UDP

Kullanıcı Veri Paketi Protokokü - User Datagram Protocol - RFC 768
UDP protokolünde üçlü el sıkışma uygulanmaz, dolayısıyla bağlantı gerekmez. Sadece bir uçtan diğer uca veri gönderilir. Verinin hangi sırada gideceği ya da gideceği garanti edilmez. Öylece gönderilir. TCP’ye göre biraz daha hızlıdır. Avantajı bağlantının açık kalması gibi bir zorunluluğun bulunmamasıdır.

SOCK_STREAM - TCP

Aktarım Denetim Protokolü - Transmission Control Protocol - RFC 793
Bu protokolde üçlü el sıkışma uygulanır. Sırasıyla:

  • İstemci » SYN »> Sunucu
  • İstemci « SYN+ACK « Sunucu
  • İstemci » ACK « Sunucu

yolu izlenerek bağlantı sağlanır. Gönderilen veriler doğru sırada karşıya iletilir. Protokol hatalara karşı korumalı olduğundan görülebilecek hatalarda sorumlu siz olursunuz.

Anlatılmayacaklar

SOCK_RAW - SOCK_RDM - SOCK_SEQPACKET - SOCK_PACKET

Bilinmesi gerekenler

errno.h, burada belirtilen fonksiyonların bulunduğu dosya.
int errno, hata durumunda -1 döndüren fonksiyonların hatalarının, ne olduklarını belirten bir değişkendir.
char * strerror ( int errnum ) , errno değerine göre hatanın açıklamasını döndüren fonksiyon.
void perror ( const char * str ) , aşağıdaki iki kodun aynı anlama geldiğini söylersem hemen anlaşılır. Kısaca mesajın sonuna ”: “ ve hata açıklaması ekleyerek ekrana basar.

1
2
printf("Mesaj: %s\n", strerror(errno));
perror("Mesaj");

Soket oluşturma

Adres aileleri
  • AF_UNIX - Unix domain socket
  • AF_INET - Internet Protocol v4 socket
  • AF_INET6 - Internet Protocol v6 socket

socket

Integer türündedir, hata durumunda -1 döndürür ve errno değişkenine hata kodunu yazar. Parametrelerine sırasıyla soket türü, adres ailesi ve protokol numarası yazılır. Protokol-0 IPPROTO_IP olarak geçer ve IP protokolüdür.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

//Örnek kullanım
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
  //Ben mesaj kısmına hatayı hangi fonksiyondan
  //aldığımı yazmayı tercih ediyorum. Kolaylık sağlıyor.
  perror("socket");
}

struct türleri

Elimizde üç struct var. sockaddr fonksiyonlarımızın alacağı adres yapısı, sockaddr_in ise bizim okumamız için basitleştirilmiş hali. Son anda fonksiyona verirken dönüştürceğiz. sin_zero , bu dönüştürme işleminin düzgün sağlanması için boyutları eşitleme adına konular bir değer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <netinet/in.h>

struct sockaddr {
  unsigned short sa_family; //adres ailesi
  char sa_data[14]; //protokol adresi
};

struct sockaddr_in {
  short sin_family; //adres ailesi
  unsigned short sin_port; //port
  struct in_addr sin_addr; //adres
  char sin_zero[8]; //sıfır
};

struct in_addr {
  unsigned long s_addr; //32bit adres
};

Ağ-Ev Sahibi dönüşümleri

Big-Endian ve Little-Endian karmaşını önlemek adına ortaya çıkan dönüşüm fonksiyonlarıdır. Çoğunlukla adres ve port belirtmede kullanılırlar.

1
2
3
4
5
6
7
#include <netinet/in.h>

uint32_t htonl(uint32_t hostlong); //host to network long
uint16_t htons(uint16_t hostshort); //host to network short
uint32_t ntohl(uint32_t netlong); //network to host long
uint16_t ntohs(uint16_t netshort); //network to host short
in_addr_t inet_addr(const char *cp); //ip adresi donusturur

Adresler

Sunucu oluştururken dinleme yapmak ve kabul etmek için, istemci oluştururken ise sunucuyu belirtmek için kullanılırlar. Her bağlantı için bir adet oluşturulur.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct sockaddr_in serverAddr; //sunucu örneği
serverAddr.sin_family = AF_INET; //soket ile aynı aile olmalı
serverAddr.sin_port = htons(PORT); //short türünde port

//INADDR_ANY çalışan bilgisayarın adresi demektir.
//inet_addr'ye ihtiyaç duymaz.
serverAddr.sin_addr.s_addr = INADDR_ANY;
memset(&(serverAddr.sin_zero), '\0', 8); //Sıfırlar doldurulmalı!

//istemci örneği
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);

//Konuşacağımız sunucu adresini inet_addr ile belirtiyoruz.
serverAddr.sin_addr.s_addr = inet_addr(DEST_IP);
memset(&(serverAddr.sin_zero), '\0', 8);

bind

Oluşturulan soketi adres(port) ile eşleştirmeye yarar. İstemci tarafı için zorunlu değildir, belirtilmediğinde işletim sistemi boş bir port belirleyecektir. Dinleme yapacak sunucu tarafı için belirtilmesi zorunludur. Sırasıyla; soket, adres, ve adresin uzunluğunu argüman olarak alır. Hata durumunda -1 döndürür ve errno değişkenine hata numarası atar.

1
2
3
4
5
6
7
8
9
10
11
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
        socklen_t addrlen);

//Örnek kullanım
if (-1 == bind(sockfd, (struct sockaddr *)&serverAddr,
        sizeof(struct sockaddr))) {
  perror("bind");
}

connect

Dinlemede olan sokete/adrese bağlanmayı sağlar. SOCK_DGRAM türünde kullanılması zorunlu değildir. Hata durumunda -1 döndürür ve errno değişkenine hata numarası atar. Parametre olarak soket, adres ve adresin uzunluğunu alır.

1
2
3
4
5
6
7
8
9
10
#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

//Örnek kullanım
if (-1 == connect(sockfd, (struct sockaddr *)&serverAddr,
        sizeof(struct sockaddr))) {
  perror("connect");
}

listen

Soketin bağlantıları beklemeye yani belirtilen adresteki portu dinlemeye başlamasını sağlar. bind komutu sonrası kullanılır. SOCK_DGRAM için kullanılması zorunlu değildir. Hata durumunda -1 döndürür ve errno değişkenine hata numarası atar. Parametre olarak soketi ve kuyrukta bekletilecek bağlantı sayısını alır.

1
2
3
4
5
6
7
8
9
#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

//Örnek kullanım
if (-1 == listen(sockfd, 20)) {
  perror("listen");
}

accept

listen ile dinlemedeyken gelen bağlantıyı kabul eder ama sadece bir bağlantıyı kabul eder. Geriye yeni bir soket döndürür. SOCK_DGRAM için kullanılması zorunlu değildir. Hata durumunda -1 döndürür ve errno değişkenine hata numarası atar. İkinci parametresi ile yeni bir adres oluşturur.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

//Örnek Kullanım
//newfd yeni soketimiz. İstemci ile haberleşirken bunu kullanacağız.
int structSize, newfd;
struct sockaddr_in clientAddr;
structSize = sizeof(clientAddr);
newfd = accept(sockfd, (struct sockaddr *)&clientAddr, &structSize);
if (-1 == newfd) {
  perror("accept");
}

send, sendto, recv, recvfrom

send ve sendto veri göndermeye, recv ve recvfrom ise veri almaya yarayan fonksiyonlardır. send ve recv connect ile bağlanılan adreslerle çalışırlar. sendto ve recvfrom fonksiyonları parametre olarak adres de alırlar. SOCK_STREAM ile send ve recv fonksiyonları kullanılır. SOCK_DGRAM ile çalışılırken connect ile bağlantı yapıldıysa send ve recv kullanılabilir, onun dışında sendto ve recvfrom kullanılarak adres belirtilmelidir. Her zamanki gibi hata durumunda -1 döndürür ve errno ataması yaparlar. Başarılı durumlarda send ve sendto gönderilen bayt sayısını döndürür. recv ve recvto ise alınan bayt değerini dönrürür. Gönderilen bayt sayısı gönderilmek istenilenden eksik olabilir. Kontrol etmek ve düzeltme uygulamak sizin sorumluluğunuza kalır. recv bağlantının kesilmesi durumunda özel olarak 0 döndürür. Kontrol sistemi olmadığı için recvfrom ile bağlantı açık mı kapalı mı öğrenilemez.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
        const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
        struct sockaddr *src_addr, socklen_t *addrlen);

//Örnek kullanımlar
char *gonder = "ozanselte.com";
int gidenBayt;
gidenBayt = send(sockfd, gonder, strlen(gonder), 0);
gidenBayt = sendfrom(sockfd, gonder, strlen(gonder), 0,
        (struct sockaddr *)&addr, sizeof(struct sockaddr));
if (-1 == gidenBayt) {
  perror("send");
}
else if (strlen(gonder) != gidenBayt) {
  printf("Gönderilen: %d \t Giden: %d\n",
          strlen(gonder), gidenBayt);
}

#define AZAMIUZUNLUK 1024
char str[AZAMIUZUNLUK];
int gelenBayt;
gelenBayt = recv(sockfd, &str, AZAMIUZUNLUK-1, 0);
gelenBayt = recvfrom(sockfd, &str, AZAMIUZUNLUK-1, 0,
        (struct sockaddr *)&addr, sizeof(struct sockaddr));
if (-1 == gelenBayt) {
  perror("recv");
}
else if (0 == gelenBayt) {
  printf("Bağlantı kapalı.\n");
}

close

Soketi ve bağlantıyı düzgünce kapatır. İki taraf da veri gönderip alamaz. Tek parametre olarak soketi alır.

1
2
3
4
close(int fd);

//Örnek Kullanım
close(sockfd);

Son

Örnek sunucu ve istemci kodlarını da vererek rehberi tamamlıyorum. Sorularınızı ve isteklerinizi yorum olarak belirtebilirsiniz.

Sunucu

sunucu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

#define LISTENPORT 2015
#define AZAMIUZUNLUK 1024

int main() {
	int sockfd, newfd; 
	struct sockaddr_in serverAddr;
	struct sockaddr_in clientAddr;
	char *gonder = "ozanselte.com";
	char str[AZAMIUZUNLUK];
	int gelenBayt, gidenBayt, structSize;

	sockfd = socket(AF_INET, SOCK_STREAM, 0); 
	if (-1 == sockfd) {
		perror("socket");
	}

	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(LISTENPORT);
	serverAddr.sin_addr.s_addr = INADDR_ANY;
	memset(&(serverAddr.sin_zero), '\0', 8);

	if (-1 == bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr))) {
		perror("bind");
	}

	if (-1 == listen(sockfd, 20)) {
		perror("listen");
	}

	structSize = sizeof(clientAddr);
	newfd = accept(sockfd, (struct sockaddr *)&clientAddr, &structSize);
	if (-1 == newfd) {
		perror("accept");
	}

	gelenBayt = recv(newfd, &str, AZAMIUZUNLUK-1, 0);
	if (-1 == gelenBayt) {  
		perror("recv");
	}
	else if (0 == gelenBayt) {  
		printf("Bağlantı kapalı.\n");
	}
	printf("%d bayt aldım:\t%s\n", gelenBayt, str);

	gidenBayt = send(newfd, gonder, strlen(gonder), 0);
	if (-1 == gidenBayt) {  
		perror("send");
	}
	else if (strlen(gonder) != gidenBayt) {
		printf("Gönderilen: %d\tGiden: %d\n", strlen(gonder), gidenBayt);
	}
	printf("%d bayt gönderdim:\t%s\n", gidenBayt, gonder);

	close(newfd);
	close(sockfd);
}

İstemci

istemci

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

#define SUNUCUPORT 2015
#define SUNUCUIP "127.0.0.1"
#define AZAMIUZUNLUK 1024

int main() {
	int sockfd; 
	struct sockaddr_in serverAddr;
	char *gonder = "Kaynak hangi site?";
	char str[AZAMIUZUNLUK];
	int gelenBayt, gidenBayt, structSize;

	sockfd = socket(AF_INET, SOCK_STREAM, 0); 
	if (-1 == sockfd) {
		perror("socket");
	}

	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(SUNUCUPORT);
	serverAddr.sin_addr.s_addr = inet_addr(SUNUCUIP);
	memset(&(serverAddr.sin_zero), '\0', 8);

	if (-1 == connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr))) {
		perror("connect");
	}

	gidenBayt = send(sockfd, gonder, strlen(gonder), 0);
	if (-1 == gidenBayt) {  
		perror("send");
	}
	else if (strlen(gonder) != gidenBayt) {
		printf("Gönderilen: %d\tGiden: %d\n", strlen(gonder), gidenBayt);
	}
	printf("%d bayt gönderdim:\t%s\n", gidenBayt, gonder);

	gelenBayt = recv(sockfd, &str, AZAMIUZUNLUK-1, 0);
	if (-1 == gelenBayt) {  
		perror("recv");
	}
	else if (0 == gelenBayt) {  
		printf("Bağlantı kapalı.\n");
	}
	printf("%d bayt aldım:\t%s\n", gelenBayt, str);

	close(sockfd);
}