Çeviri Hakkında

Çeviri Kervan ASLAN tarafından Yapılmıştır. Çevirenin notları (*** ***) içinde kalan alanlardır.

Orjinal dökümana www.enderunix.org adresinden ulaşılabilir.

elmek:[email protected]

Tarih:16/02/2004



Unix Daemon(bekletici) sunucu programlama


Takdim:


Unix işlemleri arka planda ve ön planda çalışabilir. Ön planda çalışan işlemler

kullancı ile terminal aracılığı ile etkileşirler. Arka planda çalışan programlar

ise kendi başlarına çalışırlar. Kullanıcı durumunu kontrol edebilir fakat ne

yaprıgını bilmesine gerek yoktur. Daemon (bekletici program) terimi arkaplanda

servis veren işlemler için kullanılır. Bir servis başlangıçta çalışmaya

başlayan,sonsuza kadar çalışan, genllikle sonlanmayan, arka planda işleyen,

istek bekleyen ve bunlara cevap veren ve sıksık bu istekleri işlemek için yeni

prosesler (işlem) oluşturan programlardır.


Okuyucuların Unix ve C temellerini bildikleri var sayılıyor. Herhangi bir

başlıkla ilgili daha falza açıklama için "man" komutunu kullanın (anahtar

sözcükleri parantez içerisine yazdım, bu herzaman çok kullanışlı olmuştur, bana

güvenin :)) ). Aklınızda bulunsun bu döküman herşeyi içermez, bu sadece bir

klavuzdur.


1-Daemonizing (Arka planda işletmek için programlama)[fork()]


Öncelikle fork() sistem çağrısı işlemin bir kopyasını(child(***çocuk***)) almak için

kullanılmalıdr,sonra parent(***anne***) sonlanmalıdır.Öksüz kalan çocuk proses

başlangıçtaki Prosesin olacaktir (Bu başlangıctaki sistem presessidir, başka bir

deyişle bütün proseslerin annesi). Sonuc olarak bizim prosesimiz tamamiyle

Anne prosesden kopmuş ve işlemine başlamış olacaktır.


i=fork();

if (i<0) exit(1); /* fork hatası */

if (i>0) exit(0); /* anne çıkar */

/* çocuk(daemon) devam eder*/


(*** fork() prosesin bir kopyasını almak için kullanılan çağrıdır. Anneye çocuğun pid ini döndürürken çocuk prosese 0 değerini döndürür. Hata durumunda -1 değeri döner. ***)



2-İşlem bağımsızlığı


Proses bağlı olduğu uçbirimden sinyaller alır, ve her proses üstünü kontrol eden

uçbirimi i miras alır. Bir sunucu kendisini başlatan işlemden sinyal almakmak

zorundadır, bu sebepten kendsini kontrol eden uç birimden ayrılmak zorundadır.


setsid() /*yeni bir proses gurubu elde eder.*/


bu çağrı sunucuyu yeni bir proses grubu ve oturuma yerleştirir ve kendisini

kontrol eden terminalden ayırır.(setpgrp() buna alternatif olabilir)


3- Miras tanımlayıcılar ve standart giriş/çıkış tanımlayıcıları

[gettablesize,foropen,close,dup,stdio.h]


Açık tanımlayıcılar Çocuk prosese miras kalır.bu kaynakların gereksiz

kullanılmasına sebep olabilir. gereksiz tanımlar fork()sistem çağrısından önce

kapatılmalıdır(böylece miras kalamazlar) yada bütün açık tanımlayıcılar çocuk

proses başlamadan önce kapatılmış olmalıdır.


for(i=getdtablesize();i<=0;--i) close(i);


üç adet standart giriş çıkış tanımlayıcısı vardır: standart giriş

"stdin"(0),standart çıkış "stdout"(1),standart hata "stderr"(2).

Bir standart kütüphane yordamı standart giriş ve çıkıştan okuma ve yazma yapabilir ve bu bir uçbirim veya bir dosya olabilir. Güvenlik için, bu tanımlar açılmış olmalıdır ve zararsız bir giriç/çıkış aygıtı ile bağlanmış olmalıdır (/dev/null gibi)


i=open("/dev/null",O_RDWR); /* stdin */

dup(i); /* stdout */

dup(i); /* stderr */


Unixe verilen sıralı tanımlayıcılar: fopen çağrısı stdini açacaktır ve dup çağrıları stdout ve stderr in birer kopyasını sağlayacaktır.


4-Dosya yaratma meskesi [umask]


Çogu sunucu süper-kullanıcı olarak çalışır, güvenlik sebeplerinden dolayı yarattıkları dosyaları

korumak zorundadırlar. Kullanıcı maskesi ayarlamak dosya yaratılırken meydana gelen güvensiz dosya imtiyazların önüne

geçer.

umask(027);


bu dosya yaratma modunu 750(027 nin tamamlayıcısı) ile sınırlayacaktır.


5-Çalışma dizini[chdir]


Sunucu bilinen bir dizinde çalışmak zorundadır. Bunun bi çok avantajı vardır, aslında tersinin bir çok dezavantajı vardır: Sunucumuzun bir kullanıcının ev dizininden çalışmaya başladığını var sayalım. bazı giriş çıkış dosyalarını bulamayacaktır.


