6 Kasım 2015 Cuma

TCP Soket Kullanımı

Giriş

TCP soketleri mantıksal soketlerdir. Sürekli veri göndermezler. Dolayısıyla akıllı telefonlarda pilin ömrünü azaltmazlar.

ThroughPut

Kablosuz iletişimde geçen throughput kelimesi Türkçeye yararlı yük olarak çevrilmiş. Sinyalleşme, CRC  gibi bitleri hariç bırakarak gönderilen yararlı veri anlamına geliyor. 10 byte veri göndermek için bir TCP paketi şunları içerir.

20 byte IPv4 zarfı (IPv6 ile 40 byte)
20 byte TCP zarfı
10 bytes veri

Yani toplam 50 byte gönderilir ve sadece 10 byte faydalı yüktür. Bu da yaklaşık %20 verime tekabül eder.


İstemci Soketi İçin Genel Kullanım
C'de normak socket() çağrısı, Java'da Socket, C#'ta Socket sınıfı kullanılır. Daha sonra sadece connect çağrısı yapılır. 

Sunucu Soketi İçin Genel Kullanım
C'de normak socket() çağrısı, Java'da ServerSocket, C#'ta TCPListener sınıfı kullanılır. Daha sonra sırasıyla bind(), listen(), accept() çağrıları yapılır. 

TCP İstemci Soketi Açmak
TCP İstemci Soketi yazısına taşıdım.

TCP Sunucu Soketi Açmak
C
Sunucu socket, bind, listen, accept sırası izlenerek açılır.
struct sockaddr_in my_addr;
struct sockaddr_in remote_addr;


memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = inet_addr("192.168.103.128");
my_addr.sin_port = htons(8000);
int s = socket(AF_INET, SOCK_STREAM, 0)) < 0)

bind(s,(struct sockaddr *)&my_addr,sizeof(struct sockaddr));
listen(s, 5);
socklen_t sin_size = sizeof(struct sockaddr_in);
int fd = accept(s,(struct sockaddr *)&remote_addr,&sin_size));
sockaddr_in hazırlanması
dinlenilmek istenen IP adresi verilmek istenmezse şöyle yapılır.

sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");//INADDR_ANY ile tüm arayüzler kullanılır

INADDR_ANY 0.0.0.0 anlamına gelir. Bu özel sayı non-routable bir meta adrestir. Invalid, unknown, non applicable network entity anlamına gelir.

bind çağrısı
bind işleminden sonra socketin hangi portu kullandığını öğrenmek istiyorsak getsockname() metodu kullanılabilir. Örnek: Metodu çağırmadan önce len = sizeof(sin) yapmayı unutmamak lazım.

listen çağrısı
listen çağrısında 5 gibi bir sabit vermek yerine SOMAXCONN kullanılabilir.
listen(server,SOMAXCONN);

SOMAXCONN bits/socket.h dosyasında tanımlı bir sayı. Bu sayı yerine istenilen herhangi bir sayı da kullanılabilir.

accept çağrısı
accept() meodu thread-safe ve reentrant bir metod olduğu için thread pool içinde rahatça kullanılabilir.


Java
ServerSocket yazısına bakabilirsiniz.

Gelen isteği kabul etmek için

Bir başka örnekte ise nio ve ServerSocketChannel kullanılmış. Bu sınıf SocketChannel döndürür.

ServerSocketChannel serverchannel = ServerSocketChannel.open();
serverchannel.bind(new InetSocketAddress("localhost",4666));
serverchannel.configureBlocking(false);//returns immediately, and may thus return null
SocketChannel socketChannel = serverChannel.accept();
C#
TCPListener sınıfı kullanılabilir. Örnek:
Int32 port = 13000;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
TcpListener server = new TcpListener(localAddr, port);
// Start listening for client requests.
server.Start();

// Perform a blocking call to accept requests.
// You could also user server.AcceptSocket() here.

TcpClient client = server.AcceptTcpClient();  
Herhangi bir adres vermeden tüm ağ arayüzlerini kullanmak. Örnek:
var listener = new TcpListener(IPAddress.Any, 2222);
listener.Start();
TCP ile Veri Almak
C
recv ve read kullanılabilir. Her iki metod da aynı kapıya çıkıyorlar. Aralarındaki fark, read ile herhangi bir okuma seçeneği kullanılamıyor.

recv metodu
recv() kullanılıyor. Metodun imzası aşağıda.
ssize_t
 recv(int socket, void *buffer, size_t length, int flags);

 

RETURN VALUES
 These calls return the number of bytes received, or -1 if an error occurred.

