Sunday, January 3, 2016

Soket Programlama

İstemci-sunucu mimarisi, bilgisayar ağı ile birbirine bağlı iki yazılımın biri birinden hizmet almasını amaçlar. Bu programlardan sunucu yazılım hizmet verirken, istemci yazılım ise hizmet alan taraftır. Sunucunun verdiği hizmet, zamanı söylemek kadar basit olabileceği gibi dosya aktarımı ya da e-posta göndermek gibi daha karmaşık servisler de olabilir. İstemcinin bu hizmetleri alabilmesi için önce sunucu ile bağlantı kurması ve daha sonra bağlantıyı kullanarak servise erişmesi gerekir. Soket, bilgisayar ağı üzerinden bu bağlantının kurulması ve servise ulaşılması için gerekli olan yazılım alt yapısını sunar. Soket, işletim sistemlerince desteklenen, farklı programlama dillerinden uzaktan ulaşabileceğimiz bir yazılım erişim noktasıdır. Soket programlama modeli istemci-sunucu mimarisi ile uyumludur. 
İstemci-Sunucu Modeli
Sunucu ve istemci tarafta kavramsal olarak hangi işlemlerin gerçekleştirildiğine bir bakalım. Sunucu tarafta Soket haberleşmenin adımları:
  1. Servisi duyur
  2. Bağlantı için bekle
  3. Bağlanan istemci için servis yap
  4. İstemci ile bağlantıyı kopar
  5. İkinci adıma geri dön
İstemci tarafta Soket haberleşmenin adımları:
  1. Servisin sunucusunu bul
  2. Sunucu ile bağlantı kur
  3. Servis için istek gönder
  4. Sunucuya ile olan bağlantıyı kopar
İstemci taraftaki en önemli fark, sunucu taraftakine benzer bir döngünün bulunmamasıdır. Bu yazıda soket programlamayı önce Unix platformunda C dilini kullanarak çalışacağız. Daha sonra Java'daki ve C++'daki çözümleri inceleyeceğiz.

Unix'de Soket Programlama

Unix'de soket haberleşme aynı makinada çalışan yazılımlar arasında yerel olarak gerçekleştirilebileceği gibi bilgisayar ağı ile biri birine bağlı farklı makinalardaki yazılımlar arasında da gerçekleştirilebilir:
  • AF_UNIX Hafif sıklet bir haberleşme sağlar. Haberleşme aynı makinada çalışan iki yazılım arasında yerel olarak dosya sistemi üzerinden gerçekleşeceği için veri kaybı ve paketlerin yeniden sıralanması gibi durumlar oluşmaz. Bu nedenle haberleşme başarımı yüksektir. Örneğin, Unix platformunda MySQL sunucusu ile haberleşmede bu yöntem kullanılabilinir:
mysql --socket=/tmp/mysql.sock
  • AF_INET Internet adresler kullanılarak farklı makinalardaki yazılımların haberleşmesi gerekiyor ise bu soket türü tercih edilmelidir. İstenirse aynı makinadaki yazılımlar da biri biriyle bu soket türünü kullanarak haberleşebilirler. Bu nedenle bu yazıda özellikle bu tür soketleri inceleyeceğiz.
İster AF_UNIX soket kullanmış olun isterse de AF_INET soket kullanmış olun, istemci ile sunucu arasındaki iletişim protokolü son derece yalındır:
Şekil-1 İstemci sunucu mimarisinde soket programlamada istemci ile sunucu arasındaki çağrılar ve sıralaması 
Sunucu bağlantı kuran istemciden kaç Sayısal Loto kuponu oynamak istediğini tam sayı olarak okur ve daha sonra bu sayı kadar 1-49 aralığında biri birinden farklı ve sıralı altı tane sayı oluşturur ve istemciye gönderir. Bunu önce AF_UNIX soket kullanarak kodlayalım:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <stdlib.h>
#include <fcntl.h>

