שימוש נכון ב-TCP וב - NIO

ilgnd

New member
שימוש נכון ב-TCP וב - NIO

שלום לכולם,
אני רוצה להעלות כאן שאלה, ואשמח לתשובות וזווית ראייה. מדובר בתחום שנגעתי בו לראשונה ברכיב שאני כותב עכשיו, ובעבודה שלי אין לי עם להתייעץ ואין לי שום מושג אם אני צודק או לא (זו אחת הבעיות הקשות במקום בו אני עובד). נתקלתי בויכוח ביני לבין המפתח של הלקוח, וכרגע צריך להחליט מי צודק, ומי יבצע את השינוי בצד שלו. האמת היא שזה לא כזה משנה: הלקוח תמיד צודק ובסוף אנחנו נבצע את השינוי, אבל אשמח לתשובה גם מזווית אישית, כדי לדעת איך לכתוב נכון.
הממשק ביני לבין הלקוח מבוסס על הודעות בינאריות, שעוברות על TCP פשוט. אין שום פרוטוקול יותר גבוה כמו HTTP, AMQP וכו'.
המבנה של ההודעה בנוי מכותרת ומגוף ההודעה.
מבנה הכותרת:
1. magic שמטרתו זיהוי byte-order וזיהוי תחילת הודעה במקרה שמידע הלך לאיבוד (אני יודע שב-TCP מידע לא אמור ללכת לאיבוד)
2. גודל ההודעה
3. meta-data
לאחר מכן מגיע גוף ההודעה שגודלו גודל ההודעה פחות גודל הכותרת (גודל הכותרת קבוע).
אני עובד עם JAVA עם NIO. אדגיש שוב שהבעיה העיקרית שלי היא שאני משתמש לראשונה ב-NIO ולכן אין לי שום דרך לדעת אם אני עובד נכון או לא.
יש לי חוצץ מעגלי בגודל 15MB (לצורך העניין). כאשר מגיע אובייקט מהמערכת שלי אותו אני רוצה לשלוח אני לוקח את האובייקט, יוצר ממנו את הבתים של ההודעה וכותב אותם לאותו חוצץ.
ברקע יש thread שמתעורר ברגע שה - SocketChannel מסמן לי write-ready, ואז אני כותב באמצעות channel.write את כל מה שיש לי בחוצץ באותו רגע. בפועל כמובן ישנה אפשרות שלא אצליח לכתוב את הכל (משתמש במצב של non-blocking) ולכן בפעם הבאה שאתעורר, אכתוב מהנקודה בה הפסקתי עד לנקודה האחרונה בה יש נתונים בחוצץ. בזמן הזה, אני יכול לקבל עוד אובייקטים ולדחוף אותם לחוצץ.
כך נוצר מצב שלפעמים אני כותב הודעה שלמה, לפעמים אני כותב הודעה וחצי, לפעמים חצי הודעה, ואז שלוש הודעות ואז עוד חצי וכו'. המצב הזה קביל בעיני מפני שלוגיקה יותר גבוהה אמורה לדעת לפרק את ההודעות. למעשה ככה אני עובד בקריאה: אוסף בתים לתוך חוצץ אחר ובונה הודעה רק כשקיבלתי את כל הבתים שאני צריך.
אני לא מרשה לעצמי להניח שום דבר על שלמות הגעת ההודעות, מלבד הגעה בסדר הנכון.
אם מישהו קרא עד כאן וחושב שאני עובד בצורה לא נכונה, אשמח אם יעיר לי עוד לפני שיראה איך הצד השני עובד. הרבה יותר חשוב לי לדעת אם אני עובד נכון או לא.
במקרה שיש הודעות מאוד גדולות (אנחנו מגיעים גם ל-100MB להודעה), הודעה לפעמים נקטעת באמצע ורק באירטציה הבאה אני אשלח את החלק השני. כאמור זו לא בעיה בעיני, אבל זה יוצר בעיות בצד השני (של הלקוח):
הלקוח מניח שאנחנו שולחים הודעה אחת כל פעם, ולכן קורא כל מה שיש לי לשלוח. אם הוא מקבל חצי הודעה זה לא נורא כי הוא יודע שהוא יחכה לשאר ההודעה. אם הוא מקבל הודעה ועוד בתים ששייכים להודעה הבאה, הוא פשוט זורק אותם, ומכאן יכנס למעגל אינסופי של שברי הודעות בגלל אותה הנחה.
אחדד: לטענת הלקוח אף על פי ש-TCP לא מיועד לשליחת הודעות אלא לשליחת בתים, הוא מניח שאני שולח כל פעם הודעה אחת בלבד ועל זה הוא מסתמך.השורה התחתונה שלי היא שאני אמור לעבוד בצורה סינכורנית ולשלוח הודעה רק אם סיימתי לשלוח את הקודמת. בעיני מדובר בארכיטקטורה לא נכונה, שתפגע בביצועים ובסקלביליות (יכולים להיות עשרות SocketChannels שכל אחד כותב ללקוח אחר)
וכאן לשאלה הנוספת: האם ההנחה שלי לא נכונה? עד כמה שידוע לי, TCP רשאי לשלוח מידע בכמה "מנות", ואני אמור להסתכל עליו כעל byte-stream ולא להעמיס עליו מבנה של הודעות ששייך לפרוטוקול גבוה יותר.
 

