send לא חוסם? (sockets)

IP yuval

New member
send לא חוסם? (sockets)

הקדמה: עשיתי שרת קטן שמאזין לפורט מסויים עם listen, וכשaccept חוזר, הוא פותח תהליך חדש (fork) שמטפל בוא. אני רוצה עכשיו להעביר מידע מקובץ לfd שקיבלתי מהaccept ולסגור את התהליך שיצרתי. בסדר. העברתי את המידע עם read מהקובץ וsend(client_fd, buf, count, 0) (אפשר גם להשתמש בwrite...) ללקוח. אחרי זה אני סוגר את את הfd של הלקוח, ויוצא מהתוכנית. הבעיה: הבעיה היא שאם אני ישר סוגר את הfd של הלקוח, לפעמים חלק מהמידע לא מגיע ללקוח (אף על פי שsend אמור להיות block, ולפי מהערך החזרה שלו, הכל נשלח). בשביל "לתקן" את הבעיה אני צריך לעשות sleep לפני שאני סוגר את הfd של הלקוח בשביל שהתהליך יסיים להעביר לו את כל המידע. למה זה ככה? מה עשיתי לא נכון? (התוכנית הזאת אמורה להיות התחלה של שרת http בסיסי מאוד. בעתיד זה אמור להשתמש בmmap ובptreads ואולי להיות יותר דומה לשרת http.) תודה.
 

IP yuval

New member
חלק גדול מהקוד לקוח מפה:

http://www.ecst.csuchico.edu/~beej/guide/net/html/
 
send לא חוסם.

אף פעם לא שמעת ש...
It is better to send than to recv
 
../images/Emo12.gif לא יודע ...

זה מה שמצאתי ב MAN :
MSG_DONTWAIT Enables non-blocking operation; if the operation would block, EAGAIN is returned (this can also be enabled using the O_NONBLOCK with the F_SETFL​
בדקתי ואני אף פעם לא השתמשתי בזה (תמיד שמתי שם 0)...
 

DadleFish

New member
מאיפה הבאת את זה?

אם ה-socket עובד במצב blocking, אז send יכול לחסום אם ה-buffer מלא. זה משהו שממומש ברמת ה-TCP/IP Stack. אז נכון שזה בד"כ לא יקרה, אבל זה בהחלט יכול לקרות. בכל מקרה רצוי לעבוד עם select כדי שלא להיתקל במצב כזה.
 

selalerer

New member
בדקת מה הsend החזיר? מה עם errno?

אני גם עשיתי משהו כזה מהצד ההפוך, שלחתי מהלקוח לשרת ומייד סגרתי את הsocket (של הלקוח) ולא נתקלתי בבעיה. אומנם זאת הייתה הודעה קצרה אבל השרת קיבל את כולה.
 

IP yuval

New member
הוא לא מחזיר כלום. הוא שולח תמיד

את כל המידע. הצד השני יכול להיות telnet או דפדפן. השני המקרים התוצאה זהה - לפעמים (כשהוא שולח הרבה מידע, וכאשר מהירות החיבור נמוכה (לא לlocalhost) לא מגיע כל המידע ליעדו, אלא אם אני מאריך את חיי התהליך בקצת זמן. recv פשוט אומר Connection closed by foreign host. תודה.
 

voguemaster

New member
כי אתה לא עובד נכון יקירי

ראשית, הסבר קצר על SEND ובעצם כל פונק' שכותבת לסוקט: כשאתה קורא ל-send ומציין כמה בתים אתה רוצה לכתוב לסוקט, send תחזיר לך בדיוק כמה הצלחת לכתוב (יכול להיות כמספר הבתים שרצית לשלוח או פחות, מה שנקרא short count). מה שלא מספרים לך ברפרנסים הרגילים הוא ש-send תחזיר לך את מס' הבתים שהיא הצליחה לשלוח עוד הרבה לפני שבאמת המידע ישלח ברשת. הסיבה לכך היא ש-send חוזרת אם היא מצליחה לקחת את המידע שלך ולהוסיף אותו ל-socket send buffer, שזה buffer שמוקצה עבור אותו סוקט בקרנל. הגודל של הבאפר משתנה מאימפלמנציה אחת לשניה אבל החוק זהה: send תחזור בהצלחה אם ניתן לכתוב לפחות בייט אחד לבאפר של השליחה עבור אותו סוקט. יכול מאוד להיות מצב שאתה כותב עם send, המידע משום מה לא מגיע והיישום שלך חושב שהוא כן הגיע. בד"כ נהוג לטפל בבעיה כזו ע"י המתנה לקלט מהצד השני אבל היות והיישום שלך הוא בסגנון שרת WEB הוא סוגר את החיבור ישר אח"כ. זה מביא אותי לנק' הנוספת והחשובה יותר. אל, פשוט אל תסגור את הסוקט לחלוטין עד שאתה בטוח שהמידע נשלח. תשתמש ב-shutdown קודם, שם יש אפשרות לסגור את חצי החיבור לשליחה (לכל חיבור TCP יש בעצם "שני" חיבורים, אחד לשליחה ואחד לקריאה). אתה צריך להבין שנניח אם שלחת מידע מסוים, אפילו אם הוא נשלח ברשת יש מצב שהאפליקציה בצד השני לא הספיקה לקרוא אותו (נניח אם היא הייתה עסוקה לא יכלה לקרוא ל-recv מספיק מהר, ואז קרסה לדוגמא). האפשרויות שעומדות בפניך: 1. לעבוד עם close אבל להשתמש ב-SO_LINGER כדי לקבוע זמן המתנה. מה שקורה במצב כזה הוא ש-close לא חוזרת (ולכן הסוקט לא נסגרת) עד שהמערכת בצד השני לא שולחת ACK על כל המידע (את ה-ACKים על המידע שולח הקרנל, ה-TCP שלו). זה מבטיח לך ש-close תחסום את הפרוסס (הוא הרי ילד אז זה בסדר) עד שכל המידע יגיע לצד השני או שה-TIMEOUT של הסוקט יעבור (אם שמת אחד כי הוא לא פועל בברירת מחדל). אל תשכח לבדוק את הערך המוחזר מ-close במצב כזה. שים לב, זה רק מבטיח לך שהמידע עבר ברשת והגיע לצד השני, זה לא מבטיח לך שהאפליקציה הספיקה לקרוא אותו (שזו הבעיה עם הקריסה שתיארתי קודם). 2. להשתמש ב-shutdown ולסגור את החלק של החיבור שאחראי על כתיבה. אז אתה קורא ל-read או recv והאפליקציה שלך *מחכה*. למה היא מחכה ? לסגירת החיבור של הצד השני (ה-FIN). ברגע שהשתמשת ב-shutdown וסגרת את מצב הכתיבה סימנת שאין לך יותר מה לשלוח. אז האפליקציה השניה תקבל את המידע ותסגור את החיבור ואצלך ה-read יחזיר 0 (מה שנקרא EOF). אז אתה יודע שהסוקט נסגר בצד השני ואתה יכול לסגור אותו לגמרי עם close גם אצלך.
 

voguemaster

New member
חידוד קל

לדקדקנים שבינינו, SEND תחזור מייד אם היא מצליחה לכתוב לבאפר של הקרנל (עבור אותו סוקט) מס' בתים ששווה ל-low watermark של הסוקט, שהוא בברירת מחדל 1. ניתן כמובן לשנות אותו ואז send תחסום אם היא לא מסוגלת להוסיף את כמות המידע המבוקש לבאפר.
 

IP yuval

New member
תודה רבה. הבנתי, ועכשיו זה עובד עם

shutdown, כמו שאמרת.
 
למעלה