int compare_ints(const void * elem1, const void * elem2){
 int f = *((int*)elem1);
 int s = *((int*)elem2);
 if (f > s) return  1;
 if (f < s) return -1;
 return 0;
}

int find(int element,int *array,int length){
 int i;
 for (i=0;i<length;++i){
  if (element == array[i])
   return i;
 }
 return -1;
}

int populateLottery(char *buffer){
 int i,k=0,candidate,numbers[6]={50,50,50,50,50,50};
 numbers[0]= rand() % 49 + 1;
 for (i=1;i<6;++i){
  do {
    candidate= rand() % 49 + 1;
  } while(find(candidate,numbers,6)>=0);
     numbers[i]= candidate;
 }
 qsort(numbers,6,sizeof(int),compare_ints);
 for (i=0;i<6;++i){
  if (i<5)
        k += sprintf(buffer+k,"%d,",numbers[i]);
     else
     k += sprintf(buffer+k,"%d\n",numbers[i]);
 }
 return k;
}

void service(int connection){
 static int buffer_size,num,i;
 char *buffer= malloc(18*sizeof(char));
 read(connection,&num,sizeof(num),0);
 for (i=0;i<num;++i){
  buffer_size= populateLottery(buffer);
  write( connection, buffer, buffer_size,0);
 }
    close(connection); 
 free(buffer);
}

int main() {
  int sd,i,comm;
  struct sockaddr_un myname, client;
  int namesize = sizeof(struct sockaddr_un);
  int clientsize = sizeof(struct sockaddr_un);
 
  srand(time(0L));   
  
  /* Create server's rendezvous socket sd */
  if( (sd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
    perror("srvr1.c:main:socket");
    exit(1);
  }

  /* Fill in server's address and bind it to sd */
  myname.sun_family = AF_UNIX;
  strcpy(myname.sun_path, "/tmp/socket1");
  unlink( myname.sun_path );

  if( bind(sd, (struct sockaddr*)&myname, namesize) == -1 ) {
    perror("srvr1.c:main:bind");
    exit(1);
  }

  /* Prepare to receive multiple connect requests */
  if( listen(sd, 128) == -1 ) {
    perror("srvr1.c:main:listen");
    exit(1);
  }

  /* Infinite loop to accept client requests */
  while(1) {
    comm = accept(sd, (struct sockaddr*)&client, &clientsize);
    if ( comm  == -1) {
      perror("srvr1.c:main:accept");
      exit(1);
    }
    service(comm);
  }

  return 0;
} 
accept() çağrısı bağlantı kurulana kadar sunucu kodunu bloke eder. accept() çağrısı soketi tanımlayan kimlik numarası ile döner. Bu bir tekil tam sayı değerdir. Bu kimlik değeri write() ve read() çağrılarında okuma ve yazma yapılacak kaynağın kimliği olarak birinci parametrenin değeri olarak kullanılır. Unix soketi aynı makinadaki iki proses arasındaki veri haberleşmesi için bilgisayar ağını değil dosya sistemini kullanır. Soket haberleşme için kullanılan dosyanın türü soket olarak dosya sisteminden izlenebilir:
srwx---r-x  1 bkurt bkurt 0 Jan  2 16:30 lottery.socket

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>

int main(int argc,char *argv[]) {

  int namesize = sizeof(struct sockaddr_un);
  int sd,i,sz,num;
  struct sockaddr_un srvr;
  char *buffer= (char *) malloc(18*sizeof(char));
  
  /* Create client's socket sd */
  if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 
    perror("clnt1.c:main:socket"); 
    exit(1); 
  }

  /* Fill in server's address and connect to server */
  srvr.sun_family = AF_UNIX;
  strcpy(srvr.sun_path, "/tmp/socket1");

  if( connect(sd, (struct sockaddr*)&srvr, namesize) == -1 ) { 
    perror("clnt1.c:main:connect"); 
    exit(1); 
  }

  /* Communicate with server */
  num = atoi(argv[1]);
  write(sd,&num,sizeof(num),0);
  for (i=0;i<num;++i){
    do {
 sz= read (sd, buffer, sizeof(18*sizeof(char)),0);
 buffer[sz]='\0';
 printf("%s",buffer);
    } while(sz>0);
  }
  printf("\n");
  close(sd);
  
  return 0;
}
Aynı problemi bu kez AF_INET soket kullanarak kodlamaya çalışalım. Şekil-1 ile verilen akışta bir değişiklik olmayacak. Farklılığın sunucuyu tanımlama yöntemi ile ilgili olduğuna dikkat edin. Artık iletişim bilgisayar ağı ile bağlı iki makine arasında gerçekleşiyor. IP adresi ya da DNS adı kullanarak sunucuyu ayırt etmeye çalışacağız. Bu amaçla gethostbyname()  çağrısı kullanılır. socket() çağrısı ile ip adresi ile erilen ara yüz için soket yaratılır. Daha sonra bu soket üzerinden verilen bir port numarası üzerinden gelen istekleri dinlemek üzere bind() çağrısı ile uygulamanın porta ile bağı kurulur. Port 16 bitlik bir tam sayıdır. Bağ kurulan port numarasının kullanılmayan port numaralarından seçilmelidir. İlk 1024 port numarası sistem tarafından kullanılmıştır. Uygulamaların 1024’den sonraki port numaralarından kullanmaları istenir. Sistemdeki hangi port numaralarının kullanımda olduğu netstat komutu kullanılarak öğrenilebilir:
$ netstat -a 

