צד הלקוח
צד הלקוח הצד הפשוט יותר: הוא אינו מאזין ללקוחות אלא רק מתחבר לשרת. שני הצדדים דומים אחד אל השני בדברים מסויימים. קודם כל בנינו את צד השרת, שכפלנו אותו, ערכנו אותו ויצרנו את צד הלקוח. לכן שני הצדדים דומים מאוד בעיצובים ובחלק מן הקוד שלהם. צד הלקוח מכיל את המשתנים הבאים:
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 של הלקוח. לאחר מכן התוכנה תיסגר.