user32

Well-known member
מנהל
לגבי טענת הלקוח שלך

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

לגבי מימוש פרוטוקול "גבוה" יותר על TCP: זה לא מה שכל הפרוטוקולים עושים? בסוף הTCP אכן שולח חבילות בייטים וברמת הapplication של ה7 שכבות הופכים את הבייטים למידע לפי הפרוטוקול הרצוי. אמרת שאתה אוסף בתים ובסוף הופך אותם להודעה שאתה רוצה וזה לדעתי איך שדברים אמורים לעבוד.
 

ilgnd

New member
אין לי מספיק היכרות עם פרוטוקולים מעל TCP:

לדוגמה האם HTTP עובד בצורה דומה של איסוף בתים והפיכה להודעה, או שהוא מניח הנחה דומה לזו של הלקוח?
&nbsp
אני מסכים איתכם שבסופו של דבר צריכה להיות הסכמה על הפרוטוקול, שכנראה לא הוגדרה היטב.
שאלה נוספת, היא האם בעיניכם (תחת ההנחה שלי), העבודה שלי הייתה נכונה בסך הכל?
 

user32

Well-known member
מנהל
התשובה היא כן, וראה תשובתו של ויני למטה

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

מימוש פרוטוקול ותקשורת כמו שתיארת הוא דבר מקובל אם זה מה שאתה שואל.
 

vinney

Well-known member
אתןם צריכים להסכים על הפרוטוקול