Active Connections

  Proto  Local Address          Foreign Address        State
  TCP    0.0.0.0:135            server1.example.com:0              LISTENING
  TCP    0.0.0.0:445            server1.example.com:0              LISTENING
  TCP    0.0.0.0:1536           server1.example.com:0              LISTENING
  TCP    0.0.0.0:1537           server1.example.com:0              LISTENING
  TCP    0.0.0.0:1538           server1.example.com:0              LISTENING
  TCP    0.0.0.0:1539           server1.example.com:0              LISTENING
  TCP    0.0.0.0:1541           server1.example.com:0              LISTENING
  TCP    0.0.0.0:1543           server1.example.com:0              LISTENING
  TCP    0.0.0.0:1556           server1.example.com:0              LISTENING
  TCP    0.0.0.0:2869           server1.example.com:0              LISTENING
  TCP    0.0.0.0:3306           server1.example.com:0              LISTENING
  TCP    0.0.0.0:5357           server1.example.com:0              LISTENING
  TCP    0.0.0.0:47497          server1.example.com:0              LISTENING
  TCP    127.0.0.1:1583         server1.example.com:0              LISTENING
  TCP    127.0.0.1:10000        server1.example.com:0              LISTENING
  TCP    127.0.0.1:27275        server1.example.com:0              LISTENING
  TCP    127.0.0.1:64122        server1.example.com:0              LISTENING
  TCP    192.168.1.102:139      server1.example.com:0              LISTENING
  TCP    192.168.13.1:139       server1.example.com:0              LISTENING
  TCP    192.168.195.1:139      server1.example.com:0              LISTENING
  TCP    [::]:135               server1.example.com:0              LISTENING
  TCP    [::]:445               server1.example.com:0              LISTENING
  TCP    [::]:1536              server1.example.com:0              LISTENING
  TCP    [::]:1537              server1.example.com:0              LISTENING
  TCP    [::]:1538              server1.example.com:0              LISTENING
  TCP    [::]:1539              server1.example.com:0              LISTENING
  TCP    [::]:1541              server1.example.com:0              LISTENING
  TCP    [::]:1543              server1.example.com:0              LISTENING
  TCP    [::]:1556              server1.example.com:0              LISTENING
  TCP    [::]:2869              server1.example.com:0              LISTENING
  TCP    [::]:3306              server1.example.com:0              LISTENING
  TCP    [::]:5357              server1.example.com:0              LISTENING
  TCP    [::]:47497             server1.example.com:0              LISTENING
  TCP    [::1]:1540             server1.example.com:0              LISTENING
  TCP    [::1]:27275            server1.example.com:0              LISTENING
  UDP    0.0.0.0:68             *:*
  UDP    0.0.0.0:123            *:*
  UDP    0.0.0.0:500            *:*
  UDP    0.0.0.0:1900           *:*
  UDP    0.0.0.0:3702           *:*
  UDP    0.0.0.0:3702           *:*
  UDP    0.0.0.0:4500           *:*
  UDP    0.0.0.0:5353           *:*
  UDP    0.0.0.0:5355           *:*
  UDP    0.0.0.0:6771           *:*
  UDP    0.0.0.0:47497          *:*
  UDP    0.0.0.0:56723          *:*
  UDP    127.0.0.1:1900         *:*
  UDP    127.0.0.1:49335        *:*
  UDP    127.0.0.1:59989        *:*
  UDP    192.168.1.102:137      *:*
  UDP    192.168.1.102:138      *:*
  UDP    192.168.1.102:1900     *:*
  UDP    192.168.1.102:2177     *:*
  UDP    192.168.1.102:49332    *:*
  UDP    192.168.1.102:59986    *:*
  UDP    192.168.13.1:137       *:*
  UDP    192.168.13.1:138       *:*
  UDP    192.168.13.1:1900      *:*
  UDP    192.168.13.1:2177      *:*
  UDP    192.168.13.1:49333     *:*
  UDP    192.168.13.1:59987     *:*
  UDP    192.168.195.1:137      *:*
  UDP    192.168.195.1:138      *:*
  UDP    192.168.195.1:1900     *:*
  UDP    192.168.195.1:2177     *:*
  UDP    192.168.195.1:49334    *:*
  UDP    192.168.195.1:59988    *:*
  UDP    [::]:123               *:*
  UDP    [::]:500               *:*
  UDP    [::]:3702              *:*
  UDP    [::]:3702              *:*
  UDP    [::]:4500              *:*
  UDP    [::]:5353              *:*
  UDP    [::]:5355              *:*
  UDP    [::]:47497             *:*
  UDP    [::]:56724             *:*
  UDP    [::1]:1900             *:*
  UDP    [::1]:49331            *:*
