צד הלקוח
צד הלקוח הצד הפשוט יותר: הוא אינו מאזין ללקוחות אלא רק מתחבר לשרת. שני הצדדים דומים אחד אל השני בדברים מסויימים. קודם כל בנינו את צד השרת, שכפלנו אותו, ערכנו אותו ויצרנו את צד הלקוח. לכן שני הצדדים דומים מאוד בעיצובים ובחלק מן הקוד שלהם. צד הלקוח מכיל את המשתנים הבאים:
bool connected = false; //True if the TCP client is connected
TcpClient tcpClient = new TcpClient(); //The TCP client used to connect the host
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed">string nickName = "Client";
connected – משתנה בוליאני. מכיל אמת כאשר הלקוח מחובר לשרת.
tcpClient – אובייקט זה מאפשר לנו להתחבר לשרת.
nickname – הכינוי שבו נשתמש.
כאשר התוכנה שלנו עולה, הקוד הבא יתבצע:
/* Starts listening on a specific socket.
* Multi-threading is used to keep the UI responsive.
*/
private void MainForm_Load(object sender, EventArgs e)
{
8.0pt;">AddToOutput("Client loaded.");direction:ltr;unicode-bidi:embed"> 8.0pt;color:#2B91AF">ConnectionDialog connectionDiag = new ConnectionDialog(); left;text-indent:36.0pt;line-height:normal;
text-autospace:none;direction:ltr;unicode-bidi:embed"> margin-left:36.0pt;margin-bottom:.0001pt;text-align:left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed">if (connectionDiag.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
nickName = connectionDiag.Nick_TextBox.Text;
string hostName = connectionDiag.ServerIP_TextBox.Text;
margin-left:36.0pt;margin-bottom:.0001pt;text-align:left;text-indent:36.0pt;line-height:normal;text-autospace:none;direction:
ltr;unicode-bidi:embed"> margin-left:36.0pt;margin-bottom:.0001pt;text-align:left;text-indent:36.0pt;
line-height:normal;text-autospace:none;direction:
ltr;unicode-bidi:embed">{
tcpClient.Connect(hostName, 2021); //Try to connect
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> //Succesfully connected!
connected = true;
new Thread(new ThreadStart(Client_Thread)).Start(); //Starts listening on a seperate thread
AddToOutput("Succesfully connected!");
}
catch (Exception ex)
{
//Connection failed, close the client
MessageBox.Show("Failed to connect host, closing client.", "Failed to connect", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
this.Close();
}
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> 8.0pt;">} 8.0pt;color:blue">else color:blue">this.Close(); left;text-indent:36.0pt;line-height:normal;
text-autospace:none;direction:ltr;unicode-bidi:embed">
כאשר התוכנה עולה היא מוציאה אל המשתמש דיאלוג התחברות, אשר בו הוא מקליד משתמש וכתובת IP של שרת מרוחק:
אם המשתמש מאשר את החלון, כינוי הלקוח ישתנה לכינוי שהמשתמש בחר והלקוח מנסה להתחבר לשרת המרוחק, ואם ההתחברות נכשלה, מופיעה הודעה האומרת שההתחברות נכשלה והתוכנה נסגרת. כאשר המשתמש אישר את החלון התוכנה מבצעת את הקוד הבא:
tcpClient.Connect(hostName, 2021); //Try to connect
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed">//Succesfully connected!
connected = true;
new Thread(new ThreadStart(Client_Thread)).Start(); //Starts listening on a seperate thread
AddToOutput("Succesfully connected!");
התוכנה מתחברת לשרת המרוחק, מעדכנת את משתנה ה-connected לאמת (ההתחברות הצליחה) ומתחילה הליך אשר יאזין להודעות השרת. לבסוף, התוכנה מעדכנת את ממשק המשתמש שההתחברות הצליחה. ברגע שמתרחשת שגיאה אנו יודעים כי ההתחברות נכשלה, ולכן נתפוס את השגיאה באמצעות catch, נציג הודעה על כך שההתחברות נכשלה ונצא מהתוכנה. ההליך אשר מאזין להודעות השרת מכיל את הקוד הבא:
/* This is where all of the background work is done, allows the UI stay
* responsive and not get stuck in a specific line or a loop.
* With network enabled application multi-threading is the basic and most
* important part of the application.
*/
private void Client_Thread()
{
8.0pt;color:blue">while (connected){
NetworkStream netStream = tcpClient.GetStream();
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> byte[] receivedBytes = new byte[tcpClient.ReceiveBufferSize]; left;line-height:normal;text-autospace:none;
direction:ltr;unicode-bidi:embed"> if (netStream.Read(receivedBytes, 0, receivedBytes.Length) != 0)
{
//Starts analyzing the data received
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> for (int i = 0; i <= receivedBytes.Length; i++)
{
if (i + ChatMessage.MinimumBytes >= receivedBytes.Length) //Message is not long enough to be read
break;
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> ChatMessage cMessage = ChatMessage.FromBytes(receivedBytes, i); //Converts the byte array into a ChatMessage object again left;line-height:normal;text-autospace:none;
direction:ltr;unicode-bidi:embed"> if (cMessage.IsMessageValid)
{
AddToOutput(cMessage.NickName + ": " + cMessage.Message); //Updates the UI with the message received
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> //Increase the offset, skip the message bytes we just analyzed
i += cMessage.BytesCount – 1;
}
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed">
}
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> }
else //Client got disconnected
break;
margin-left:36.0pt;margin-bottom:.0001pt;text-align:left;text-indent:36.0pt;line-height:normal;text-autospace:none;direction:
ltr;unicode-bidi:embed">} color:blue">catch (Exception ex)
{
//An error had occured, probably TcpClient stopped the connection using the Close() method
connected = false;
}
left;text-indent:36.0pt;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> left;line-height:normal;text-autospace:none;
direction:ltr;unicode-bidi:embed">
//We are disconnected from host when we get to this line
8.0pt;color:#2B91AF">MessageBox.Show("Server shutdown, client has been disconnected", "Server shutdown", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
AddToOutput("Client disconnected!");
}
מפני שכבר עברנו על קוד צד השרת וכיצד הוא מנתח הודעות שהתקבלו, נעבור רק על קטעים ספציפיים. הליך זה ירוץ כל עוד אנו מחוברים לשרת (המשתנה connected הוא אמת). בהליך זה נשתמש בפונקציה netStream.Read בכדי לקבל את מערך הבייטים שהשרת שלח. אם הפונקציה מחזירה ערך אפסי, זה אומר שמספר הבייטים שקיבלנו אפסי גם כן, מכאן נובע שהתנתקנו מהשרת, ולכן נודיע למשתמש על ההתנתקות. אם מספר הבייטים שקיבלנו גדול מ-0, נתחיל לנתח את המידע שהתקבל בדיוק כפי שעשינו בצד השרת. כאשר נרצה לשלוח הודעה לשרת נגיע לקוד הבא:
private void Send_Button_Click(object sender, EventArgs e)
{
8.0pt;color:#2B91AF">ChatMessage cMessage = new ChatMessage(nickName, Input_TextBox.Text);tcpClient.GetStream().Write(cMessage.GetBytes(), 0, cMessage.BytesCount); //Turns the ChatMessage object into a byte array and sends it out to the remote client
AddToOutput(cMessage.NickName + ": " + cMessage.Message); //Updating the UI
8.0pt;">}
8.0pt;color:blue">else
AddToOutput("Message could not be sent. no connection to server!");
left;line-height:normal;text-autospace:none;direction:ltr;unicode-bidi:embed"> 8.0pt;color:green">//Disable the send button and clear the textbox 8.0pt;">Input_TextBox.Clear(); 8.0pt;">Send_Button.Enabled = false;
}
בדיוק כמו בצד השרת, ניצור אובייקט ChatMessage עם הכינוי ותוכן ההודעה, ואת תוכן ההודעה נשאב מתיבת הטקסט. אם אנו מחוברים, לשרת נשלח אליו את ההודעה על ידי הפיכת האובייקט למערך בייטים וכתיבת הבייטים ל-NetworkStream. לאחר מכן נעדכן את ממשק המשתמש עם ההודעה שנשלחה. כאשר נסגור את צד הלקוח נבצע את הקוד הבא:
/* Stops listening for any clients and closes the form
* Always close any open sockets when application is about to close.
*/
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
8.0pt;">connected = false; //Allows the client thread to end}
את משתנה ה-connected נשנה לשקר (וכך נגרום להליך קבלת ההודעות להיסגר) ונסגור את ה-Socket של הלקוח. לאחר מכן התוכנה תיסגר.
הקישור לזיכרון RAM לא נכון
זה לא נכון לקשר מכאן לזכרון RAM כי זה לא הנושא.
מדובר כאן על זכרון של תהליכים, לכל תהליך מוקצה אזור זכרון ואת אותו זכרון הוא לא יכול לחלוק עם תהליכים אחרים.
אין שום טעם לקשר את זה לזכרון RAM למרות שהזכרון הזה נמצא שם.
חוץ מזה מדריך מעולה!!
אתם אתר נפלא שתמיד מחכים אותי.
כתבה מעולה…
תודה רבה קודם כל, אפשר ללמוד המון.
אפשר לשפר בכך שתפרט מעט יותר על כל מיני מושגים שמבחינתך הם בסיסיים אך מבחינת אנשים שאתה בא ללמד הם ממש לא.
בכל אופן ישר כח ותמשיך כך.מצוין!
למה בדוד-מת?
הוא מת כבר לפני שנים.
לא רלוונטי
ב-.NET יש תשתית WCF שהיא תשתית ה-communication של ה-Framework.
לעבוד ישירות מול המחלקות המתוארות זה חסר טעם ולא נכון מבחינת ארכיטקטורה.
התשתית שהיא עשירה לאין ערוך ממה שתואר כאן, מאפשרת העברת נתונים באופן נוח ו"שקוף" ללקוחות המאזינים ולקבוע את צורת התקשורת (Named Pipes, וכיו"ב) בקונפיגרציה.
היא כמובן גם מאפשרת להתמודד עם תרחישים פשוטים ועד תרחישים מסובכים הרבה יותר בהם לדוגמה הנתונים נאספים ממספר מערכות בתהליך טרנזאקטיבי או תרחישים אחרים.
תודה רבה
מדריך מעולה ,נפל עלי כיאלו הזמנתי אותו אישית
בינוני.
מצטער שזה יוצא כ"כ תקיף, אבל הקוד שנכתב הוא ברמה בינונית מאוד.
כל העטיפה, השימוש בThreadים בצורה לא נכונה, עבודה מול UI בצורה לא נכונה, חוסר עבודה בקונבנציות…
ד"א, לבחור שכתב שכדאי לכתוב את זה בWCF – אתה טועה. WCF מתאים לשירותים (SOA במיוחד) ולא לתקשורת כזו. זה סתם overhead לא הכרחי.
סוגרים על פרוטוקול ושולחים בraw sockets כמו שהבחור עשה… אלא אם יש לך שרת מרכזי שמולו אתה עובד (כמו שlive messenger עובד) ואז הארכיטקטורה שלך צריכה להשתנות ואולי WCF יותר יתאים.
בכל מקרה- בחור, רצון יש אבל ידע לוקה בחסר.
6 -קצת צניעות לא תזיק וגם לא ידע
WCF לא קשורה בהכרח ל- SOA למרות שהיא דרך אופרטיבית אידיאלית להשיג SOA.
WCF זו תשתית התקשורת של ה-FRAMEWORK והיא מטפלת בהעברת נתונים באשר היא.
מבחינת ארכיטקטורה הרצון שלך הוא להגדיר מה אתה רוצה להעביר בצורה פשוטה בקוד (גישת AOP שבה עושה שימוש WCF)ולקבוע בקונפיגרציה האם אתה רוצה העברה פשוטה כמו NAMED PIPES או TCP או משהו כמו HTTP או כל פרוטוקול אחר בהתאם לצורך ול-HOST.
אתה יכול לעבור מצורת העברה אחת לאחרת ללא שינוי קוד.
לעיתים בכלל לא מדובר על העברת נתונים בין מכונות אלא על העברה פשוטה בין אפליקציות. זה בכלל לא משנה.
תכנות נכון הוא להבטיח את המחר, את היכולת לשנות ולגדול בלי שכתובים מהותיים.
במקרה שלך, אתה מציע לכתוב קוד "בסיסי" (שהוא אגב הרבה יותר מורכב מ-WCF) ומחר כשהצורך משתנה או גדל להחליף את כולו.
אני מצטער, אבל גם לך יש קצת שעורי בית להשלים.
מיושן – יש היום WCF
בשביל ליצור תקשורות ב
.NET
לא משתשמים בדרך הפרמטיבית הזאת עם על הכבוד , יש טכנולוגיה שנקראת
WCF ( שמחליפה את הדרך שאתה מציג)
שאתה כותב קוד קצר ומאחורי הקלעים עולם ומלאו(שמכיל את הדרך הזאת) כולל אבטחה ( RSA גם). ניתן לבחור בשלל אפשריות מתוך ה
WIZARD
אבטחה – סוג תקשורות TCP UDP WSHTTPS ועוד הרבה .
מטרת המדריך
שלום, מטרת המדריך אינה להציג למשתמשים את הדרך הפשוטה והקלה ביותר ליצירת תקשורת בסביבה זו. המטרה היא לא להעביר את המידע במספר השורות הקצר ביותר או בדרך הקלה ביותר. מטרת המדריך היא ללמד את המשתמשים ליצור תקשורת ברמה נמוכה יותר תוך כדי שהם לומדים כיצד חבילות המידע נשלחות ומה קורה בעצם ברקע. במדריך המשתמשים לומדים כיצד להשתמש ב-Multi-Threading ובקבצי DLL. אומנם הדרך שבה המידע הועבר בין ההליכים אינה הדרך הטובה ביותר ולא הכי נכונה אך היא המובנת ביותר. כמובן ניתן היה למשל להשתמש במחלקת ה-SynchronizationContext בכדי להעביר מידע בין ההליכים בצורה נכונה יותר.
למי שמגיב נגד, ולכותב המדריך
אני לא ראיתי אף מדריך שמסביר זאת
מי שחושב שהמדריך לא טוב, שיכתוב מדריך בבקשה.
אני כרגע לומד את זה ואני אומר תודה רבה למי שכתב את המדריך.
אני מקווה שיעזור לי.