מתחילים בתכנות
תחילה, נוריד את קובצי תוכנת הצ'אט, על מנת שנוכל לעשות בקוד המקור שלה שימוש במקביל לתכנות שלנו. קובצי התוכנה זמינים להורדה בקישור הבא.
חלצו את הקבצים מתוך ה-ZIP ופתחו את הקובץ ChatSolution.sln. לאחר מכן לחצו על הפרוייקט ChatProtocol ולאחר מכן על ChatProtocol.cs.
כפי שכבר צויין, בפרוטוקול ה-TCP קיים שרת ולקוח, והלקוח מתחבר לשרת דרך פורט מסויים אשר נקבע. עלינו לכתוב שלושה פרוייקטים שונים: תוכנת השרת, תוכנת הלקוח ופרוטוקול ההודעות (ספריית DLL). פרוטוקול ההודעות יופיעו ב-DLL, כך ששתי התוכנות האחרות יוכלו להשתמש בפונקציות הקיימות בו בצורה משותפת, ללא צורך בכתיבת הקוד מחדש. ניתן ללמוד על יצירת DLL באמצעות C# בקישור-הבא.
ראשית, נתחיל עם כתיבת ספריית פרוטוקול ההודעות. לספרייה זו נקרא ""ChatProtocol ובתוך ספרייה זו ניצור מחלקה בשם ChatMessage"". מחלקה זו תתאר הודעה המועברת בין שני הצדדים (חבילת המידע). מחלקה זו תכיל מספר תכונות (משתנים):
NickName – הכינוי של מי ששלח את ההודעה (מחרוזת).
Message – תוכן ההודעה (מחרוזת).
BytesCount – מספר הבייטים של כל ההודעה (מספר שלם).
IsMessageValid – משתנה בוליאני של אמת ושקר עבור אם ההודעה תקפה, כלומר שהבייטים שניתנו אכן מכילים כינוי ותוכן.
Message – תוכן ההודעה (מחרוזת).
BytesCount – מספר הבייטים של כל ההודעה (מספר שלם).
IsMessageValid – משתנה בוליאני של אמת ושקר עבור אם ההודעה תקפה, כלומר שהבייטים שניתנו אכן מכילים כינוי ותוכן.
מחלקה זו מכילה את הקבוע הבא
MinimumBytes – מספר הבייטים שהודעה ריקה מכילה. הודעה ריקה תמיד תכיל 8 בייטים מפני שהכינוי והתוכן ריקים. התכונה IsMessageValid"" תהיה אמת רק כאשר מספר הבייטים יהיה גדול מקבוע זה – כך אנו יודעים שההודעה ששלחנו אכן תקפה ולא ריקה מתוכן.
בנוסף, המחלקה מכילה את המשתנה הסטטי הבא:
ProtocolEncoding – הקידוד שבו נשתמש בכדי להמיר מחרוזות לבייטים. קידוד היא צורה מסויימת שבה נרצה לתאר תווים במחרוזת. אנו משתמשים בקידוד מסוג ASCII, לכן ערכו של משתנה זה יהיה שווה ל-System.Text.ASCIIEncoding.ASCII.
יחד עם אלו, המחלקה מכילה את הפונקציות הבאות:
GetBytes – פונקציה זו מחזירה לנו מערך של בייטים אשר נרצה לשלוח לצד השני. מערך הבייטים יתאר את אובייקט ה-ChatMessage. אנו מעבירים בין שני הצדדים זרם של בייטים ולכן עלינו להמיר את ההודעה למערך של בייטים.
FromBytes – פונקציה סטטית, אשר הופכת מערך של בייטים לאובייקט ChatMessage. היא מקבלת מערך של בייטים (data) ופרמטר המורה לה מהיכן להתחיל לקרוא את האובייקט (Offset).
FromBytes – פונקציה סטטית, אשר הופכת מערך של בייטים לאובייקט ChatMessage. היא מקבלת מערך של בייטים (data) ופרמטר המורה לה מהיכן להתחיל לקרוא את האובייקט (Offset).
הפונקציה GetBytes מחזירה מערך של בייטים מאובייקט ChatMessage, בעוד הפונקציה FromBytes מחזירה אובייקט ChatMessage מתוך מערך של בייטים. במילים אחרות, שתי הפונקציות עושות פעולה הפוכה אחת מהשניה: המרה מפורמט האחד אל משנהו. כפי שהזכרנו, במחלקה זו שני הצדדים הולכים להשתמש, לכן עלינו להפוך אותה לקובץ DLL.
כעת נעבור לתוכנות צד השרת והלקוח. תוכנת צד השרת והלקוח יראו אותו הדבר, כאשר ההבדל היחיד הוא שתוכנת הלקוח תכיל חלון נוסף אשר יבקש מן המשתמש להקליד את הכינוי וכתובת הIP- של השרת המרוחק.
ריבוי הליכים
בשני הצדדים אנו נעשה שימוש בשיטה הידועה בשם "Multi-threading". על מנת להסביר שיטה זו נשתמש בדוגמה הבאה: כאשר הקוד שלנו נתקע בלולאה מסויימת, ממשק התוכנה שלנו לא מגיב. למה בעצם זה קורה? כאשר התוכנה שלנו נתקעת על שורת קוד מסויימת, או בתוך לולאה, ממשק התוכנה לא יכול להגיב לשום דבר, זאת מפני שאותו ההליך (ההליך הראשי של התוכנה) האחראי לקבל קלט מן המשתמש לא פנוי, שכן הוא עסוק בלעבד את הקוד הקודם שהמשתמש נתן לו.
אז איך בעצם נגרום לתוכנה שלנו להגיב גם כאשר היא תקועה בתוך לולאה? התשובה לכך היא פשוטה: ריבוי הליכים (Multi-threading). כמו שב-Windows יש לנו מספר תהליכים העובדים בנפרד, גם אם האחד נתקע השני ימשיך לעבוד. כך יש לנו גם הליכים (Threads), כאשר בתוך תהליך (Process) יש לנו מספר הליכים. ההליך העיקרי הינו הליך ה-GUI (ממשק המשתמש). כאשר אנו כותבים קוד אנו כותבים אותו ישירות בהליך הראשי של התוכנה, אלא אם כן ציינו אחרת. כל ההליכים יכלים לגשת לזיכרון המשותף של התוכנה (למשתנים הגלובליים של התוכנה), בניגוד לתהליכים שלא יכלים לשתף זיכרון אחד עם השני.
בעת שימוש בשיטה זו, עדיין ישנה נקודה בעייתית: הליך שהוא אינו ההליך הראשי לא יכול לבצע שינויים בממשק המשתמש. במילים אחרות, הוא אינו מסוגל לדוגמה לשנות את רקע החלון, לשנות טקסט בתוך תיבת הטקסט או לצייר בתוך הממשק. מה עלינו לעשות במצב כזה? קיימות מספר שיטות לבצע זאת, אך במדריך זה נסביר רק על אחת מהן. להלן השיטה הנפוצה: ניצור מתודה אשר אחראית על עדכון ממשק התוכנה, וכאשר אחד מן ההליכים קורא למתודה זו, נבדוק בתוך המתודה אם ההליך אשר קרא לה הוא ההליך הראשי. במידה וכן, נקרא לאותה המתודה שוב כשהפעם נגיד להליך הראשי לבצע אותה.
אז למה בעצם אנחנו צריכים ריבוי תהליכים בתוכנות הצ'אט שלנו? התשובה לכך היא שהתוכנה שלנו תמיד תבדוק אם התקבל מידע חדש מן הצד שני. פעולה זו מתבצעת בתוך לולאה הנמשכת כל עוד קיימת תקשורת בין שני הצדדים. כל התקשורת שלנו תתבצע בהליך נפרד, והליך זה יעדכן את הממשק ברגע שהתקבלה הודעה חדשה.