Önce sunucu kodunu inceleyelim:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/utsname.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>

#define PORTNUM 2016

int compare_ints(const void * elem1, const void * elem2){
 int f = *((int*)elem1);
 int s = *((int*)elem2);
 if (f > s) return  1;
 if (f < s) return -1;
 return 0;
}

int find(int element,int *array,int length){
 int i;
 for (i=0;i<length;++i){
  if (element == array[i])
   return i;
 }
 return -1;
}

void service(int connection){
 static int buffer_size,num,i;
 char *buffer= malloc(18*sizeof(char));
 read(connection,&num,sizeof(num),0);
 for (i=0;i<num;++i){
  buffer_size= populateLottery(buffer);
  write( connection, buffer, buffer_size,0);
 }
    close(connection); 
 free(buffer);
}

int populateLottery(char *buffer){
  int i,k=0,candidate,numbers[6]={50,50,50,50,50,50};
  numbers[0]= rand() % 49 + 1;
  for (i=1;i<6;++i){
     do {
       candidate= rand() % 49 + 1;
     } while(find(candidate,numbers,6)>=0);
     numbers[i]= candidate;
  }
  qsort(numbers,6,sizeof(int),compare_ints);
  for (i=0;i<6;++i){
     if (i<5)
        k += sprintf(buffer+k,"%d,",numbers[i]);
     else
        k += sprintf(buffer+k,"%d\n",numbers[i]);
  }
  return k;
}