length parametresi
Burada length parametresinin SSIZE_MAX değerinden büyük olmasının portability kabiliyetini azaltacağı yazılmış.

flags parametresi
Metodu çağırırken flags parametresi genellikle 0 geçilir.
char buf[2000];
recv(sockfd, buf, len, 0);

flag olaral MSG_PEEK
Veriyi soket tamponundan okumadan sadece göz atabilmemizi sağlar.


return value
Kaç byte okunduğunu döner. Eğer string okumak istiyorsak şöyle yapabiliriz.
Eğer karakter
char msg[1024];
ssize_t n = recv(fd, msg, 1023, 0);
if (n >= 0)
 msg[n] = '\0';


read metodu
read de kullanılabilir. Gönderilen tüm veriyi okumak için örnek:
stringstream s;
s.str("");// resets the internal string
char recvBuff [2048];
while((int n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0)
{
    recvBuff[n] = 0;
    s << recvBuff;
}

TCP ile Veri Göndermek
C
write metodu ile veri gönderilir. Metodun imzası şöyledir.
ssize_t write(int fildes, const void *buf, size_t nbyte);
ssize_t signed size_t olduğunu gösterir. size_t SSIZE_MAX kadar değer alır.

Write metodu verilen tüm veriyi göndermek zorunda değildir. Kısmi olarak gönderebilir.

Java
Metin göndermek
Aşağıdaki örnekte metin verisi göndermek ve alma işlemi gösterilmiş.



UTF veri göndermek istersek :
ServerSocket socketServer=...
DataOutputStream out=new DataOutputStream(server.getOutputStream());
String send=...
out.writeUTF(send);
Binary veri göndermek
Örnekte bir nesnenin ObjectOutputStream sınıfı kullanılarak TCP ile gönderilip alınması gösterilmiş.

TCP Sliding Window
TCP Sliding Window yazısına taşıdım.

Nagle Algoritması

Kablosuz iletişimde geçen throughput kelimesi Türkçeye yararlı yük olarak çevrilmiş. Sinyalleşme, CRC  gibi bitleri hariç bırakarak gönderilen yararlı veri anlamına geliyor. 10 byte veri göndermek için bir TCP paketi şunları içerir.

20 byte IPv4 zarfı (IPv6 ile 40 byte)
20 byte TCP zarfı
10 bytes veri



Yani toplam 50 byte gönderilir ve sadece 10 byte faydalı yüktür. Bu da yaklaşık %20 verime tekabül eder. Bu verimsizlikten kurtulmak için Nagle algoritması kullanılıyor. Kısaca küçük paketleri birleştirerek gönderen bir algoritma. Algoritma sebebiyle bir paket Unix'te 0.2 saniye yani 200 ms. kadar bekletilebilir. Dolayısıyla verim artarken gecikme de artabiliyor.

Bazen paketleri hemen göndermek gerekebilir. O zaman şöyle yapıyoruz.

C
Örnek
int flag = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
C#
Örnek:
TcpClient client = new TcpClient(...);
client.NoDelay = true;  

TCP INPUT

TCP Input Queue

TCP socketinde bir "input queue" bulunur. Linux üzerinde bu kuyrukta recv ile kaç byte veri okunabileceğini görmek için bir ioctl komutu bulunmakta.


int value;
error =ioctl(s,FIONREAD,&value);

TCP State'leri

Aşağıdaki şekilde getsockopt() ile socket'in bulunduğu state'i öğrenme örneği var.


3 Way Handshake
LISTEN ve SYNC_RECVD state'leri kullanılır.

TCP Handshake yazısına taşıdım.

FIN_WAIT_1

Bu durumda soketimiz karşı tarafa FIN mesajı göndermiş ve karşı tarafın ACK ile mesajı aldığını onaylamasını bekliyor demektir.

Burada yazdığına göre, established yani connect durumdaki bir socket data gönderir ancak ACK alamazsa, active close işlemine de başlamadan, karşı tarafın öldüğü kabul ederek sessizce bağlantıyı kapatırmış.

TIME_WAIT
Eğer server socket kullanıyorsak, sunucu kapatılınca işletim sistemi socketin kullandığı portu TIME_WAIT'e alır. Amacımız sunucuyu hemen tekrar açmak ise aşağıdaki  SO_REUSEADDR seçeneği ile işletim sistemine socket'in portunu hemen kullanıma açmasını söyleyebiliriz. Bu işlem Java'da aşağıdaki gibi yapılıyor.
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.setReuseAddress(true);
Aslında SO_REUSEADDR ne yapılması gerektiğini protokolün gerçekleştirmesine bırakmış.



1 yorum: