KARADENİZ TEKNİK ÜNİVERSİTESİ BİLGİSAYAR MÜHENDİSLİĞİ BÖLÜMÜ BİLGİSAYAR AĞLARI LABORATUARI Soket Programlama 1. Giriş JAVA dili süreçler arası iletişim için TCP ve UDP olmak üzere iki farklı soket yapısı kullanır. Her iki soket yapısı da haberleşmede İstemci-Sunucu (Client-Server) mimarisini kullanır. Bu deneyde, istemci-sunucu soket setlemeleri ve çoklu kullanım (multithreading) konularından bahsedildikten sonra TCP yapısına göre bir soket uygulaması üzerinde ÜreticiTüketici (Producer-Consumer) probleminin çok iş parçacıklı (thread) çözümü üretilen kaynak yığına (stack) itilerek, tüketilen de yığından çekilerek gerçeklenecektir. 2. Server Soket Setlemeleri Server Soket setleme adımları aşağıda verilmiştir: ServerSocket nesnesi türetme: servSock isimli ServerSocket nesnesi özel amaçlar için ayrılmış numaraların dışında, yani 1024-65535 arası bir port numarası seçerek aşağıdaki gibi oluşturulur: ServerSocket servSock = new ServerSocket(2861); Sunucuyu bekleme konumuna getirme: servSoc, ServerSocket sınıfına ait accept() metodunu kullanarak herhangi bir istemcinin bağlanmasını bekler. Bağlantı kurulduğunda link soket nesnesi şu şekilde türetilir: Socket link = servSock.accept(); Yollanacak ve alınacak veriler için giriş/çıkış (input/output) katar (stream) Setleme: İstemci bilgisayarlardan gelecek mesajları almak ve onlara mesaj göndermek için Socket sınıfına ait getInputStream ve getOutputStream metodları 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); Veri Gönderme ve Alma: Soket ile veri gönderme ve alma işlemleri için sırasıyla println() ve nextLine() metodları kullanılır: output.println(“Gönderilecek karakter dizisi”); String message = input.nextLine(); Soket bağlantısını sonlandırma: Bağlantı Socket nesnesinin close() metodu ile aşağıdaki şekilde sonlandırılır: link.close(); 3. İstemci Soket Setlemeleri Sunucu ile bağlantı kurma dışında istemci soket setlemeleri yukarıda sunucu için anlatılanlarla aynıdır. Sunucu ile bağlantı kurmak için link soket nesnesi sunucunun ismi (veya IP adresi) ve port numarası parametreleri yardımıyla aşağıdaki gibi türetilir: Socket link = new Socket(“SunucuAdı” , 2861); 4. İstemci-Sunucu Uygulaması Kaynak kodlardan Soket klasöründe basit bir istemci-sunucu uygulaması verilmiştir (Tek istemci ve tek sunucu olacak şekilde çalışmaktadır). Uygulama test edilirken öncelikle TCPServer.java programı koşularak sunucu başlatılır. Daha sonra TCPClient.java koşularak istemci çalıştırılır ve sunucu ile bağlantı kurulur. Bu uygulamada istemci tarafından gönderilen mesajlar sunucu tarafından sayılır ve istemciden “BYE BYE” mesajı geldiğinde bağlantı sonlandırılırken toplam mesaj sayısı yazılır. Örnek bir program çıktısı aşağıdaki gibidir: SERVER CLIENT [0] Waiting for connection... [1] Connected to Server [3] Received Message : Merhaba [2] Enter message : Merhaba [5] Received Message : Nasılsın [4] Enter message : Nasılsın [7] Because of Received Message: BYE BYE * Closing connection... * [6] Enter message : BYE BYE [8] SERVER > 3 messages received. * Closing connection... * 5. Çok İstemcili Tek Sunucu Kullanımı Sunucu bilgisayara 1‘den fazla istemci bilgisayarın bağlantı kurması durumunda sunucuda, her bir istemci için yeni bir is parçacığı başlatılmalıdır. Bunun için öncelikle Thread sınıfı miraslanarak bir thread nesnesi türetilir. Start() metodu ile bu iş parçacığının yapacağı işin kodunu barındıran run() metodu çağrılır. Çok sayıda iş parçacığı aynı anda koşarken birbirlerine zaman ayırmaları için sleep() metodu kullanılır ve parametre olarak aldığı milisaniye kadar askıda durur. Kaynak kodlardan Multithreading klasöründe, biri ekrana 5 kere “Hello” diğeri de 0-4 arası sayıları yazan iki iş parçacığını çağıran ThreadHelloCount adlı java programı için örnek çıktı aşağıda verilmiştir: Hello! 0 1 Hello! 2 3 Hello! Hello! 4 Yukarıdaki program çıktısına dikkat edilirse iş parçacıkları farklı sıklıklarla çağrılmıştır. Bunun nedeni sleep() metodundaki Math.random()*1000 ifadesidir. Böylece random fanksiyonu ile her bir iş parçacığı 0-1 saniye arasında değişen farklı sürelerde askıda durmaktadır. Dolayısıyla ThreadHelloCount.java adlı program her koşuşunda farklı bir çıktı üretecektir. Kaynak kodlardan Multithreading klasöründe daha önce anlatılan soket uygulamasının çok iş parçacıklı uygulaması verilmiştir. MultiServer.java programında, Thread sınıfını miraslayan ClientHandler sınıfı herbir Client bağlantısında bir iş parçacığı başlatmaktadır. MultiClient.java programı soket uygulamasındaki TCPClient.java ile hemen hemen aynıdır. 6. Senkronize Edilmiş İş parçacıkları Farklı iş parçacıklarının ortak kullandıkları kaynaklara eşzamanlı erişimleri yanlış sonuçlar üretmeye neden olabilir. O yüzden ortak kaynaklara eş zamanlı erişimi engelleyecek bir mekanizmaya ihtiyaç vardır. Java dili bunu synchronized anahtar kelimesi ile gerçekleştirir. Örneğin aşağıdaki metodda ortak kullanılan sum adlı değişkenin aynı anda farklı iş parçacıkları tarafından güncellenmemesi için updateSum() adlı metod synchronized yapılmıştır. public synchronized void updateSum(int amount) { sum += amount; } Synchronized bir metodu koşan iş parçacığına o anlık bir iş düşmüyorsa wait() metodunu çağırarak synchronized metod üzerindeki kilidi kaldırır ve böylece diğer iş parçacıklarının da ilgili metodu koşmasına izin verir. Eğer bir iş parçacığı işini tamamlamışsa ve wait() konumundaki başka bir iş parçacığının çalışmasını sağlamak istiyorsa notify() metodunu kullanır. Wait konumundaki bütün iş parçacıklarının çalıştırılması için de notifyall() metodu kullanılır. Bu durumda hangi iş parçacığına öncelik verileceğine Java Virtual Machine (JVM) karar verir. 6.1. Üretici-Tüketici (Producer-Consumer) Probleminin Soketlerle Gerçeklenmesi Bilindiği gibi üretici-tüketici probleminde üreticinin ürettiği ve tüketicinin tükettiği kaynak ortak kullanılmaktadır. Burada en önemli problem kaynağa eş zamanlı erişimi engelleyerek tutarlılığı sağlamaktır. Üretici ve tüketicinin ortak çağırdıkları metodlar synchronized yapılarak tutarlı bir kaynak güncellemesi yapılabilir. Üretici-Tüketici uygulaması, kaynak kodlardan Multi Producer-Consumer adlı klasörün içindedir. Üretici ve tüketicinin ortak erişeceği kaynağı (resourse) üretme ve tüketme işlerini yapan addOne() ve takeOne() metodları, Resourse.java programı içindedir. ResourseServer.java programı öncelikle item isimli bir Resourse nesnesi türetir ve Producer içinde item.addOne() çağrısı ile üretmeye başlar. istemci tarafından sunucuya bağlantı kurulduğunda ResourseServer.java programında handler isimli bir ClientThread threadi başlatılır ve istemcilerin isteklerine cevap verilir. İstemci, Sunucuya “1” karakteri yolladıkça kaynakları tutan item nesnesinin takeOne() metodu çağrılarak kaynak harcanır. “0” karakteri ile de bağlantı sonlandırılır. Herhangi bir addOne() metodunda kaynak üretimi belli bir MAX (5) değerle sınırlandırılmıştır. Bu değere ulaşıldığında wait() metodu çağrılarak istemcilerin kaynağı tüketmesi beklenir. Herhangi bir kaynak üretildiğinde tüketilebilmesi için notifyall() metodu ile istemcilere bilgi verilir. takeOne() metodunda da kaynak bittiğinde (değer 0 olduğunda) wait() metodu çağrılarak sunucunun kaynak üretmesi beklenir. Herhangi bir kaynak tüketildiğinde üretilebilmesi için notify() metodu ile sunucuya bilgi verilir. 7. Deney Hazırlığı 1. Socket klasörü içerisindeki TCPServer.java ve TCPClient.java uygulamalarını çalıştırarak bir istemci ve bir sunucudan oluşan istemci-sunucu uygulamasının nasıl çalıştığını gözlemleyiniz. Soket nesnesinin nasıl oluşturulduğunu, soket yazma ve soketten okuma işlemlerinin nasıl gerçekleştirildiğini kavrayınız. 2. Multithreading klasöründeki ThreadHelloCount.java kaynak kodlarında iş parçacıklarının nasıl oluşturulduğunu ve kullanıldığını kavrayınız. Aynı klasördeki çok istemci tek sunucu uygulamasını çalıştırarak çok istemciliğin iş parçacıkları ile nasıl gerçeklendiğini kavrayınız. 3. MultiClient.java programını (Bölüm 6.1’de anlatılan) Multi Producer-Consumer klasörüne kopyalayıp ismini ConsumerClient.java olarak değiştiriniz ve gerekli değişiklikleri yaparak Multi Producer-Consumer uygulamasının doğru bir şekilde çalışmasını sağlayınız. Yazdığınız programı deneye getiriniz. 4. Java programlama dilinde yığın veri yapısının nasıl kullanıldığını Stack klasöründeki uygulamayı kullanarak kavrayınız. Ayrıca, ilgili dilde rastgele sayı üretiminin nasıl gerçekleştirileceğini araştırınız. 5. Eclipse, JCreator, Netbeans gibi farklı IDE’leri kullanarak uygulamaları çalıştırabilirsiniz. Deney sırasında Eclipse kullanılacaktır. 8. Deney Tasarımı ve Uygulaması 1. Deney sorularını cevaplayınız. 2. Deney uygulamalarını aynı makine üzerinde ve ağ ortamında çalıştırınız. 3. Aşağıda detaylı bir şekilde anlatılan üretici-tüketici problemini çok istemci tek sunucu mimaride gerçekleyiniz. Resourse.java programı içindeki addOne() ve takeOne() metotlarında gerekli değişikleri yaparak, 0..99 arası rastgele bir sayı olarak üretilen kaynak yığına itilirken “PUSHED ITEM = ##”; tüketilen kaynak yığından çekilirken “POPED ITEM = ##” şeklinde mesaj yazılmasını sağlayınız (Burada ##, 0..99 arası bir sayıdır). Sunucunun (üretici) yığından çektiği değeri istemciye (tüketici) “YOU POPED = ##” şeklinde yollaması için gerekli değişiklikleri yapınız. Yığının dolu veya boş olduğu bilgisini “STACK IS FULL/EMPTY” şeklinde ekrana yazınız. Java dilinde yığın veri yapısının nasıl kullanılacağı Stack adlı klasördeki StackImplement.java isimli kaynak kodda verilmiştir. Herhangi bir programda yığın veri yapısını kullanmak için java.util.* adlı paket dahil edilmelidir. İstemciden gelen isteklere (“1” kaynağı tüket emrini, “0” ise uygulamanın sonlanması emrini verir) bağlı olarak ResourseServer.java ve ConsumerClient.java programlarının ekran çıktısı aşağıdakine benzer olmalıdır: ResourseServer ConsumerClient PUSHED ITEM = PUSHED ITEM = PUSHED ITEM = PUSHED ITEM = PUSHED ITEM = STACK IS FULL Enter message ('0' to exit): 1 SERVER> YOU POPED = 69 42 57 85 97 69 New client accepted. POPED ITEM = 69 PUSHED ITEM = 74 POPED ITEM = 74 PUSHED ITEM = 41 STACK IS FULL POPED ITEM = 41 PUSHED ITEM = 16 POPED ITEM = 16 POPED ITEM = 97 POPED ITEM = 85 POPED ITEM = 57 PUSHED ITEM = 70 POPED ITEM = 70 POPED ITEM = 42 STACK IS EMPTY PUSHED ITEM = 12 POPED ITEM = 12 PUSHED ITEM = 34 PUSHED ITEM = 1 PUSHED ITEM = 85 PUSHED ITEM = 34 PUSHED ITEM = 55 STACK IS FULL Closing down connection... Enter message ('0' to exit): 1 SERVER> YOU POPED = 74 Enter message ('0' to exit): 1 SERVER> YOU POPED = 41 Enter message ('0' to exit): 1 SERVER> YOU POPED = 16 Enter message ('0' to exit): 1 SERVER> YOU POPED = 97 Enter message ('0' to exit): 1 SERVER> YOU POPED = 85 Enter message ('0' to exit): 1 SERVER> YOU POPED = 57 Enter message ('0' to exit): 1 SERVER> YOU POPED = 70 Enter message ('0' to exit): 1 SERVER> YOU POPED = 42 Enter message ('0' to exit): 1 SERVER> YOU POPED = 12 Enter message ('0' to exit): 0 SERVER> Connection closed... Closing connection... 9. Deney Soruları 1. Soket kavramı, uçtan uca haberleşme ve TCP/UDP kavramlarını açıklayınız. 2. Çok istemcililiğin nasıl gerçeklenebileceğini açıklayınız. 3. addOne() metodundaki notify(); satırı kapatılırsa nasıl bir problemle karşılaşılır? Producer ve Consumer hangi sırada wait durumuna düşer? 4. takeOne() metodundaki notifyall(); satırı kapatılırsa nasıl bir problemle karşılaşılır? Producer ve Consumer hangi sırada wait durumuna düşer? 5. Producer, addOne() metodunda hem wait() ile beklerken hem de notifyall() yaparken istemcilerin kaynağı tüketmesini istiyor. Buradaki wait() ve notifyall() çağrıları arasındaki fark nedir? 6. Consumer, takeOne() metodunda hem wait() ile beklerken hem de notify() yaparken sunucunun kaynak üretmesini istiyor. Buradaki wait() ve notify() çağrıları arasındaki fark nedir? KARADENİZ TEKNİK ÜNİVERSİTESİ BİLGİSAYAR MÜHENDİSLİĞİ BÖLÜMÜ BİLGİSAYAR AĞLARI LABORATUARI 2014-2015 Bahar Dönemi Soket Programlama Deney Raporu Grup No: NUMARA Ad ve Soyad 1. Deneye Soruları Deney sorularını el yazısıyla cevaplayınız. Not: Deney raporu el yazısı ile bu şablon kapak sayfası olacak şekilde hazırlanacaktır. Raporlar, bir sonraki hafta deneyine kadar teslim edilebilir. Kopya raporlar 0 puan alacaklarını kabul ederler. Deney Sorumlusu: Çağatay Murat Yılmaz