int main() {

  struct utsname name;
  struct sockaddr_in socketname, client;
  int sd, ns, clientlen = sizeof(client);
  struct hostent *host;
  time_t today;

  /* determine server system name and internet address */
  if (uname(&name) == -1) {
    perror("uname");
    exit(1);
  }

  if ((host = gethostbyname(name.nodename)) == NULL) {
    perror("gethostbyname");
    exit(1);
  }

  /* fill in socket address structure */
  memset((char *) &socketname, '\0', sizeof(socketname));
  socketname.sin_family = AF_INET;
  socketname.sin_port = PORTNUM;
  memcpy( (char *) &socketname.sin_addr, host->h_addr, host->h_length);

  /* open socket */
  if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket");
    exit(1);
  }

  /* bind socket to a name */
  if (bind(sd, (struct sockaddr *) & socketname, sizeof(socketname))) {
    perror("bind");
    exit(1);
  }

  /* prepare to receive multiple connect requests */
  if (listen(sd, 128)) {
    perror("listen");
    exit(1);
  }
 
  while (1) {
    if ((ns = accept(sd, (struct sockaddr *)&client, &clientlen)) == -1) {
      perror("accept");
      exit(1);
    }
    service(ns);
  }
}
İstemci kodunu aşağıda izleyebilirsiniz:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/utsname.h>
#include <netdb.h>
#include <netinet/in.h> 
#include <errno.h>
#include <time.h>
#include <fcntl.h>

#define PORTNUM 2016

int main(int argc,char *argv[]) {

  int sd,i,sz,num;
  char *buffer= (char *) malloc(18*sizeof(char));
  
  struct sockaddr_in server;
  struct hostent *host;
  time_t srvrtime;
  struct utsname name;

  /* create the socket for talking to server*/
  if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 
    perror("socket"); 
    exit(1);
  }

  /* get server internet address and put into addr
   * structure fill in the socket address structure 
   * and connect to server
   */
  memset((char *) &server, '\0', sizeof(server));
  server.sin_family = AF_INET;
  server.sin_port = PORTNUM;

  /* Server is local system.  Get its name. */
  if (uname(&name) == -1) {
    perror("uname");
    exit (1);
  }

  if ((host = gethostbyname(name.nodename)) == NULL) { 
    perror("gethostbyname"); 
    exit(1); 
  }
  memcpy((char *)&server.sin_addr,host->h_addr,host->h_length);

  /* connect to server */
  if( connect(sd, (struct sockaddr *)&server,
     sizeof(server))) { 
    perror("connect"); 
    exit(1);
  }

  /* Communicate with server */
  num = atoi(argv[1]);
  write(sd,&num,sizeof(num),0);
  for (i=0;i<num;++i){
     do {
 sz= read (sd, buffer, sizeof(18*sizeof(char)),0);
 buffer[sz]='\0';
 printf("%s",buffer);
     } while(sz>0);
  }
  printf("\n");  
  close(sd);
  
  return 0;
}

Java'da Soket Programlama

Java'da soket programlama için iki temel sınıf bulunuyor: Sunucu tarafta ServerSocket sınıfı ve İstemci tarafta Socket sınıfı. Java’da da soket sınıfları Şekil-1’de verilen akışa uygun olarak davranırlar. ServerSocket sınıfının accept() çağrısı bağlantı kurulana kadar uygulamanın bloke olmasına neden olur, bağlantı kurulduğunda ise accept() çağrısı Socket nesnesi ile döner. İletişim bu nesnenin java.io paketinde yer alan giriş/çıkış sınıfları olan Reader, Writer, InputStream, OutputStream kullanılarak gerçekleştirilir.
package com.example;