chdir("/server/");


kök "/" dizini her sunucu için uygun olmayabilir, dizin sunucunun tipine göre dikkatli seçilmelidir.


6-Birbirini dışlamak ve tek kopya çalıştırmak.


Bir çok sunucu aynı anda sadece bir kopya çalıştırmaya gerek duyar. Dosya kilitleme metodu bunun için iyi bir çözümdür. İlk proses dosyayı kilitler böylece çalıştırılan ikinci proses bir servisin o anda çalışır durumda olduğunu anlar. Sunucu sonlandığında kilitli dosya otomatik olarak serbest kalır. ve böylece yeni proses çalışmaya başlayabilir. işlemin pid ini kaydetmek iyi bir fikirdir."cat mydaemon.lock" ın "ps -ef|grep mydaemon" dan çok daha kullanışlı olacağı kesindir.


lfp=open("exampled.lock",O_RDWR|O_CREAT,0640);

if (lfp<0) exit(1); /* dosya açılamadı çık*/

if (lockf(lfp,F_TLOCK,0)<0) exit(0); /* dosya kilitlenemedi çık*/

/* sadece ilk proses devam eder. */


sprintf(str,"%d\n",getpid());

write(lfp,str,strlen(str)); /* pid i kilitli dosyaya kaydet */


7-Sinyalleri yakalama[signals,sys/signals]

işlemler kullanıcıdan veya diğer işlemlerden sinyaller alabilir, sinyalleri yakalamak ve uygun davranmak en iyisidir, çocuk prosesler sonlandıklarında SIGCHLD sinyanlini gönderirler. Sunucu işlemler bu sinyalleri ya değerlendirirler yada görmezden gelirler. Bazı sunucular hang-up sinyalini yeniden düzenlenmek için kullanırlar ve bir sinyalle yeniden düzenlenmek çok iyi bir fikirdir.Not: “kill” komutu SIGTERM(15) sinyalini gönderir ve SIGKILL(9) sinyali yakalanamaz.


signal(SIG_IGN,SIGCHLD);/*çocuk proeses sonlandı sinyali*/


***)

Sanrım yukardaki çağrıda bir hata var doğrusu şu şekilde olmalıdır.

signal (SIGCHLD,SIG_IGN);

aşağıda düzeltilmiş hali zaten mevcut. Kafanız karışmasın diye belirttim.

***)



Yukardaki kod çocuk prosesin sonlandrma sinyalini görmezden gelmemizi sağlar(bsd sistemlerinde anne proses çocuk prosesleri beklemelidir. Zombi proseslerin oluşmaması için sinyal yakalanmaldır.), ve aşağıda sinyallerin nasil yakalacağı gösterilmiştir.


void Signal_Handler(sig) /* sinyal yakalayıcı fonksiyon.*/

int sig;

{

switch(sig){

case SIGHUP:

/* suncuyu yeniden düzenle*/

break;

case SIGTERM:

/* sunucuyu sonlandır.*/

exit(0)

break;

}

}


signal(SIGHUP,Signal_Handler); /* hangup sinyali */

signal(SIGTERM,Signal_Handler); /*kill den alınan yazlılım sonlandırma sinyali */


8-)Günlük (log) [syslogd,syslog.conf,openlog,syslog,closelog]


Çalışan sunucular mesaj yaratırlar, ve doğal olarak bazıları önemlidir ve günlüğe kaydedilmelidir. Programcı hata ayıklama mesajlarını, sistem oparatörü hata mesajlarını görmek isteyecektir. Bu mesajları elde değerlendirmenin birkaç yolu vardır.


