İşletim Sistemleri

advertisement
İşletim Sistemleri
İşletim Sistemleri
Dr. Binnur Kurt
[email protected]
Omega Eğitim ve Danışmanlık
http://www.omegaegitim.com
Bölüm 11
Soket Haberleşme
1|Sayfa
İşletim Sistemleri
İÇİNDEKİLER
1. İşletim Sistemi
2. Kabuk
3. Prosesler
4. İplikler
5. İplikler Arası Eş Zamanlama
6. Prosesler Arası İletişim
7. İş Sıralama
8. Prosesler Arası Eş zamanlama
9. Bellek Yönetimi
10. Dosya Sistemi
11. Soket Haberleşme
Bölüm 11
Soket Haberleşme
2|Sayfa
İşletim Sistemleri
BÖLÜM 11
Soket Haberleşme
Bölümün Amacı
Bölüm sonunda aşağıdaki konular öğrenilmiş olacaktır:
► İstemci-Sunucu Modeli
► Soket Programlama
o C ile Unix Soket Programlama
o Java ile Soket Programlama
o C++’da ASIO ile Soket Programlama
Bölüm 11
Soket Haberleşme
3|Sayfa
İşletim Sistemleri
11.1 Giriş
İ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.
11.2 İ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.
Şimdi farklı platformlarda ve farklı programlama dilleri kullanarak soket
programlamanın detaylarını ve yukarıda verilen istemci sunucu modelinin nasıl
gerçeklendiğini çalışacağız. Önce Unix işletim sisteminde C programlama dili
kullanarak soket programlama yapacağız. Daha sonra Java'daki ve C++'daki çözümleri
inceleyeceğiz.
11.3 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ı
Bölüm 11
Soket Haberleşme
4|Sayfa
İşletim Sistemleri
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-11.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
Bölüm 11
Soket Haberleşme
5|Sayfa
İşletim Sistemleri
farklı ve sıralı altı tane sayı oluşturur ve istemciye gönderir. Bunu
önce AF_UNIX soket kullanarak kodlayalım:
Kod Listesi 11.1: AF_UNIX soket Sunucu kaynak kodu
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<sys/types.h>
<sys/socket.h>
<sys/un.h>
<time.h>
<stdlib.h>
<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);
Bölüm 11
Soket Haberleşme
6|Sayfa
İşletim Sistemleri
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("main:bind");
exit(1);
}
/* Prepare to receive multiple connect requests */
if( listen(sd, 128) == -1 ) {
perror("main:listen");
exit(1);
}
/* Infinite loop to accept client requests */
while(1) {
comm = accept(sd, (struct sockaddr*)&client, &clientsize);
if ( comm == -1) {
perror("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ısı 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
Bölüm 11
Soket Haberleşme
7|Sayfa
İşletim Sistemleri
Kod Listesi 11.2: AF_UNIX soket İstemci kaynak kodu
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<sys/types.h>
<sys/socket.h>
<sys/un.h>
<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("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("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-11.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
Bölüm 11
Soket Haberleşme
8|Sayfa
İşletim Sistemleri
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
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
TCP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
Local Address
0.0.0.0:135
0.0.0.0:445
0.0.0.0:1536
0.0.0.0:1537
0.0.0.0:1538
0.0.0.0:1539
0.0.0.0:1541
0.0.0.0:1543
0.0.0.0:1556
0.0.0.0:2869
0.0.0.0:3306
0.0.0.0:5357
0.0.0.0:47497
127.0.0.1:1583
127.0.0.1:10000
127.0.0.1:27275
127.0.0.1:64122
192.168.1.102:139
192.168.13.1:139
192.168.195.1:139
[::]:135
[::]:445
[::]:1536
[::]:1537
[::]:1538
[::]:1539
[::]:1541
[::]:1543
[::]:1556
[::]:2869
[::]:3306
[::]:5357
[::]:47497
[::1]:1540
[::1]:27275
0.0.0.0:68
0.0.0.0:123
0.0.0.0:500
0.0.0.0:1900
0.0.0.0:3702
0.0.0.0:3702
0.0.0.0:4500
0.0.0.0:5353
0.0.0.0:5355
0.0.0.0:6771
0.0.0.0:47497
0.0.0.0:56723
127.0.0.1:1900
127.0.0.1:49335
127.0.0.1:59989
192.168.1.102:137
192.168.1.102:138
192.168.1.102:1900
192.168.1.102:2177
192.168.1.102:49332
192.168.1.102:59986
192.168.13.1:137
192.168.13.1:138
192.168.13.1:1900
192.168.13.1:2177
192.168.13.1:49333
192.168.13.1:59987
192.168.195.1:137
192.168.195.1:138
192.168.195.1:1900
192.168.195.1:2177
192.168.195.1:49334
Bölüm 11
Soket Haberleşme
Foreign Address
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
server1.example.com:0
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
State
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
9|Sayfa
İşletim Sistemleri
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
UDP
192.168.195.1:59988
[::]:123
[::]:500
[::]:3702
[::]:3702
[::]:4500
[::]:5353
[::]:5355
[::]:47497
[::]:56724
[::1]:1900
[::1]:49331
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
*:*
Önce sunucu kodunu inceleyelim:
Kod Listesi 11.3: AF_INET soket Sunucu kaynak kodu
#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);
}
Bölüm 11
Soket Haberleşme
10 | S a y f a
İşletim Sistemleri
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) {
Bölüm 11
Soket Haberleşme
11 | S a y f a
İşletim Sistemleri
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:
Kod Listesi 11.4: AF_UNIX soket İstemci kodu
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<sys/types.h>
<sys/socket.h>
<sys/utsname.h>
<netdb.h>
<netinet/in.h>
<errno.h>
<time.h>
<fcntl.h>
#define PORTNUM 2016
int main(int argc,char *argv[]) {
int sd,i,sz,num;
char *buffer= (char *) malloc(18*sizeof(char));
struct
struct
time_t
struct
sockaddr_in server;
hostent *host;
srvrtime;
utsname name;
Bölüm 11
Soket Haberleşme
12 | S a y f a
İşletim Sistemleri
/* 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.
if (uname(&name) == -1) {
perror("uname");
exit (1);
}
Get its name. */
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;
}
11.4 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-11.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ı
Bölüm 11
Soket Haberleşme
13 | S a y f a
İşletim Sistemleri
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.
Kod Listesi 11.5: Java’da soket Sunucu kodu
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 ([email protected])
*/
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.
Bölüm 11
Soket Haberleşme
14 | S a y f a
İşletim Sistemleri
Kod Listesi 11.6: Java’da soket İstemci kodu
package com.example;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
/**
*
* @author Binnur Kurt ([email protected])
*/
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 Şekil11.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-11.2 Çok iplikli sunucu mimarisi
Bölüm 11
Soket Haberleşme
15 | S a y f a
İşletim Sistemleri
Kod Listesi 11.7: Çok İplikli İstemci çözümü
package com.example;
import
import
import
import
import
import
import
import
import
import
import
import
java.io.OutputStreamWriter;
java.io.Writer;
java.net.ServerSocket;
java.net.Socket;
java.util.List;
java.util.Random;
java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
java.util.concurrent.ScheduledExecutorService;
java.util.concurrent.TimeUnit;
java.util.concurrent.atomic.AtomicLong;
java.util.stream.Collectors;
/**
*
* @author Binnur Kurt ([email protected])
*/
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)
Bölüm 11
Soket Haberleşme
16 | S a y f a
İşletim Sistemleri
.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. Ancak bir iplik aynı anda birden fazla istemciye hizmet verebilir.
Bunun için NIO ile gelen Channel, Tıkamasız (Non-blocking) Soket ve Selector
yapılarını kullanarak problemin çözümünü yeniden kodlayacağız. Kodlanacak
çözümün mimarisi Şekil-11.3’de verilmiştir.
Şekil-11.3 Tek iplikli, tıkamasız soketlerin kullanıldığı sunucu mimarisi
Bölüm 11
Soket Haberleşme
17 | S a y f a
İşletim Sistemleri
Kod Listesi 11.8: Tek İplikli Soket kullanan İstemci çözümü
package com.example;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.net.InetSocketAddress;
java.nio.channels.SelectableChannel;
java.nio.channels.SelectionKey;
java.nio.channels.Selector;
java.nio.channels.ServerSocketChannel;
java.nio.channels.SocketChannel;
java.nio.charset.Charset;
java.util.Iterator;
java.util.List;
java.util.Random;
java.util.concurrent.Executors;
java.util.concurrent.ScheduledExecutorService;
java.util.concurrent.TimeUnit;
java.util.concurrent.atomic.AtomicLong;
java.util.stream.Collectors;
/**
*
* @author Binnur Kurt ([email protected])
*/
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,
Bölüm 11
Soket Haberleşme
18 | S a y f a
İşletim Sistemleri
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();
}
}
}
}
Java 7’de daha önce Selector kullanarak elde ettiğimiz çözüm yerine, olay temelli
haberleşme yapabilmemizi sağlayan daha basit programlama modeli sunan
AsynchronousServerSocketChannel sınıfı geldi. Aynı problemi bu kez
Asenkron Soket kullanarak tekrar çözeceğiz (Kod Listesi-11.9).
Bölüm 11
Soket Haberleşme
19 | S a y f a
İşletim Sistemleri
Kod Listesi 11.9: Olay tabanlı Soket haberleşme çözümü:
AsynchronousServerSocketChannel
package com.example;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.io.IOException;
java.net.InetSocketAddress;
java.nio.channels.AsynchronousChannelGroup;
java.nio.channels.AsynchronousServerSocketChannel;
java.nio.channels.AsynchronousSocketChannel;
java.nio.channels.CompletionHandler;
java.nio.charset.Charset;
java.util.List;
java.util.Random;
java.util.concurrent.Executors;
java.util.concurrent.TimeUnit;
java.util.logging.Level;
java.util.logging.Logger;
java.util.stream.Collectors;
/**
*
* @author Binnur Kurt ([email protected])
*/
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())
Bölüm 11
Soket Haberleşme
20 | S a y f a
İşletim Sistemleri
.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.
11.5 C++'da Soket Programlama
C++11'de ve C++14'de soket programlama ile ilgili hazır bir çözüm yok. 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:
Kod Listesi 11.10 C++’da Boost.Asio kütüphanesi ile yazılan sunucu
#include
#include
#include
#include
#include
#include
#include
#include
#include
<cstdlib>
<iostream>
<memory>
<random>
<algorithm>
<utility>
<string>
<sstream>
<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();
}
Bölüm 11
Soket Haberleşme
21 | S a y f a
İşletim Sistemleri
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_;
Bölüm 11
Soket Haberleşme
22 | S a y f a
İşletim Sistemleri
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:
Kod Listesi 11.11 C++’da Boost.Asio kütüphanesi ile yazılan istemci
#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);
cout.write(buf.data(), len);
}
}
catch (std::exception& e) {
cerr << e.what() << endl;
}
return 0;
}
Bölüm 11
Soket Haberleşme
23 | S a y f a
Download