import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class Server1 { private static final int PORT= 2016; public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(PORT); System.out.println("Server is running at port " + PORT); while (true) { try ( Socket socket = serverSocket.accept(); Writer writer = new OutputStreamWriter(socket.getOutputStream()); ) { final List<Integer> numbers = new Random().ints(1, 50) .distinct() .limit(6) .sorted() .boxed() .collect(Collectors.toList()); writer.write(numbers.toString()); } } } }
İstemcinin herhangi bir sorumluluğu olmadı için istemci taraftaki kod daha da basittir. Kodun basit olmasını biraz Java 7 ile birlikte gelen AutoCloseable kaynakların otomatik olarak finally bloğuna ihtiyaç duymadan otomatik olarak kod yazmadan kapatılmasını sağlayan yeniliğe borçludur.

package com.example;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class Client1 {

    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket("127.0.0.1", 2016)) {
            BufferedReader reader = new BufferedReader(
                      new InputStreamReader(socket.getInputStream())
            );
            System.out.println("Numbers: " + reader.readLine());
        }
    }
}
Ancak bu sunucunun bir problemi var: Belirli bir anda sadece tek bir istemciye cevap verebilir. Şimdi sunucuyu bir anda birden fazla istemciye hizmet verebilir hale getirelim. Bunun için sunucu tarafta her bir bağlantı için bu bağlantıdan gelen istekleri karşılayacak bir ipliğe soket atanır. Bu durumda uygulamanın mimarisi Şekil-2’deki yapıya sahip olacaktır. Burada iplikleri kontrol edebilmek için iplik havuzu kullanmak uygun olur. Bunu Java’daki Executors yardımcı sınıfını kullanarak kılaylıkla gerçekleştirebiliriz. Sabit sayılı iplik havuzu (Executors.newFixedThreadPool()) ya da saklamalı iplik havuzu (Executors.newCachedThreadPool()) kullanılabilir. Sabit iplik havuzunda, havuzdaki iplik sayısı sabittir. Örneğin havuzdaki iplik sayısı 50 olsun. Bu durumda aynı anda 50 tane bağlantıdan gelen istekler karşılanabilir. 50’den sonraki istekler ise iplik havuzunun kuyruğunda bekleyecektir. Saklamalı iplik havuzunda ise her gelen istek için havuzda boşta iplik yoksa yeni bir tane iplik yaratılır ve isteği yaratılan iplik karşılar. İplik işi bitince havuza geri döner, belirli bir süre (1 dakika) yeni bir iş gelmezse sonlanır. Havuzdaki iplik sayısı değişkendir. Daha çok parlama türünde (burst mode) çalışma şekline uygundur.
Şekil-2 Çok iplikli sunucu mimarisi
Şimdi sunucuyu bir anda birden fazla istemciye hizmet verebilir hale getirelim:
package com.example;

import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class Server1MT {
    private static final int PORT= 2016;
    static AtomicLong served= new AtomicLong();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(PORT);
        System.out.println("Server is running at port " + PORT);
        ExecutorService es= Executors.newCachedThreadPool();
        ScheduledExecutorService ses= Executors.newSingleThreadScheduledExecutor();
        ses.scheduleAtFixedRate(() -> System.out.println(served), 0, 10,TimeUnit.SECONDS);
        while (true) {
            es.submit(new Service(serverSocket.accept()));
        }
    }

}

class Service implements Runnable {
    private final Socket socket;

    public Service(Socket socket) {
        this.socket = socket;
    }
    
    @Override
    public void run() {
            try (
                Socket socket=this.socket;
                Writer writer= new OutputStreamWriter(socket.getOutputStream()); 
            ){
                List<Integer> numbers = new Random()
                          .ints(1, 50)
                          .distinct()
                          .limit(6)
                          .sorted()
                          .boxed()
                          .collect(Collectors.toList());
                writer.write(numbers.toString());
                Server1MT.served.incrementAndGet();
            } catch(Exception e){
            } 
    }
}
İplik havuzu kullanarak problemi çözmüş gibiyiz. CachedThreadPool yerine FixedThreadPool da kullanılabilir. Ama hala bir problemimiz var. Bilgisayar ağının hızı işlemcinin hızı ile karşılaştırıldığında yavaştır. Biz her ipliğe bir istemciyi atayarak hizmet veriyoruz. Her istemci için bir iplik kullanılıyor. Aralarında bire bir eşleme kurduk. Halbuki bir iplik aynı anda birden fazla istemciye hizmet verebilir. Bunun için NIO ile gelen Channel, Tıkamasız Soket (Non-blocking Socket) ve Selector yapılarını kullanarak problemin çözümünü yeniden kodlayacağız. Kodlanacak çözümün mimarisi Şekil-.3’de verilmiştir.
Şekil-3 Tek iplikli, tıkamasız soketlerin kullanıldığı sunucu mimarisi
package com.example;