Bütün çıktıları standart giriş çıkışa yönelndirmek. Bunu eksi sunucular kullanır, stdout ve stderr i kullanırlar böylece mesajlar konsola,uçbirime,dosyaya yada yazıcı aracılığı ile kağıda yazılır. Sunucu başlatılırken giriş/çıkış yendiden yönlendirilir. (yönlendirmeyi deyiştirmek için sunucu yeniden başlatılmak zorundadır. Aslında bu tür sunucular önplanda çalışırlar bekletici program (daemon) değillerdir.


# mydaemon 2 > error.log


bu örenek standart çıkışları konsola standart hataları “error.log” isimli dosyaya yazar.


Not: bu bir bekletici program değildir. Ama normal bir programdır.


Günlük dosyası metodu:bütün mesjlar günlük dosyasına kaydedilmelidir(ihtiyac duyuluyorsa başka dosyalarada kaydedilebilir. Aşağıda bir kayıt tutma örnek fonksiyonu var.


void log_message(filename,message)

char *filename;

char *message;

{

FILE *logfile;

logfile=fopen(filename,"a");

if(!logfile) return;

fprintf(logfile,"%s\n",message);

fclose(logfile);

}


log_message("conn.log","connection accepted");

log_message("error.log","can not open file");


Günlük sunucusu metodu:günlük sunucusunu kullanmak daha esnek bir tekniktir. Unix dağıtımları ismi “syslogd” adında sitem günlüğü daemonuna sahiptir. Bu daemon mesajları sınıflara ayırır(facility olarak bilinir) ve bu sınıflar başka yerlere yönlendirilebilirler. Syslog deamonu bi ayar dosyası kullanır (/etc/syslog.conf) bu dosya da yönlendirme kuralları bulunur.

openlog("mydaemon",LOG_PID,LOG_DAEMON)

syslog(LOG_INFO, "Connection from host %d", callinghostname);

syslog(LOG_ALERT, "Database Error !");

closelog();

openlog çağrısında “mydaemon” katarı bizim daemonumuzun tanımlayıcısıdır. LOG_PID syslogd nin preosess id nin her mesajla birlikte kaydetmesini sağlar ve LOG_DAEMON da mesaj sınıfıdır. Syslog çağrısındaki ilk paramatre en önemlsidir ve diğerleride printf/sprintf gibi çalışır. Bir kaçtane mesaj sınıfı vardır. Günlük kayıt tercihleri ve önem seviyesi. Aşağıda bir kaç tane örenk var.

Mesaj sınıfları (message classes):LOG_USER, LOG_DAEMON, LOG_LOCAL0 , LOG_LOCAL7

Kayıt tercihi (log options): LOG_PID, LOG_CONS, LOG_PERROR

önem seviyesi (priority levels): LOG_EMERG, LOG_ALERT, LOG_ERR, LOG_WARNING, LOG_INFO



HAKKINDA


Bu döküman Levent KARAKAŞ tarafından yazılmaıştır elmek:levent at mektup dot at . Bir çok kitap, kaynak ve kalvuzlar kullanılmıştır. Bu metin örnek bir sunucu kaynak kodu içerir. (Linux 2.4.2, OpenBSD 2.7, SunOS 5.8, SCO-Unix 3.2 veya sizin sahip olduğunuz herhangi bir Unix dağıtımınzda derlenebilir.) Kaynak kodunu burdan indirebilirsiniz:

http://www.enderunix.org/docs/eng/exampled.c Bu dökümanı kullanışlı bulmanız dileği ile.Unix i seviyoruz.





/*

UNIX Daemon sunucu programlama örnek programı

Levent Karakas <levent at mektup dot at > MAYIS 2001


Derlemek için: cc -o exampled examped.c

Çalıştırmak için: ./exampled

Test etmek için: ps -ef|grep exampled (or ps -aux on BSD systems)

kayıt dosyasını test için: tail -f /tmp/exampled.log

sinyalleri test etmek için: kill -HUP `cat /tmp/exampled.lock`

Sonlandırmak için: kill `cat /tmp/exampled.lock`

*/


#include <stdio.h>

#include <fcntl.h>

#include <signal.h>

#include <unistd.h>


#define RUNNING_DIR "/tmp"

#define LOCK_FILE "exampled.lock"

#define LOG_FILE "exampled.log"


void log_message(filename,message)

char *filename;

char *message;

{

FILE *logfile;

logfile=fopen(filename,"a");

if(!logfile) return;

fprintf(logfile,"%s\n",message);

fclose(logfile);

}


void signal_handler(sig)

int sig;

{

switch(sig) {

case SIGHUP:

log_message(LOG_FILE,"hangup signal catched");

break;

case SIGTERM:

log_message(LOG_FILE,"terminate signal catched");

exit(0);

break;

}

}


void daemonize()

{

int i,lfp;

char str[10];

if(getppid()==1) return; /*çalışan bir sunucu mevcut */

i=fork();

if (i<0) exit(1); /* fork da hata */

if (i>0) exit(0); /* anne çıkar */

/*çocuk (daemon)devam eder */

setsid(); /*yeni bir işlem gurub elde eder */

for (i=getdtablesize();i>=0;--i) close(i); /* bütün tanımlayıcıları kapat */

i=open("/dev/null",O_RDWR); dup(i); dup(i); /*standart giriş çıkış*/

umask(027); /* dosya imtiyazlarını yeniden ayarla */

chdir(RUNNING_DIR); /*çalışma dizinini değiştir. */

lfp=open(LOCK_FILE,O_RDWR|O_CREAT,0640);

if (lfp<0) exit(1); /*dosya açılamadı */

if (lockf(lfp,F_TLOCK,0)<0) exit(0); /*dosya kilitlenemedi */

/*ilk çalışan yoluna devam eder */

sprintf(str,"%d\n",getpid());

write(lfp,str,strlen(str)); /*proses tanımayıcısını (pid) kaydet */

signal(SIGCHLD,SIG_IGN); /*çocuk proeses sinyallerini görmezden gel */

signal(SIGTSTP,SIG_IGN); /*terminal sinyallerini görmezden gel */

signal(SIGTTOU,SIG_IGN);

signal(SIGTTIN,SIG_IGN);

signal(SIGHUP,signal_handler); /*hangup sinyalini yakala */

signal(SIGTERM,signal_handler); /*kill sinyalini yakala */

}


main()

{

daemonize();

while(1) sleep(1); /* çalışmaya başla*/

}


/* EOF */