"הוא מניח שאני שולח כל פעם הודעה אחת בלבד " - זה בדיוק הפרוטוקול. אז אתם לא משתמשים בHTTP אלא המצאתם משהו משלכם - זה עדיין פרוטוקול. צריך להגדיר אותו. הגדרתם את מבנה ההודעה - סבבה, אבל איך זה עובר בתקשורת?
&nbsp
ברמה העקרונית אתה צודק - הTCP מעביר bytestream ולא אמור להכיר את התוכן או את השימוש האפליקטיבי של הביתים. בהחלט אפשר להעביר סוף הודעה A ותחילת הודעה B בתוך פקטת TCP אחת ולתת לפרוטוקול מעל לסדר אותן - בדיוק כפי שציפית. אבל מצד שני - הפרוטוקול מעל זה הפרוטוקול שאתם צריכים להסכים עליו. ואם אתם מסכימים שבפקטת TCP אחת יש הודעה שלמה או חלקית אחת (ז"א לא יכול להיות שיהיו חלקי שתי הודעות שונות באותה הפקטה) - אז אתה צריך לוודא שזה אכן מתקיים.
 

ilgnd

New member
העניין הוא שההסכמה הזו נפלה בין הכסאות

לא נאמר בשום מקום "פקטה לא מעבירה מידע משתי הודעות" ולא נאמר בשום מקום "פקטה עשויה להעביר מידע משתי הודעות".
אגב, כן נכתב שלאחר הודעה שבורה יש לקרוא את הבתים אחד אחד עד שמגיעים ל - magic.
&nbsp
השאלה היא האם אין פה abuse מסויים בכל שהוא הניח משהו על TCP, שלא אמורים להניח?
 

vinney

Well-known member
הוא לא הניח שום דבר לגבי הTCP

הוא הניח משהו לגבי הפרוטוקול בינכם, שכאמור לא היה מוגדר היטב.
&nbsp
עכשיו זה שאלה למי יותר פשוט להבטיח מה. האם אתה יכול להבטיח שפקטת TCP תכלול רק הודעה אחת שלכם ביתר קלות מאשר הוא יכול לחבר חלקי הודעות בין פקטות? אם כן - תשנה בצד שלך. אם לא - שהוא ישנה.
&nbsp
ממה שתיארת, נראה לי שיותר פשוט שהשינוי יהיה בצד שלך. גם יותר בטוח בטווח הרחוק.
 

ilgnd

New member
לשנות אצלי יהיה הרבה יותר פשוט, אבל למה שזה יהיה יותר בטוח?

קריאת ההודעות מהצד שלי מתבססת על אותה הנחה: שאי אפשר להניח שום דבר.
אני חושב שאצלי המצב הרבה יותר בטוח.
 

user32

Well-known member
מנהל
לא הבנתי את החיבור בין הכותרת להודעה

אז אתה טוען שאצלך יותר בטוח או פחות?

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

ilgnd

New member
אני גם שולח וגם מקבל

הטענה שלי היא שהצד שלי יותר בטוח, וההכוונה בתגובה לvinney היא שגם אם אני אשנה בצד שלי, אני לא רואה איך אני אגיע למצב יותר בטוח ממה שיש עכשיו.
&nbsp
כשאני מקבל, כאמור אני לא מניח שום הנחות ואני יודע לקבל כל הודעה.
כשאני שולח, אני מניח שהצד השני מניח אותן הנחות שלי וגם יודע לקבל את ההודעות בכל צורה.
 

vinney

Well-known member
אין שום בעיה עם זה

כל עוד שני הצדדים מסונכרנים. אבל זה לא המצב פה. הצד השני הניח הנחות אחרות ממך.
&nbsp
אני, אגב, מסכים עם אחרים לגבי המצאת הגלגל. לא צריך להמציא את הגלגל, יש מספיק גלגלים קיימים לבחור מהם. אבל אם כבר הלכתם בדרך הזאת - תלכו עד הסוף ותגדירו את הפרוטוקול באופן מלא.
 

vinney

Well-known member
יש להנחה הזאת כמה יתרונות וכמה חסרונות

יתרונות:
&nbsp
קל יותר להתאפס. לזה גם התכוונתי כשאמרתי שזה יותר בטוח. אם מובטח לך שבכל פקטה יש רק הודעה אחת, אז הודעה תתחיל תמיד בתחילת הפקטה. משהו השתמש ויצאת מסנכרון? תקרא פקטות עד שתמצא פקטה עם המג'יק בהתחלה - והתאפסת. בלי ההנחה הזאת אתה עלול למצוא את עצמך במצב בדיוק כמו שתיארת: משהו השתבש וכל התקשורת נפלה כי הקורא לא מצליח להתאפס.
&nbsp
קל יותר לממש, פחות מקרי קצה לטפל בהם, וכו'. הרי הלקוח שלך לא הניח את ההנחה הזאת סתם

&nbsp
החסרונות:
&nbsp
בזבזני משהו. אתה יכול לדחוס יותר מידע ברוחב הפס שלך אם תנצל את הTCP ביעילות, וההנחה הזאת פוגעת ביעילות ניצול התווך.
&nbsp
הדבר הכי חשוב זה שפרוטוקול צריך להיות מוגדר היטב, תכל'ס גם היתרונות וגם החסרונות יחסית זניחים, אבל חשוב מאוד ששני הצדדים ידברו באותה השפה. אם יותר קל גם מעשית וגם פוליטית לעשות את השינוי אצלך ולהשאיר את הלקוח כמו שהוא - תעשה את השינוי אצלך. זה לא משהו שהייתי יוצא למלחמה עליו, אלא אם כן אתם סופרים ביטים בbandwidth (ולא נשמע שזה המצב).
 

הפרבולה

New member
לדעתי כדאי לעשות מאמץ ולשכנע את הלקוח לשנות

לפרוטוקול שלך בגלל הביצועים.
אם מדובר בהודעות גדולות אז ההשפעה על הביצועים קטנה יותר כי אחת להרבה פקטות של TCP יש הודעה חדשה ואז פחות משמעותי מבחינת ביצועים אם היא תשלח בתחילת הפקטה הבאה במקום להשלח כבר בפקטה הנוכחית יחד עם סוף ההודעה הקודמת.

אבל אם מדובר בהודעות באורך קטן אז זה די בזבזני לשלוח כל הודעה ב TCP חדש במקום לדחוס כמה הודעות לפקט אחד של TCP ( יש תקורה של המעטפת וההמתנה לאישור ).
 
שימוש נכון ב-TCP

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

ilgnd

New member
לפעמים לא צריך סיבה טובה לא להמציא את הגלגל

במקרה שלנו יש סיבה לא טובה להמציא את הגלגל: זו התשתית של הלקוח ב-10 השנים האחרונות והוא לא מוכן להחליף.
אגב, אני התעקשתי על הנושא של magic ושל אורך ההודעה. עד אז אפילו זה לא היה קיים.
 

יבגניי34

New member
מה לא יעשה מתכנת כדי לייצר לעצמו עוד עבודה

"אנחנו לא רוצים פרוטוקול highi level מעל tcp. עדיף להתווכח על הפרוטוקול שהמצאנו הרגע ולנסות לפתור את כל הבעיות שהפרוטוקולים הקיימים פתרו לפני 50 שנה"

קיצר תעבדו עם http או mqtt
 

Rשף

New member
בדיוק

לפני עשרים שנה בערך ייצרתי לעצמי בעייה דומה ופתרונות דומים למה שהוצע (אגב TCP יכול על דעת עצמו להעביר בתים מפה לשם ולסמוך על המקבל לחתוך במקום הנכון) בשנים האחרונות השתמשתי ב HTTP / rest והבעיות פשוט לא קיימות יותר, וזמן הפיתוח קצר משמעותית
נכון שקיים overhead מסויים אבל עבור הגדלים המתוארים הוא די זניח.
 

user32

Well-known member
מנהל
לא מתאים לכל דבר

היה לי פרוייקט שבמסגרתו מתגים היו משדרים הודעות, בדרך כלל של 1-4 בתים. לממש HTTP קליינט על שבב ללא מערכת הפעלה זה over head מיותר והמתכנת שתכנת את הכרטיסים בכלל לא היה מבין מה אני רוצה ממנו.
 
למעלה