import java.net.InetSocketAddress;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class Server2 {

    private static final int PORT = 2016;
    static AtomicLong served = new AtomicLong();

    public static void main(String[] args) throws Exception {
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        ses.scheduleAtFixedRate(() -> System.err.println(served), 0, 5, TimeUnit.SECONDS);

        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server is running at port " + PORT);
        while (true) {
            selector.select();
            for (Iterator<SelectionKey> it = selector.selectedKeys()
                                                     .iterator(); it.hasNext();) {
                SelectionKey sk = it.next();
                SelectableChannel sc = sk.channel();
                if (sc instanceof ServerSocketChannel) {
                    SocketChannel clientSocketChannel =
                         ((ServerSocketChannel) sk.channel()).accept();
                    if (clientSocketChannel != null) {
                        clientSocketChannel.configureBlocking(false);
                        clientSocketChannel.register(selector, SelectionKey.OP_WRITE);
                    }
                } else if (sc instanceof SocketChannel) {
                    SocketChannel clientSocketChannel = (SocketChannel) sk.channel();
                    final List<Integer> numbers = new Random()
                                                            .ints(1, 50)
                                                            .distinct()
                                                            .limit(6)
                                                            .sorted()
                                                            .boxed()
                                                            .collect(Collectors.toList());
                    clientSocketChannel.write(
                         Charset.defaultCharset().encode(numbers.toString())
                    );
                    Server2.served.incrementAndGet();
                    clientSocketChannel.close();
                }
                it.remove();
            }
        }
    }

}
Kod karışık mı geldi? Java 7’de, daha önce Selector kullanarak elde ettiğimiz çözüm yerine, olay temelli haberleşme yapabilmemizi sağlayan, daha yalın bir programlama modeli sunan AsynchronousServerSocketChannel sınıfı geldi. Aynı problemi bu kez Asenkron Soket kullanarak tekrar çözeceğiz:
package com.example;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class Server3 {

    private static final int PORT= 2016;
    public static void main(String[] args) throws Exception {
        AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(Executors
            .newSingleThreadExecutor());
        AsynchronousServerSocketChannel listener=
                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT));
        listener.accept(null, new CompletionHandler<AsynchronousSocketChannel,Void>(){
            @Override
            public void completed(AsynchronousSocketChannel channel, Void attachment) {
                try {
                    listener.accept( null, this );
                    final List<Integer> numbers = 
                           new Random().ints(1, 50)
                                       .distinct()
                                       .limit(6)
                                       .sorted()
                                       .boxed()
                                       .collect(Collectors.toList());
                    channel.write(
                       Charset.defaultCharset().encode(numbers.toString())
                    );
                    if(channel.isOpen())
                        channel.close();
                } catch (IOException ex) {
                    Logger.getLogger(Server3.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
                
            }
        });
        System.out.println("Server is running at port " + PORT);
        group.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
    }
}
Eğer Java platformunda yüksek başarımlı Soket programlama yapmak istiyorsanız, Java SE 7 ile gelen bu çözümü tercih etmelisiniz. Tüm bunları hala karmaşık buluyorsanız daha üst seviye bir kütüphaneye ya da çatıya geçmeniz gerekir. Bunun için Netty çatısına bakabilirsiniz:
Başka bir yazıda Netty çatısını inceleyeceğiz.

