Bu yazımda sizlere, istemci-sunucu uygulaması geliştirenlerin kullanabileceği
"Acceptor (alıcı)-Connector
(bağlayıcı)" tasarım
kalıbını açıklayacağım.
Alıcı-bağlayıcı tasarım kalıbı, genel olarak iletişim servislerini başlatmak
ve istemci-sunucu arasında bağlantıyı
sağlamak için kullanılan "nesne oluşturma (object
creational)" tasarım kalıplarındandır. Bu tasarım kalıbının amacı; bağlantı kurulması ve servislerin başlatılması
işlemlerini, veri iletişimi aşamalarından ayırmaktır. Örneğin; medikal görüntülerin
saklandığı bir sunucuyu ele alalım. Bu sunucu, istemcinin belirttiği bir IDye
göre ilişkili görüntüyü istemciye göndermekten sorumlu olsun.
Bu örnek için, öncelikle istemci ve sunucu arasında TCP/IP
ya da UDP
türünden bir bağlantının kurulması gerektiği açıktır. Bunun ardından, sunucu
ile istemci arasında belirlenen protokole göre gerekli komutlar gönderilerek
medikal görüntüler istemciye ulaşacaktır. Alıcı-bağlayıcı tasarım kalıbının
bu örnekte kullanılması sayesinde; baðlantı kurulması işlemleri, görüntünün
istemciye aktarılması aşamalarından ayrılmış olacaktır. Bu sayede, iletişimin
kurulması için oluşturduğunuz kodları değiştirmeden sunucuya yeni servisler
kolaylıkla eklemeniz mümkün olacaktır. Ayrıca, bağlantı kurulması için oluşturduğunuz
kodları bir kütüphane içinde toplayarak yeni projelerinizde hızlıca kullanabiliceksiniz
ya da kullandığınız protokolü değiştirmek istediğinizde tek ilgilenmeniz gereken
sınıf bağlantıyı sağladığınız sınıf olacaktır.
Alıcı-bağlayıcı tasarım kalıbı temel olarak 3 bileşenden oluşmaktadır: Alıcılar,
bağlayıcılar, servis işleyiciler.
Bağlayıcı (connector); alıcı
ile bağlantıyı aktif olarak kuran ve oluşan bağlantı üzerinden veri transferi
için gerekli olan "servis işleyicileri" (service handler) oluşturan
bileşendir.
Alıcı (acceptor) bileşeni de
pasif olarak bağlayıcılardan gelecek istekleri bekler, istekle birlikte bağlantıyı
kurar ve kendi "servis işleyicisini" oluşturur.
Servis işleyiciler; uygulama
seviyesinde veri transferini, komutların aktarımını, kısacası uygulamaya özgü
veri iletişimi işlemlerinin yapıldığı bileşendir.
Teknik ayrıntılara geçmeden önce, bu tasarım kalıbının kullanıldığı sistemlerden
örnekler vermek istiyorum.
Ericsson Çağrı Merkezi
Yönetim Sistemi için bu tasarım kalıbı kullanılarak,
Çağrı Merkezi Yönetici Sunucuları pasif moddaki süpervizörlere bağlantı kurmaktadır.
Ayrıca; yüksek hızlı medikal görüntü aktarımı için gerçekleştirilmiş olan Spectrum
projesinde de aynı tasarım kalıbı uygulanmıştır. Platformdan bağımsız ağ uygulamalarını
C++ dili ile gerçekleştirmek için oluşturulmuş olan ACE
kütüphanesi de aynı tasarım kalıbını içermektedir. ACE kütüphanesi
içinde; yukarıda açıklanan bileşenler ServiceHandler, Acceptor, Connector isminde
sınıflar olarak karşımıza çıkmaktadır. Bu aşamada, alıcı-bağlayıcı tasarım kalıbının avantaj ve dezavantajlarını inceleyelim.
Avantajlar:
- Yazılımın yeniden kullanılabilirliği, taşınabilirliği
artmaktadır: Bağlantının kurulmasını uygulamaya özgü veri
iletişimi aşamalarından ayırdığınız için yeniden kullanılabilir bileşenler
oluşturmuş olacaksınız. Projelerinizde servis işleyicileri değiştirerek kısa
sürede yeni uygulamalar geliştirmeniz mümkün olacaktır.
- Uygulamanızın çevikliği artacaktır: Servis
işleyicilerle alıcıyı ayırdığınız için, haberleşmede kullandığınız aktarım
elemanlarının yanlışlıkla yazılıp/okunma problemi ile karşılaşmayacaksınız.
Bu tür hatalara; soket veya
TLI gibi ağ programlama arayüzlerini
kullanan ve tasarım kalıpları uygulanmayan yazılımlarda sıkça rastlanmaktadır.
- Tasarımdan koda geçiş hızınız artacaktır:
Tasarımınızda alıcı ve servis işleyiciler için farklı sınıflar
oluşturmanız sayesinde, koda geçtiğiniz zaman geliştirme hızınızın arttığını
ve kod içinde kaybolmadığınızı farkedeceksiniz.
Dezavantaj:
- Ek karmaşıklık getirmektedir: Basit
istemci-sunucu uygulaması geliştirdiğiniz durumda, bu tasarım kalıbının normalden
fazla sayıda sınıf oluşturmanıza sebep olacaktır. Ancak; servis sayısı ve
uygulamalarınızdaki karmaşıklık düzeyi arttıkça geliştirdiğiniz sistem üzerinde
kolay değişiklik yapabildiğinizi farkedeceksiniz.
ACE kütüphanesini kullanarak basit bir uygulamada bu tasarım kalıbını denemek
isterseniz, ihtiyaç duyacağınız sınıflar Acceptor,
Connector, ServiceHandler
ve Reactor olacaktır. Reactor
sınıfı işletim sistemi seviyesindeki "select" mekanizmasina benzer
olarak çalışmaktadır. Reactor sınıfı, ileriki yazılarımda açıklamayı düşündüğüm
Reactor tasarım kalıbına göre oluşturulmuştur. İstemciden bir istek gelmesi
durumunda, işletim sistemi düzeyinde algılanan bu olay önceden Reactore kayıt
olmuş olan Olay İşleyiciye (event
handler) yönlendirilecektir. Şekil 1de sunucu-istemci mimarisine göre bir uygulamanın sınıfları arasındaki
etkileşim gösterilmektedir.
Şekil 1: Sunucu- İstemci Etkileşimi
Şekilde dikkat edilmesi gereken önemli noktalardan biri her iki tarafta da
Reactor sınıfının kullanılmasıdır.
Bununla birlikte, istemci tarafında Connector kullanılırken sunucu tarafında
Acceptor kullanılmaktadır. İstemciden bağlantı isteği gelmesi durumunda, sunucu
tarafındaki Reactor bu durumu Acceptor bileşenine bildirmekte, bağlantının kurulmasının
ardından istemciye bir geri bildirim yapılmaktadır. Bu aşamada, istemci tarafındaki
Reactor durumu algılar ve Connector bileşenine yönlendirir. Daha sonra, sunucu
ve istemci tarafında ServiceHandlerlar yaratılarak bundan sonraki veri iletişimi
bu 2 bileşen arasında gerçekleşir.
ACE kütüphanesini kullanarak platformdan bağımsız olarak C++ dili ile iletişim
uygulamaları geliştirmek istiyorsanız, bu kütüphaneyi http://deuce.doc.wustl.edu/Download.html
adresinden ücretsiz olarak indirebilirsiniz. Bu kütüphanenin tamamı, tasarım
kalıplarını kullanarak geliştirilmiş ve Ericsson,
Boing, Siemens,
Lockheed-Martin gibi firmalar
tarafından yıllardır kullanılmaktadır. İçerdiği tasarım kalıplarını kendi uygulamalarınızda
kolaylıkla uygulayabilir, hızlı şekilde yazılım geliştirebilirsiniz.
ACE kütüphanesinde bu sınıfların nasıl kullanıldığını göstermek için basit
bir istemci-sunucu uygulamasına inceleyelim. ServerServiceHandler
sınıfı bağlantı kurulduktan sonra, ekrana bağlantının kurulduğunu bildirmek
için bir mesaj yazacak olan open( ) metoduna sahiptir.
class ServerServiceHandler:public ACE_Svc_Handler<ACE_SOCK_STREAM,ACE_NULL_SYNCH>{
public:
int open(void*){cout<<"server tarafinda baglanti kuruldu..."<<endl;}
};
typedef ACE_Acceptor<ServerServiceHandler,ACE_SOCK_ACCEPTOR> MyAcceptor;
int main( ){
ACE_INET_Addr addr(PORT_NUM);
MyAcceptor acceptor(address, ACE_Reactor::instance( ) );
while(1)
ACE_Reactor::instance()->handle_events();
}
|
MyAcceptor ise, ACE içinde yer
alan ACE_Acceptor sınıfının ServerServiceHandler ve ACE_SOCK_ACCEPTOR ile örneklenmesi
sonucu oluşturulmuştur. main( ) içerisinde acceptor yaratılarak, sonsuz döngüde
Reactor bileşeninin olayları işlemesi için handle_events fonksiyonu kullanılmıştır.
Bağlantının kurulmasının ardından, ServerServiceHandlerın open( ) metodu otomatik
olarak çağrılacak ve ekrana mesaj basacaktır.
Sunucu tarafında çalışacak bu kodlarda gördüğünüz gibi, ServerServiceHandler
ve MyAcceptor sınıfları ayrı sınıflar olarak oluşturulmuştur. Servis sayısının
artacağı durumlarda farklı sınıflar yazarak işlemlerin basitleşeceği bu basit
uygulamada da görülmektedir.
İstemci programı da, ServiceHandler ve MyConnector sınıfları yazılarak oluşturulabilir.
main( ) içinde, connector yaratılmakta ve connect( ) metodu ile sunucuya bağlanmaktadır.
Bağlantının kurulmasının ardından otomatik olarak open( ) metodu çağrılmaktadır.
Bu örneğin ve daha ileri düzeydeki örneklerin detaylarına ACE kütüphanesi içinden
ulaşabilirsiniz.
class ClientServiceHandler:public ACE_Svc_Handler<ACE_SOCK_STREAM,
ACE_NULL_SYNCH>{
public:
int open(void*){cout<<"istemci tarafinda baglanti kuruldu..."<<endl;
};
typedef ACE_Connector<ClientServiceHandler, ACE_SOCK_CONNECTOR>
MyConnector;
int main( ){
ACE_INET_Addr addr(PORT_NO, HOSTNAME);
ClientServiceHandler *handler=new ClientServiceHandler;
MyConnector connector;
if(connector.connect(handler, addr) = = -1)
cout<<"iletisim hatasi..."<<endl;
while(1)
ACE_Reactor::instance()->handle_events( );
}
|
Geliştireceğiniz istemci-sunucu uygulamalarında bu yazıda açıklanan tasarım
kalıbını kullanırsanız, uygulamanıza yeni işlevler kolaylıkla katabilirsiniz. Aksi halde; bağlantının kurulması, servislerin başlatılması, komutların gönderilmesi,
veri iletişimi kodları iç içe geçecek ve belirli bir zaman sonra kodu yönetebilmeniz
mümkün olmayacaktır. Bir sonraki yazıda görüşmek üzere.
Çağatay ÇATAL
[email protected]
Kaynaklar:
1. D. C. Schmidt, “Connector: a Design Pattern for Actively Initializing Network
Services,” C++ Report, vol. 8, January 1996.
2. Douglas C. Schmidt and Steve Huston, C++ Network Programming: Systematic
Reuse with ACE and Frameworks, Addison-Wesley Longman, 2003.
3. Douglas C. Schmidt and Steve Huston, C++ Network Programming: Mastering
Complexity with ACE and Patterns, Addison-Wesley Longman, 2002.
4. http://www.cs.wustl.edu/~schmidt/ACE.html
5. The ACE Programmers Guide: Practical Design Patterns for Network and Systems
Programming
by Stephen D. Huston, James CE Johnson, Umar Syyid
Makale:
Acceptor (Alıcı)-Connector (Bağlayıcı) Tasarım Kalıbı C++ ve C++.NET dili Çağatay Çatal
|