220 likes | 450 Views
דוגמא ל Daemon ב Java. תכנות מתקדם 2 89-211 תרגול מספר 10 תש"ע 2009-2010. אליהו חלסצ'י. בשיעור שעבר ראינו כיצד ניתן להריץ תוכנית java כ service תחת windows . השתמשנו ב tomcat כתוכנית native שהריצה JVM ובתוכה התוכנית שלנו.
E N D
דוגמא ל Daemon ב Java תכנות מתקדם 2 89-211תרגול מספר 10 תש"ע 2009-2010 אליהו חלסצ'י
בשיעור שעבר ראינו כיצד ניתן להריץ תוכנית java כ service תחת windows. השתמשנו ב tomcat כתוכנית native שהריצה JVM ובתוכה התוכנית שלנו. בשיעור זה נלמד כיצד לייצר Daemon עבור תוכנית ה java שלנו – תחת Unix (לינוקס) התוכניות בתרגול נכתבו תחת ubuntu 9.04 גם הפעם ברצוננו ליצור תהליך מנותק שרץ ברקע ללא התערבותנו הישירה. הקדמה
תחילה נעשה שימוש ב fork() ה fork יוצר העתק (הילד) של התהליך שלנו (ההורה). ניתן להורה לסיים, והילד יעבור להיות תחת Init (אב כל התהליכים). התוצאה: תהליך הילד מנותק לחלוטין מתהליך האב וממשיך לרוץ ברקע כעצמאי. נאתחל את הרשאות התהליך ע"פ הצורך ע"י umask() (user mask) התהליך שלנו עדיין יכול לקבל signals מההורה המקורי או מהחברים באותה קבוצת תהליכים, לכן נרצה לנתק אותו ע"י setsid(). כיצד נוצר Daemon
נרצה לשנות את ה directory ע"י chdir() כדי שנפעל מתוך אחת מוכרת (ההפך עלול לגרום לבעיות) או אחת המתאימה לריצת התהליך שלנו (לדוג' chdir(“/servers/”)) נרצה לסגור Descriptors שאולי ירשנו ובהם stdin, stdout, stderr נפנה אותם ל I/O device בלתי מזיק כמו /dev/null כעת, לאחר כל הניתוקים, נוכל להריץ כל תוכניתכ daemon ע"י קריאת מערכת system() למשל "java –jar MyDaemon.jar” כיצד נוצר Daemon
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #defineEXIT_SUCCESS0 #defineEXIT_FAILURE1 staticvoiddaemonize(void) { pid_tpid,sid; if(getppid()==1)return; pid=fork(); if(pid<0){ exit(EXIT_FAILURE); } if(pid>0){ exit(EXIT_SUCCESS); } umask(0); sid=setsid(); if(sid<0){ exit(EXIT_FAILURE); } if((chdir("/"))<0){ exit(EXIT_FAILURE); } freopen("/dev/null","r",stdin); freopen("/dev/null","w",stdout); freopen("/dev/null","w",stderr); } נכתוב תוכנית ב C שתריץ עבורנו כלתוכנית java כ daemon נגדיר את הפונקציה daemonizeכך שתבצע את הניתוקים ההכרחיםותשאיר אותנו בתהליך הילד אחריהם. זה אומר שאנחנוכבר daemon... מה זה אומר אםהpid שלנו שווה 1?
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #defineEXIT_SUCCESS0 #defineEXIT_FAILURE1 staticvoiddaemonize(void) { pid_tpid,sid; if(getppid()==1)return; pid=fork(); if(pid<0){ exit(EXIT_FAILURE); } if(pid>0){ exit(EXIT_SUCCESS); } umask(0); sid=setsid(); if(sid<0){ exit(EXIT_FAILURE); } if((chdir("/"))<0){ exit(EXIT_FAILURE); } freopen("/dev/null","r",stdin); freopen("/dev/null","w",stdout); freopen("/dev/null","w",stderr); } נכתוב תוכנית ב C שתריץ עבורנו כלתוכנית java כ daemon נגדיר את הפונקציה daemonizeכך שתבצע את הניתוקים ההכרחיםותשאיר אותנו בתהליך הילד אחריהם. מה ה fork עושה? יוצר העתק חדש שלתהליך ההורה ומחזירprocess id תהליך הילד ימשיךמאותה השורה בקוד.
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #defineEXIT_SUCCESS0 #defineEXIT_FAILURE1 staticvoiddaemonize(void) { pid_tpid,sid; if(getppid()==1)return; pid=fork(); if(pid<0){ exit(EXIT_FAILURE); } if(pid>0){ exit(EXIT_SUCCESS); } umask(0); sid=setsid(); if(sid<0){ exit(EXIT_FAILURE); } if((chdir("/"))<0){ exit(EXIT_FAILURE); } freopen("/dev/null","r",stdin); freopen("/dev/null","w",stdout); freopen("/dev/null","w",stderr); } נכתוב תוכנית ב C שתריץ עבורנו כלתוכנית java כ daemon נגדיר את הפונקציה daemonizeכך שתבצע את הניתוקים ההכרחיםותשאיר אותנו בתהליך הילד אחריהם. pid<0 אומר שתהליךה fork נכשל. pid>0 אומר שאנונמצאים בתהליך ההורהולכן נסיים אותו בהצלחה. pid=0 אומר שאנו נמצאיםבתהליך הילד ולכן נמשיךעם הניתוקים שנותרו. מהו pid<0? pid>0? מה קורה אםpid==0?
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #defineEXIT_SUCCESS0 #defineEXIT_FAILURE1 staticvoiddaemonize(void) { pid_tpid,sid; if(getppid()==1)return; pid=fork(); if(pid<0){ exit(EXIT_FAILURE); } if(pid>0){ exit(EXIT_SUCCESS); } umask(0); sid=setsid(); if(sid<0){ exit(EXIT_FAILURE); } if((chdir("/"))<0){ exit(EXIT_FAILURE); } freopen("/dev/null","r",stdin); freopen("/dev/null","w",stdout); freopen("/dev/null","w",stderr); } נכתוב תוכנית ב C שתריץ עבורנו כלתוכנית java כ daemon נגדיר את הפונקציה daemonizeכך שתבצע את הניתוקים ההכרחיםותשאיר אותנו בתהליך הילד אחריהם. מגדיר את הרשאות התהליך (כמו הפקודה chmod) למשל umask(027) תקביל ל chmod 750 (המשלים של 027) מה מגדיר umask?
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #defineEXIT_SUCCESS0 #defineEXIT_FAILURE1 staticvoiddaemonize(void) { pid_tpid,sid; if(getppid()==1)return; pid=fork(); if(pid<0){ exit(EXIT_FAILURE); } if(pid>0){ exit(EXIT_SUCCESS); } umask(0); sid=setsid(); if(sid<0){ exit(EXIT_FAILURE); } if((chdir("/"))<0){ exit(EXIT_FAILURE); } freopen("/dev/null","r",stdin); freopen("/dev/null","w",stdout); freopen("/dev/null","w",stderr); } נכתוב תוכנית ב C שתריץ עבורנו כלתוכנית java כ daemon נגדיר את הפונקציה daemonizeכך שתבצע את הניתוקים ההכרחיםותשאיר אותנו בתהליך הילד אחריהם. את כל ערוצי ה signals שתהליך זה אולי ירש ועשויים להפריע לו. מה אנו רוצים לנתקכעת ?
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #defineEXIT_SUCCESS0 #defineEXIT_FAILURE1 staticvoiddaemonize(void) { pid_tpid,sid; if(getppid()==1)return; pid=fork(); if(pid<0){ exit(EXIT_FAILURE); } if(pid>0){ exit(EXIT_SUCCESS); } umask(0); sid=setsid(); if(sid<0){ exit(EXIT_FAILURE); } if((chdir("/"))<0){ exit(EXIT_FAILURE); } freopen("/dev/null","r",stdin); freopen("/dev/null","w",stdout); freopen("/dev/null","w",stderr); } נכתוב תוכנית ב C שתריץ עבורנו כלתוכנית java כ daemon נגדיר את הפונקציה daemonizeכך שתבצע את הניתוקים ההכרחיםותשאיר אותנו בתהליך הילד אחריהם. את הפקודה cd. כדי להחליף ל directory המתאים לשירות שלנו. מה הפקודה chdir מבצעת?
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #defineEXIT_SUCCESS0 #defineEXIT_FAILURE1 staticvoiddaemonize(void) { pid_tpid,sid; if(getppid()==1)return; pid=fork(); if(pid<0){ exit(EXIT_FAILURE); } if(pid>0){ exit(EXIT_SUCCESS); } umask(0); sid=setsid(); if(sid<0){ exit(EXIT_FAILURE); } if((chdir("/"))<0){ exit(EXIT_FAILURE); } freopen("/dev/null","r",stdin); freopen("/dev/null","w",stdout); freopen("/dev/null","w",stderr); } נכתוב תוכנית ב C שתריץ עבורנו כלתוכנית java כ daemon נגדיר את הפונקציה daemonizeכך שתבצע את הניתוקים ההכרחיםותשאיר אותנו בתהליך הילד אחריהם. סגירה של ה descriptors הסטנדרטיים. ב unix גם ה stdin ודומיו מצביעים ל"קובץ". בפקודות כאן אמרנו להם להצביע מחדש ל"קובצי" device אחרים – לnull מה מתבצע כעת וכיצד?
כעת ב main נוכל לקרוא ל daemonize() שאחריו נהיה למעשה בתהליך הילד שכעת מנותק מהכל ומהווה למעשה daemon. בדוגמא לקחנו את כל הארגומנטים וצירפנו לפקודה java תחת המחרוזת command. (יותר כללי היה לו לא היינו מקבעים את java) את command נריץ ב shell ע"י system(). לתוכנית המקומפלת נקרא Daemon. ריצה למשל ע"י: # ./Daemon –jar /home/eli/MyDaemon.jar מה יקרה כשנריץ? intmain(intargc,char*argv[]){ daemonize(); charcommand[80]="java"; inti; for(i=1;i<argc;i++){ strcat(command," "); strcat(command,argv[i]); } system(command); return0; } Init Daemon ההורה Daemon הילד java
כעת ב main נוכל לקרוא ל daemonize() שאחריו נהיה למעשה בתהליך הילד שכעת מנותק מהכל ומהווה למעשה daemon. בדוגמא לקחנו את כל הארגומנטים וצירפנו לפקודה java תחת המחרוזת command. (יותר כללי היה לו לא היינו מקבעים את java) את command נריץ ב shell ע"י system(). לתוכנית המקומפלת נקרא Daemon. ריצה למשל ע"י: # ./Daemon –jar /home/eli/MyDaemon.jar מה יקרה כשנעשה kill ל java? intmain(intargc,char*argv[]){ daemonize(); charcommand[80]="java"; inti; for(i=1;i<argc;i++){ strcat(command," "); strcat(command,argv[i]); } system(command); return0; } Init Daemon הילד java
כמו שראינו יכולנו להפוך את התוכנית שלנו לכלי גנרי להרצת כל תוכנית שאינה יכולה לעשות daemonize. כלי אחד כבר קיים תחת השםstart-stop-daemon דוגמא לשימוש: חשוב לייצר pidחדש ולשמור את תוכנו בקובץ כדי שהמע' לא תטעה עם תהליך אחר של java, וכדי שמאוחר יותר נוכל לבצע --stop ל pid השמור. שימוש בכלים קיימים # sudo start-stop-daemon --start --quiet --background --make-pidfile --pidfile /var/run/eli.txt --startas java -- -jar /home/eli/MyDaemon.jar
הריגת התהליך בצורה "ברוטאלית" שכזו, כפי שראינו בדוגמאות עד כה, לא מאפשרת לנו לבצע סגירות אחרונות מתוך תוכנית ה java שלנו. התהליך עצמו נסגר, ואין קריאה לאיזושהי מתודת stop שמימשנו בתוך ה java. אז מה ניתן לעשות כדי לסגור תוכנית כמו שצריך? בעיה • ניתן למשל לפתוח socket על port כלשהו ולהאזין. • כשתינתן הפקודה “stop me” למשל, נבצע סגירה. • נבנה תוכנית נוספת השולחת ל port הזה “stop me”ופשוט נריץ אותה רגע לפני שנהרוג את התהליך בברוטאליות.
אפשרות נוספת תהיה להשתמש בכלים אחרים כגון jsvc של apache. (הכלי המקובל ל java) מספק ממשק Daemon עם מתודות לאתחול, הפעלה, עצירה והריסה. הפקודה jsvc תפעיל את אותן המתודות ע"פ הפרמטרים שתקבל כשתופעל. לדוגמא: שימוש בכלים קיימים # jsvc -cp /usr/share/java/commons-daemon.jar:/home/eli/MyService.jar Main
התקנה: הורידו את ה source מתוך: http://commons.apache.org/downloads/download_daemon.cgi קמפלו את הקוד: כעת ניתן להעתיק את תיקיות ה package שתחת java אל כל פרויקט java רגיל ואז לממש את הממשק Daemon. שימוש בכלים קיימים JAVA_HOME=/usr/bin/java export JAVA_HOME cd daemon-1.0.1/src/native/unix ./configure make
שימוש בכלים קיימים importorg.apache.commons.daemon.*; publicclassMainimplementsDaemon{ @Override publicvoiddestroy(){ // TODO Auto-generated method stub } @Override publicvoidinit(DaemonContextcontext)throwsException{ // TODO Auto-generated method stub } @Override publicvoidstart()throwsException{ // TODO Auto-generated method stub } @Override publicvoidstop()throwsException{ // TODO Auto-generated method stub } }
איך jsvc עובד? שימוש בכלים קיימים יוצר מפעיל ע"י signals את ה controlled process יוצר
בד"כ נרצה שה daemon שבנינו יופעל עם אתחול המערכת. התיקייה /etc/init.d מכילה סקריפטים שטוענים daemons בעת אתחול המערכת. כדי להפעיל את ה daemon שלנו עם האתחול, נצטרך ליצור סקריפט הפעלה עבורו בתיקייה זו. סקריפט בד"כ נכתב ב bash אם כי אין זו חובה. נראה כעת שתי דוגמאות של סקריפטים מוכנים. הפעלה בעת אתחול
/etc/init.d/skeleton סקריפט שלדי עבור הרצת daemons. ניתן להעתיק ולהתאים ל daemon שלנו. משתמש ב start-stop-daemon /etc/init.d/tomcat6 סקריפט להרצת שרת ה tomcat כ daemon. משתמש ב jsvc. הפעלה בעת אתחול
מדוע היינו צריכים לעשות fork ולא להריץ ישר באותו התהליך? מה הבעיה בשיטות שראינו בתחילת השיעור וכיצד ניתן לפתור אותן? למה משמשת התיקייה /etc/init.d ? חישבו אילו התאמות תצטרכו לעשות בפרויקט הקורס כדי להריצו כשירות גם ב Windows וגם ב Linux. הטמעה