C++'da Soket Programlama

C++11'de ve C++14'de Soket programlama ile ilgili hazır bir çözüm yok. C++17'de ise ağ programlama için bir Soket çözümünün gelmesi planlanıyor. 
Ancak Boost.Asio kütüphanesini kullanarak soket programlama yapabiliriz. Yukarıda Java platformunda kodladığımız sunucu ve istemciyi şimdi Boost.Asio ile kodlayalım:
#include <cstdlib>
#include <iostream>
#include <memory>
#include <random>
#include <algorithm>
#include <utility>
#include <string>
#include <sstream>
#include <boost/asio.hpp>

using namespace std;
using boost::asio::ip::tcp;

namespace std {
    template < typename T > std::string to_string( const T& n ) {
        std::ostringstream stm ;
        stm << n ;
        return stm.str() ;
    }
}

class session : public std::enable_shared_from_this<session> {
public:
  session(tcp::socket socket) : socket_(move(socket)),mt(rd()),dist(1, 49) {
  }

  void start() {
        generateLotteryNumbers(); 
 do_write();
  }

private:
  void generateLotteryNumbers(){
     numbers.empty();
     while (numbers.size()<6){
    int candidate= dist(mt);
    if (find(numbers.begin(),numbers.end(),candidate)==numbers.end())
       numbers.push_back(candidate);
     }
     sort(numbers.begin(),numbers.end());
     _buffer.empty();
     for (auto x: numbers){
    _buffer.append(std::to_string(x)).append(",");
     }
     _buffer.erase(_buffer.begin()+_buffer.size()-1);
     _buffer.append("\n");
  }
  
  void do_write(){
    auto self(shared_from_this());
    boost::asio::async_write(socket_, boost::asio::buffer(_buffer.c_str(), _buffer.size()),
        [this, self](boost::system::error_code ec, std::size_t){
        });
  }

  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
  string _buffer;
  random_device rd;
  mt19937 mt;
  uniform_int_distribution<int> dist;
  vector<int> numbers;
};

class server {
public:
  server(boost::asio::io_service& io_service, short port)
    : acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
      socket_(io_service) {
    do_accept();
  }

private:
  void do_accept() {
    acceptor_.async_accept(socket_,
        [this](boost::system::error_code ec)
        {
          if (!ec) {
            std::make_shared<session>(std::move(socket_))->start();
          }

          do_accept();
        });
  }

  tcp::acceptor acceptor_;
  tcp::socket socket_;
};

int main(int argc, char* argv[]){
    if (argc != 2)    {
      std::cerr << "Usage: lottery_server <port>\n";
      return 1;
    }

    try  {
 boost::asio::io_service io_service;
 server s(io_service, std::atoi(argv[1]));
 io_service.run();
    }
    catch (std::exception& e){
  std::cerr << "Exception: " << e.what() << endl;
    }

    return 0;
}
Şimdi istemciyi Boost.Asio kullanarak kodlayalım:

#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main(int argc, char* argv[]) {
   if (argc != 2) {
        std::cerr << "Usage: lottert_client <host>" << std::endl;
        return 1;
   }
   try {
 boost::asio::io_service io_service;
 tcp::resolver resolver(io_service);
 tcp::resolver::query query(argv[1]);
 tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
 tcp::socket socket(io_service);
 boost::asio::connect(socket, endpoint_iterator);
 while (true){
    boost::array<char, 128> buf;
    boost::system::error_code error;
    size_t len = socket.read_some(boost::asio::buffer(buf), error);
    if (error == boost::asio::error::eof)
  break; // Connection closed cleanly by peer.
    else if (error)
  throw boost::system::system_error(error); // Some other errors.
    cout.write(buf.data(), len);
 }
   }
   catch (std::exception& e) {
 cerr << e.what() << endl;
   }
   return 0;
}

No comments:

Post a Comment