Java Dagitik Programlama - KTÜ Bilgisayar Mühendisliği

Transkript

Java Dagitik Programlama - KTÜ Bilgisayar Mühendisliği
KARADENİZ TEKNİK ÜNİVERSİTESİ
Bilgisayar Mühendisliği Bölümü
Bilgisayar Sistemleri Laboratuarı
JAVA ile DAĞITIK PROGRAMLAMA
1. Giriş
JAVA, ağdaki programların haberleşmesi için TCP ve UDP olmak üzere iki farklı socket türü
kullanır. Her iki socket türü de haberleşmede Client-Server ilişkisini kullanır. Bu deneyde TCP socket
kullanılarak Server ve Client bilgisayarlar için yazılacak socket programları ile JAVA ile dağıtık
programlama anlatılacaktır. Örnek programlar “An Introduction to Network Programming with Java” adlı
kitaptan alınmıştır.
2. Server Socket Setlemeleri
Burada Client bilgisayar ile haberleşme için gerekli Server Socket setlemeleri anlatılacaktır:
2.1. ServerSocket Nesnesi Türetme
servSock isimli ServerSocket nesnesi 1024-65535 arası yani özel amaçlar için ayrılmış numaraların
dışında bir port numarası seçerek aşağıdaki gibi oluşturulur:
ServerSocket servSock = new ServerSocket(1234);
2.2. Server’ı Bekleme Konumuna Getirme
servSock, ServerSocket sınıfına ait accept methodunu kullanarak herhangi bir Client’ın
bağlanmasını bekler. Bağlantı kurulduğunda aşağıdaki gibi link isimli bir Socket nesnesi türetilir:
Socket link = servSock.accept();
2.3. Gönderilecek/Alınacak Veriler için Input/Output Stream Setleme
Client bilgisayardan gelecek mesajları almak ve ona mesaj göndermek üzere Socket sınıfına ait
getInputStream ve getOutputStream methodları kullanılarak input ve output isimli stream nesneleri
aşağıdaki gibi türetilir:
Scanner input = new Scanner(link.getInputStream());
PrintWriter output = new PrintWriter(link.getOutputStream(),true);
2.4. Veri Gönderme ve Alma
Veri gönderme ve alma işlemleri için sırasıyla println ve nextLine methodları kullanılır :
output.println(“.....”);
String message = input.nextLine();
2.5. Bağlantıyı Sonlandırma
Bağlantı Socket sınıfının close methodu ile sonlandırılır:
link.close();
3. Client Socket Setlemeleri
Server ile bağlantı kurma dışındaki Client Socket setlemeleri yukarıda Server için anlatılanlarla
ayınıdır.
Server ile bağlantı kurmak için link isimli Socket nesnesi Server’ın ismi (veya IP adresi) ve port
numarası parametreleri yardımıyla aşağıdaki gibi türetilir:
Socket link = new Socket( “ServerName” , 1234);
4. Client-Server Programı
Aşağıda örnek bir Client-Server programı ve ekran çıktısı verilmiştir. Öncelikle TCPServer.java
programı koşularak Server başlatılır. Sonra TCPClient.java koşularak Server ile bağlantı kurulur. Bu
örnekte Client tarafından gönderilen mesajlar Server tarafından sayılır ve “BYE BYE” mesajı geldiğinde
bağlantı sonlandırılırken toplam mesaj sayısı yazılır.
TCPServer.java
import java.io.*;
import java.net.*;
import java.util.*;
public class TCPServer
{
private static ServerSocket servSock;
private static final int PORT = 1234;
public static void main(String[] args)
{
try
{
servSock = new ServerSocket(PORT);
System.out.println("Waiting for connection...\n");
}
catch(IOException ioEx)
{
System.out.println("Unable to attach to port!");
System.exit(1);
}
do
{
handleClient();
}
while (true);
}
2
private static void handleClient()
{
Socket link = null;
String message = null;
try
{
link = servSock.accept();
Scanner input = new Scanner(link.getInputStream());
PrintWriter output = new PrintWriter(link.getOutputStream(),true);
int numMessages = 1;
message = input.nextLine();
while (!message.equals("BYE BYE"))
{
System.out.println("Received Message : " + message);
numMessages++;
message = input.nextLine();
}
output.println(numMessages + " messages received.");
}
catch(IOException ioEx){
ioEx.printStackTrace();
}
Finally
{
try{
System.out.println("Because of Received Message : " + message);
System.out.println(" * Closing connection... *");
link.close();
}
catch(IOException ioEx){
System.out.println("Unable to disconnect!");
System.exit(1);
}
}
}
}
TCPClient.java
import java.io.*;
import java.net.*;
import java.util.*;
public class TCPClient
{
private static InetAddress host;
private static final int PORT = 1234;
public static void main(String[] args)
{
try{
//host = InetAddress.getByName("ServerName");
host = InetAddress.getLocalHost();
System.out.println("Connected to Server");
}
catch(UnknownHostException uhEx)
{
System.out.println("Host ID not found!");
System.exit(1);
}
accessServer();
}
3
private static void accessServer()
{
Socket link = null;
try
{
link = new Socket(host,PORT);
Scanner input = new Scanner(link.getInputStream());
PrintWriter output = new PrintWriter(link.getOutputStream(),true);
Scanner userEntry = new Scanner(System.in);
String message, response;
do
{
System.out.print("Enter message : ");
message = userEntry.nextLine();
output.println(message);
}
while (!message.equals("BYE BYE"));
response = input.nextLine();
System.out.println("\nSERVER > "+response);
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
finally
{
try
{
System.out.println("* Closing connection... *");
link.close();
}
catch(IOException ioEx)
{
System.out.println("Unable to disconnect!");
System.exit(1);
}
}
}
}
SERVER
CLIENT
[0]Waiting for connection...
[1]Connected to Server
[3]Received Message : MERHABA
[2]Enter message : MERHABA
[5]Received Message : NASILSIN
[4]Enter message : NASILSIN
[7]Because of Received Message : BYE BYE
[6]Enter message : BYE BYE
* Closing connection... *
[8]SERVER > 3 messages received.
* Closing connection... *
4
5. Multithreading
Java ile dağıtık programlamada Server bilgisayara 1 ‘den fazla Client bilgisayarın bağlantı kurması
durumunda her bir Client bilgisayar için bir thread başlatılır. Bunun için öncelikle Thread sınıfı
miraslanarak bir thread nesnesi türetilir. start methodu ile bu threadin yapacağı işin kodunu barındıran
run methodu çağrılır. Çok sayıda thread aynı anda koşarken birbirlerine zaman ayırmaları için sleep
methodu kullanılır ve parametre olarak aldığı milisaniye cinsinden zaman kadar askıda durur. Aşağıdaki
örnek programda biri 5 kere “Hello”; diğeri de 1-5 arası sayıları ekrana yazan iki threadi çağıran
ThreadHelloCount adlı java programı ve çıktısı verilmiştir:
ThreadHelloCount.java
import java.io.*;
public class ThreadHelloCount
{
public static void main(String[] args)
{
HelloThread hello = new HelloThread();
CountThread count = new CountThread();
hello.start();
count.start();
}
}
class HelloThread extends Thread
{
public void run()
{
for (int i=0; i<5; i++)
{
try
{
System.out.println("Hello!");
sleep((int)(Math.random()*1000));
}
catch (InterruptedException interruptEx)
System.out.println(interruptEx);
}
}
}
class CountThread extends Thread
{
public void run()
{
for (int i=0; i<5; i++)
{
try
{
System.out.println(i);
sleep((int)(Math.random()*1000));
}
catch (InterruptedException interruptEx)
System.out.println(interruptEx);
}
}
}
5
Hello!
0
1
Hello!
2
3
Hello!
Hello!
4
Hello!
Yukarıdaki program çıktısına dikkat edilirse threadler farklı sıklıklarla çağrılmıştır. Bunun nedeni
sleep methodundaki Math.random()*1000 ifadesidir. Böylece random fanksiyonu ile her bir thread 0 ile
1 saniye arasında değişen farklı sürelerde askıda durmaktadır. Dolayısıyla ThreadHelloCount adlı program
her koşuşunda farklı bir çıktı üretecektir.
Aşağıda, daha önce anlatılan TCPServer ve TCPClient programlarının multithreading versiyonu
verilmiştir. MutiServer.java programı, Thread sınıfını miraslayan ClientHandler sınıfı herbir Client
bağlantısında bir thread başlatmaktadır. MultiClient.java programı TCPClient ile hemen hemen aynıdır.
MultiServer.java
import java.io.*;
import java.net.*;
import java.util.*;
public class MultiServer
{
private static ServerSocket serverSocket;
private static final int PORT = 1234;
public static void main(String[] args) throws IOException
{
try{
serverSocket = new ServerSocket(PORT);
}
catch (IOException ioEx){
System.out.println("\nUnable to set up port!");
System.exit(1);
}
do
{
Socket client = serverSocket.accept();
System.out.println("\nNew client accepted.\n");
ClientHandler handler = new ClientHandler(client);
handler.start();
//As usual, method calls run.
}
while (true);
}
}
class ClientHandler extends Thread
{
private Socket client;
private Scanner input;
private PrintWriter output;
public ClientHandler(Socket socket)
{
//Set up reference to associated socket...
client = socket;
try{
input = new Scanner(client.getInputStream());
output = new PrintWriter(
client.getOutputStream(),true);
}
catch(IOException ioEx){
ioEx.printStackTrace();
}
}
6
public void run()
{
String received;
do
{
//Accept message from client on the socket's input stream...
received = input.nextLine();
//Echo message back to client on the socket's output stream...
output.println("ECHO: " + received);
}
while (!received.equals("QUIT")); //Repeat above until 'QUIT' sent by client...
try
{
if (client!=null)
{
System.out.println("Closing down connection...");
client.close();
}
}
catch(IOException ioEx)
{
System.out.println("Unable to disconnect!");
}
}
}
MultiClient.java
import java.io.*;
import java.net.*;
import java.util.*;
public class MultiClient
{
private static InetAddress host;
private static final int PORT = 1234;
public static void main(String[] args)
{
try
{
//host = InetAddress.getByName("ServerName");
host = InetAddress.getLocalHost();
}
catch(UnknownHostException uhEx)
{
System.out.println("\nHost ID not found!\n");
System.exit(1);
}
sendMessages();
}
7
private static void sendMessages()
{
Socket socket = null;
try
{
socket
= new Socket(host,PORT);
Scanner networkInput
= new Scanner(socket.getInputStream());
PrintWriter networkOutput = new PrintWriter(socket.getOutputStream(),true);
Scanner userEntry
= new Scanner(System.in);
String message, response;
do
{
System.out.print("Enter message ('QUIT' to exit): ");
message = userEntry.nextLine();
networkOutput.println(message);
response = networkInput.nextLine();
System.out.println("\nSERVER> " + response);
}
while (!message.equals("QUIT"));
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
finally
{
try
{
System.out.println("\nClosing connection...");
socket.close();
}
catch(IOException ioEx)
{
System.out.println("Unable to disconnect!");
System.exit(1);
}
}
}
}
6. Synchronized Thread’ler
Farklı threadlerin ortak kullandıkları kaynaklara eşzamanlı erişimleri yanlış sonuçlar üretmeye
neden olabilir. O yüzden ortak kaynaklara erişimi engelleyecek bir mekanizmaya ihtiyaç vardır. Java
synchronized anahtar kelimesi ile bunu gerçekleştirir. Örneğin aşağıdaki methodda ortak kullanılan sum
adlı değişkenin aynı anda farklı threadler tarafından güncellenmemesi için updateSum adlı method
synchronized yapılmıştır.
public synchronized void updateSum(int amount)
{
sum+=amount;
}
Synchronized bir methodu koşan thread’e o anlık bir iş düşmüyorsa wait methodunu çağırarak
synchronized method üzerindeki kilidi kaldırır ve böylece diğer threadlerin de o methodu koşmasına izin
verir. Eğer bir thread işini tamamlamışsa ve wait konumundaki başka bir thread’in çalışmasını sağlamak
istiyorsa notify methodunu kullanır. Wait konumundaki bütün threadlerin çalıştırılması için de notifyall
methodu kullanılır. Bu durumda hangi thread’e öncelik verileceğine JVM karar verir.
8
6.1. Producer-Consumer Problemi
Bilindiği gibi producer-consumer probleminde producerın ürettiği – consumerın tükettiği ortak
kaynak kullanılmaktadır. Burada en önemli problem kaynağa eş zamanlı erişimi engelleyerek tutarlılığı
sağlamaktır. Yukarıda da bahsedildiği gibi producer ve consumerın ortak çağırdıkları methodlar
synchronized yapılarak tutarlı bir kaynak güncellemesi yapılabilecektir.
Producer-consumer uygulaması, kaynak kodlardan “Multi Producer-Consumer” adlı klasörün
içindedir. Producer ve consumerın ortak erişeceği, kaynak (resourse) üretme ve tüketme işini yapan
addOne ve takeOne methodları, Resourse.java programı içindedir. ResourseServer.java programı
öncelikle item isimli bir Resourse nesnesi türetir ve bu nesneyi parametre vererek türettiği Producer
türünden producer isimli thread nesnesinde addOne methodunu sürekli çağırarak kaynak üretmeye
başlar. ResourseServer adından da anlaşılabileceği gibi aynı zamanda bir server programıdır. Kaynak
üreten server programıdır. Kaynak tüketimi de bu servera bağlanan clientlar tarafından yapılacaktır.
Bağlantı kurulduğunda ResourseServer programında handler isimli bir ClientThread threadi başlatılır ve
clientlerın isteklerine cevap verilir. Client bilgisayar “1” karakteri yolladıkça kaynakları tutan item
nesnesinin takeOne methodu çağrılarak kaynak harcanır. “0” karakteri ile de bağlantı sonlandırılır.
Kaynak üretimi belli bir MAX (5) değerle sınırlandırılmıştır. Bu değere ulaşıldığında wait methodu
çağrılarak clientların kaynağı harcaması beklenir. Ayrıca herhangi bir kaynak üretildiğinde de yine
tüketilebilmesi için notifiyall methodu ile clientlara bilgi verilir.
7. Deneye Hazırlık
Bölüm 6.1. ‘de anlatılan Producer-Consumer uygulamasında Client bilgisayarda koşacak program
Multithreading uygulamasındaki MultiClient.java programına çok benzediği için ayrıca anlatılmamıştır.
MultiClient.java programının ismini ConsumerClient.java olarak değiştiriniz ve gerekli değişiklikleri
yaparak Producer-Consumer uygulamasının doğru bir şekilde çalışmasını sağlayınız. Yazdığınız programı
deneye getiriniz.
8. Deneyin Yapılışı
Resource.java programı ekrana addOne methodunda üretilen, takeOne’da da kalan
numResources toplam kaynak sayısını yazmaktadır. Bunun yerine Resourse.java programına (“Deneyin
Yapılışı” adlı klasördeki basit bir Stack uygulamasından da yararlanarak) Stack ekleyip, addOne methodu
çağrıldığında hangi kaynağın üretildiğini “PUSHED ITEM = 55” şeklinde, takeOne çağırıldığında da hangi
kaynağın tüketildiğini “POPED ITEM = 55” şeklinde ekrana yazınız. Burada üretilen kaynak numResources
değişkeninden farklı olarak 0..100 arası random belirlensin. Dolayısıyla stack’e itilen ve stack’ten çekilen
değerler de 0..100 arası olacaktır. Stack’in dolu veya boş olduğu bilgisini de “STACK IS FULL/EMPTY”
şeklinde ekrana yazınız.
Resourse.java programının başında java.util.*; adlı package’i import etmeyi unutmayınız.
ResourseServer.java, ConsumerClient.java ‘ya “YOU POPED = 55” şeklinde stackten çekilen
değeri yollasın.
ConsumerClient.java programından gelen isteklere (“1” veya “0”) bağlı olarak
ResourseServer.java ve ConsumerClient.java programlarının ekran çıktısı aşağıdakine benzer olmalıdır :
9
RESOURSESERVER.JAVA
CONSUMERCLIENT.JAVA
PUSHED ITEM = 55
New client accepted.
POPED ITEM = 55
PUSHED ITEM = 21
POPED ITEM = 21
STACK IS EMPTY
PUSHED ITEM = 12
POPED ITEM = 12
STACK IS EMPTY
PUSHED ITEM = 73
POPED ITEM = 73
PUSHED ITEM = 51
PUSHED ITEM = 6
POPED ITEM = 6
POPED ITEM = 51
STACK IS EMPTY
PUSHED ITEM = 47
POPED ITEM = 47
PUSHED ITEM = 48
PUSHED ITEM = 18
PUSHED ITEM = 17
PUSHED ITEM = 0
PUSHED ITEM = 70
STACK IS FULL
Closing down connection...
Enter message ('0' to exit):
SERVER> YOU POPED = 55
Enter message ('0' to exit):
SERVER> YOU POPED = 21
Enter message ('0' to exit):
SERVER> YOU POPED = 12
Enter message ('0' to exit):
SERVER> YOU POPED = 73
Enter message ('0' to exit):
SERVER> YOU POPED = 6
Enter message ('0' to exit):
SERVER> YOU POPED = 51
Enter message ('0' to exit):
SERVER> YOU POPED = 47
Enter message ('0' to exit):
SERVER> Connection closed...
Closing connection...
Process completed.
1
1
1
1
1
1
1
0
8. Rapor
Raporu programların olduğu klasördeki şablon belgeyi kullanarak yazınız. Rapor grup adına
yazılacaktır. Her öğrencinin ayrı ayrı rapor yazmasına gerek yoktur. Raporun son teslim tarihi sonraki
hafta deney saatidir. Daha geç verilen raporlar değendirilmeyecektir.
9. Değerlendirme Kriterleri
Deney notu aşağıdaki kriterlere göre verilecektir:
DENEYE HAZIRLIK
DENEYİN YAPILIŞI
RAPOR
40 PUAN
40 PUAN
20 PUAN
10

Benzer belgeler