פרק 14:

 

 

Networking

 

תקשורת

 

 

·           הקדמה

·           פרוטוקול הTCP\IP-

·           פרוטוקול הUDP-

·           פרוטוקול SMTP (שליחת email)


הקדמה

 

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

התקשורת בין שתי תכניות (שבשני מחשבים) נעשית ב-JAVA בדרך כלל עפ"י הפרוטוקולים TCP\IP (הפרוטוקולים שמשמשים לתקשורת ברשת האינטרנט). פרוטוקול ה-TCP\IP הוא שם כולל לפרוטוקולים שונים, שמשמשים להעברת קבצים, התחברות למחשבים מרחוק ולפעולות תקשורת אחרות.

 

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

 

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

 

החלק השני עוסק בפרוטוקול התקשורת UDP. בתחילה מוסבר הפרוטוקול, ולאחר מכן מוצגות מספר טכניקות מקובלות לשימוש בו.

 

החלק השלישי מציג את הפרוטוקול שמקובל בשליחת email, הפרוטוקול SMTP ומדגים כיצד להשתמש בו.


פרוטוקול התקשורת TCP\IP -

 

מבוא לTCP\IP-

 

התקשורת בין שתי תכניות (שבשני מחשבים) נעשית ב-JAVA עפ"י הפרוטוקולים TCP\IP.

TCP = Transmission Control Protocol

IP = Internet Protocol

 

תפקידו העיקרי של פרוטוקול ה-TCP\IP הוא לשמש כפרוטוקול התקשורת בין שתי נקודות קצה (שתי תכניות בשני מחשבים). התקשורת במקרה זה מתבצעת באמצעות שני streams: stream אחד שדרכו מועברת האינפורמציה מנקודה אחת לנקודה השניה, ו-stream שני שדרכו מועברת האינפורמציה מהנקודה השניה לנקודה הראשונה. בכל אחד משתי התכניות שבשני המחשבים ניתן לקרוא/לכתוב אינפורמציה מ/אל ה-stream המתאים. אם אין שום תקלה אז המידע שנשלח מנקודה לנקודה זהה בתוכנו ובסדר הפנימי שלו למידע שמגיע.

 

בתקשורת בין נקודה לנקודה עפ"י הפרוטוקול TCP/IP  צריך שתהיה כתובת ייחודית לכל נקודה ברשת (בדומה למספר טלפון). כל כתובת (בגרסה הנוכחית של TCP\IP) היא מספר שלם בגודל של 32 ביט, ונהוג לייצגו באמצעות 4 מספרים, שכל אחד מהם בטווח:0-255.

לדוגמא:

                                                                                                                                                                                                                                                                                                                                                                       

12                     . 254                 . 252                 . 5

 

ובתרגום לשפת מכונה:

00001100          . 01111110        . 01111100        . 000000101

 

כדי שהתקשורת תהיה יותר נוחה, פותחו מספר מכניזמים שיאפשרו שימוש בשם מילולי במקום בסדרת המספרים.

לדוגמא:

www.zindell.com

 

ברשתות תקשורת פרטיות ניתן להשתמש במגוון גישות שונות לביצוע התרגום של השם המילולי אל סדרת המספרים. ב-web, נעשה שימוש רק בגישה אחת, בגישת ה-DNS:  Domain Name System.  גישה זו מאפשרת קישור בין מחשבים באמצעות שמות טקסטואליים במקום סדרות של מספרים. 

כדי לאפשר למנהלי התקשורת מידה מסוימת של חופשיות במתן השמות שמחליפים את סדרות המספרים נהוגה גישה היררכית.

                                                                                                                                                                                                                    www.zindell.com

 

 

 

                                                                                                                                    שם האיזור הרחב          שם האזור               שם המכונה

                                                                                                                                    יותר במכונה                 הספציפי               שעמה רוצים

                                                                                                                                                                        במכונה שעימו      ליצור את הקשר

                                                                                                                                                                      רוצים לייצור

                                                                                    את הקשר

 

כל זאת, בדומה לכתובת של בית בעיר מסויימת:

#31, Hazait st. Karmei-Yosef                                                                                

המקביל למספר הבית (31#) הוא : www

המקביל לשם הרחוב (Hazait) הוא : zindell

המקביל לשם העיר (Karmei-Yosef) הוא : com

 

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

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

 

מכאן, שכל קשר בין שני מחשבים עפ"י הפרוטוקול TCP\IP, מאופיין בארבעה משתנים:

       1.         כתובת המחשב שמהווה מקור (סדרה של 4 מספרים)

       2.         כתובת המחשב שמהווה יעד (סדרה של 4 מספרים)

       3.         מספר ה-port במחשב שמהווה מקור לקשר.

       4.         מספר ה-port במחשב שמהווה את היעד בקשר.

 

בדרך כלל, מידיעת מספר ה-port במחשב היעד(server) ניתן לדעת מהו השירות שמתבצע בקשר שבין שני המחשבים. מספרי ה-port מתחת ל-1024 שמורים לשימוש על ידי שירותים מוגדרים מראש (25, למשל, שמור לפעולת SMTP(e-mail)).

 

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

 

מספר רב של מערכות מחשבים שפועלות ברשת האינטרנט משתמשות ב-URL ככתובת של שירות שהן נותנות. URL בתצורתו המקובלת מכיל מידע רב ומפורט: שם המכונה, האיזורים הרחבים והספציפיים במכונה, מספר ה-port, הפרוטוקול והשם ספציפי של קובץ.

 

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

השכבה הראשונה (השכבה הפיזית)

זוהי השכבה הבסיסית ביותר, והיא פועלת ברמה הנמוכה ביותר. היא מטפלת בשליחה ובקבלה הפיזיים של המידע אל/מ כבל התקשורת הפיזי.

שכבה זו איננה חלק רשמי ממערכת ה-TCP\IP, והיא בדרך כלל פועלת באמצעות פרוטוקול אחר שאיננו חלק מפרוטוקול ה-TCP\IP.

שכבה זו שולחת/מקבלת בלוק של bytes (datagram) אל/מ הרשת הפיזית. ה-datagram שנשלח/מתקבל אל/מ רשת התקשורת הפיזית מגיע/מועבר מ/אל השכבה שמעליה (השכבה השניה).

שכבה זו איננה אחראית לבדיקה כי ה-datagram אכן הגיע ליעדו הנכון, וכי אכן הגיע בהצלחה.  באופן דומה, שכבה זו גם לא אחראית לבדוק שה-datageams שהגיע אליה אכן היה צריך להגיע וכי הוא הגיע בשלמותו.  עם זאת, ישנם יישומים רבים (שאינם פועלים עפ"י TCP\IP) שבהם השכבה הראשונה כן בודקת שהמידע שהגיע אכן היה צריך להגיע וכי הוא הגיע בשלמותו. לסיכום, חשוב לזכור, כי פרוטוקול ה-TCP\IP איננו דורש שהשכבה הראשונה תבצע פעולות אלה.

 

השכבה השניה (שכבת ה-IP)

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

פעולות נוספות שבאחריות שכבה זו כוללות בין היתר את תזמונם המדויק של ה-datagrams שנשלחים/מתקבלים ואת ביצוע ההתאמות השונות שיש לבצע בין המחשב לרשת.

 

השכבה השלישית (שכבת ה-TCP ושכבת ה-UDP)

מעל שכבת ה-IP קיימת השכבה השלישית. השכבה השלישית מורכבת ממספר שכבות במקביל. מבין השכבות הרבות שקיימות במקביל בשכבה זו אנו נתמקד בשכבת ה-TCP ובשכבת ה-UDP.

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

באופן דומה, כאשר בלוק של מידע מועבר משכבת ה-IP אל השכבה השלישית, בהתאם למספר ה-port, השכבה השלישית שולחת את המידע שהגיע אל התהליך(התכנית) המתאים.

 

ניתן לדמות את פעולת שלושת השכבות למעטפות שמוכנסות אחת לתוך השניה:

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

באופן דומה, כאשר מגיע מידע ממכונה אחרת, המידע מגיע מכבל התקשורת אל השכבה הראשונה (השכבה הפיזית). המידע מגיע במעטפה, והשכבה הראשונה מעבירה אותה אל השכבה השניה (שכבת ה-IP). השכבה השניה בודקת עפ"י שם המכונה שכתוב על המעטפה כי אכן המעטפה הייתה אמורה להגיע. אם המעטפה אכן הייתה אמורה להגיע אז השכבה השניה פותחת אותה, ומעבירה הלאה אל השכבה השלישית את המעטפה שנמצאת בפנים. השכבה השלישית מעבירה את המידע שהגיע אל ה-port המתאים.

 

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

 

כאשר בלוק של נתונים (segment) נשלח באמצעות שכבת ה-TCP מוצמדים אליו שני מספרים חשובים:  מספר סידורי (sequence number), שתפקידו לאפשר מעקב אחרי סדר הגעתם של ה-segments (הבלוקים) שמגיעים, וכמו כן, מספר נוסף (checksum) שתפקידו כתפקידה של ספרת הביקורת, לאפשר את הבדיקה שהמידע אכן הגיע בשלמותו.

 

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

האחריות על משלוח הודעת האישור להצלחה בקליטת ה-segment נתונה בידי שכבת ה-TCP המתאימה בצד המקבל.

 

 

שרת סדרתי מבוסס TCP\IP

 

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

התהליך דומה מאד לקשר טלפוני:

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

 

לפני שאמשיך, להלן ההסבר למספר מושגים בסיסיים בנושא הנדון:

stream הוא זרם של בתים (כפי שאתה כבר יודע) שיכול לייצג מידע שמגיע או מידע שנשלח.

 

socket הוא השם שניתן לכל אחת משתי נקודות הקצה בקשר שנוצר. כל נקודת קצה משמשת תהליך שמתבצע במחשב מסוים. 

 

socket programming הוא השם הכללי שניתן לתכניות שמתקשרות ביניהן, כאשר האחת פועלת בתור שרת והשניה בתור הלקוח.

 

כל socket יכול להחזיק בשני streams: stream אחד שמייצג את האינפורמציה שנשלחת ו-stream שני שמייצג את האינפורמציה שמגיעה. כל אחד מה-streams מקושר ל-stream המתאים ב-socket השני.

 

המחלקה Socket

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

 

public Socket(String host, int port) throws UnknownHostException, IOException

constructor ליצירת אובייקט חדש מטיפוס Socket שיוכל לייצג נקודת קצה בתקשורת בין שני תהליכים בשני מחשבים. האובייקט החדש שנוצר מקושר למכונה ששמה נשלח כארגומנט ראשון, ב-port שמספרו נשלח כארגומנט שני.

אם יצירת אובייקט ה-Socket  נעשית למכונה(מחשב מרוחק) שה- security manager לא מאפשר לייצור עימו את ההתחברות, אז מתוך ה-constructor ייזרק exception security. בזמן יצירת האובייקט עלולה להיווצר בעיה שקשורה במרכיבי הקלט/פלט של המחשב ולכן מתוך constructor זה עלול גם להיזרק IOException.

 

public InputStream getInputStream() throws IOException

מתודה זו מחזירה reference לאובייקט מטיפוס InputStream אשר מקושר אל אובייקט ה-Socket. באמצעות אובייקט ה-InputStream אשר ה-reference שלו הוחזר על ידי המתודה יבוצעו פעולות לקריאת bytes שמגיעים אל ה-socket.

אם בעת יצירת ה-stream מתרחשת תקלה אז המתודה זורקת IOException.

 

public OutputStream getOutputStream() throws IOException

מתודה זו מחזירה reference לאובייקט מטיפוס OutputStream אשר מקושר אל אובייקט ה-Socket. באמצעות אובייקט ה-OutputStream אשר ה-reference שלו הוחזר על ידי המתודה יבוצעו פעולות לכתיבת bytes כדי שיישלחו אל ה- socket שבקצה השני של קו התקשורת.

 

public void close() throws IOException

מתודה לסגירת ה- socket. אם מתרחשות תקלות בעת סגירת ה-socket אז המתודה זורקת IOException.

 

המחלקה ServerSocket

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

 

public ServerSocket(int port) throws IOException

constructor ליצירת אובייקט חדש מטיפוס ServerSocket אשר יאזין ל-port שמספרו נשלח אליו כארגומנט. אובייקט ה-ServerSocket יאזין ל-port האמור, ויחכה לקבלת בקשה ליצירת קשר.


public Socket accept() throws IOException, SecurityException

מתודה שמרגע שהופעלה ה-Thread הנוכחי נעצר, ומחכה שתגיע בקשה ליצירת קשר. ברגע שמגיעה הבקשה המתודה יוצרת אובייקט חדש מטיפוס Socket, ומחזירה את ה-reference שלו. ברגע שזה קורה ה-Thread הנוכחי ממשיך את פעולתו.

אם קיים security manager אז עלול גם להיזרק SecurityException.

 

public void close() throws IOException

מתודה לסגירת אובייקט ה-ServerSocket שנוצר. בעת סגירת ה-ServerSocket עלול להיזרק IOException.

 

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

 

//filename:TCPSimpleServer.java

 

//Copyright (c) 2000 Haim Michael & Zindell Publishing House, Ltd.

//All rights reserved. No part of the contents of this program may be

//reproduced or transmitted in any form or by any means without the

//written permission of the publisher.

 

 

import java.io.*;

import java.net.*;

import java.util.*;

 

public class TCPSimpleServer

{

  private ServerSocket sSoc;

  public static final int PORT = 1300;

 

  public static void main(String args[]) throws IOException

  {

      TCPSimpleServer server = new TCPSimpleServer();

server.go();

  }

 

  public void go() throws IOException

  {

    Socket soc = null;

    sSoc = new ServerSocket(PORT);

    while(true)

    {

      //creating a Socket

      soc = sSoc.accept();

     

      //creating an OutputStream object

      OutputStream os = soc.getOutputStream();

       

      //creating an OutputStreamWriter object

      OutputStreamWriter osw = new OutputStreamWriter(os,"8859_1");

       

      //creating a BufferedWriter object

      BufferedWriter bw = new BufferedWriter(osw);

     

      bw.write("Java time is : " + (new Date()).toString() + "\n");

      bw.write("Using these classes you can do nice things\n");

      bw.write("Think ahead and think in depth !\n");

       

      bw.close();

      soc.close();

    }

  }

}

 

בתכנית זו נוצר אובייקט מטיפוס ServerSocket אשר בהפעילנו עליו את המתודה accept() מתבצע תהליך של האזנה ל-port שצוין בעת יצירת אובייקט ה-ServerSocket. הפעלת המתודה accept עוצרת את ה-thread הנוכחי, ובכך עוצרת למעשה את פעולת התכנית. רק לאחר שמגיעה בקשה ליצירת קשר עם השרת הנ"ל מסיימת המתודה accept את פעולתה, ומחזירה reference לאובייקט מטיפוס Socket. אל ה-stream שמתקבל מאובייקט ה- Socket נכתבות שלוש שורות שמגיעות אל הלקוח, ומייד אחר כך השרת ממשיך לפעול. המתודה accept() מופעלת שוב. כיוון ששרת זה מסוגל לטפל בכל רגע נתון רק בקישור אחד, הוא גם קרוי בשם שרת סדרתי (באופן סדרתי הוא מסוגל לטפל בכל רגע נתון רק בלקוח אחד, וכך אם יש מספר לקוחות אשר פונים אליו כדי לייצור קשר הוא יכול להתפנות בכל רגע נתון ליצירת קשר רק עם לקוח אחד, ולכן על כל התכניות הפונות לחכות בתור).

 

//filename:TCPSimpleClient.java

 

//Copyright (c) 2000 Haim Michael & Zindell Publishing House, Ltd.

//All rights reserved. No part of the contents of this program may be

//reproduced or transmitted in any form or by any means without the

//written permission of the publisher.

 

 

import java.net.*;

import java.io.*;

 

public class TCPSimpleClient

{

 

      public static final int DT_PORT = 1300;

 

      String hostName;

      Socket soc;

 

      public static void main(String args[]) throws IOException

      {

            TCPSimpleClient client = new TCPSimpleClient(args[0]);

            client.go();

      }

 

      public TCPSimpleClient(String hostString)

      {

            this.hostName = hostString;

      }

 

      public void go() throws IOException

      {

            soc = new Socket(hostName, DT_PORT);

     

            //creating an InputStream that will be connected to soc

            InputStream is = soc.getInputStream();

     

            //creating InputStreamReader that will be connected to is

            InputStreamReader isr = new InputStreamReader(is);

     

            //creating a BufferedReader object that will be connected

//to isr

            BufferedReader ibr = new BufferedReader(isr);

     

            System.out.println(ibr.readLine());

      System.out.println(ibr.readLine());   

            System.out.println(ibr.readLine());

     

            ibr.close();

            soc.close();

      }

}

 

בתכנית שפועלת בצד הלקוח יוצרים אובייקט מטיפוס Socket אשר מקושר למכונה ששמה הגיע בשורת הפקודה אל אותו port שהשרת מאזין לו. ביצירת אובייקט זה נשלחת הבקשה ליצירת קשר אל השרת, והמתודה accept שפועלת בצד השרת מסיימת את פעולתה.  מאובייקט ה-Socket שנוצר מקבלים stream אשר דרכו מגיעה האינפורמציה שנשלחה על ידי השרת.

 

נקודות נוספות שכדאי לשים אליהן לב:

·           כאשר מפעילים את המתודה close() על אובייקט שהוא stream אז המתודה close() תופעל באופן אוטומאטי על אובייקט ה-stream שעליו "רוכב" ה-stream הראשון. אם ה-stream השני "רוכב" על stream אחר, אז המתודה close() תופעל גם על האחר וכך הלאה

·           כאשר מפעילים את המתודה close() על אחד ה-streams  שמחוברים ל-socket אז באופן אוטומאטי תופעל על אובייקט ה-socket המתודה close().

·           טווח מספרי ה-port עד 1024 שמורים לשירותים שונים שכל מחשב מעניק. לא ניתן לבחור באופן חופשי במספר port כרצוננו. יש צורך לבדוק לפני ביצוע הבחירה במספר port כלשהו כי הוא לא תפוס.

 

 

תכנון תכניות שרת-לקוח

 

בעת כתיבתן של תכניות שרת-לקוח יש מספר שיקולים שיש לקחת בחשבון:

 

בחירת סוג השרת

בדוגמא שראינו היה שרת סדרתי פשוט (sequential\iterative server) שהיה מסוגל לייצור קשר אחד בלבד בכל רגע נתון (היה בו רק Thread אחד). רק לאחר שקשר עם תכנית הלקוח הסתיים השרת היה מסוגל לייצור קשר עם לקוח אחר.

סוג אפשרי אחר של שרת הוא השרת המקבילי (parallel\concurrent server) אשר מסוגל לטפל בו זמנית ביותר מקישור אחד (כלומר, במספר תכניות לקוח אשר פונות אליו בו זמנית). בשרת מסוג זה יש יותר מ-Thread אחד.

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

יתרון נוסף שיש בשרת סדרתי הוא פשטותו. שרת מקבילי מסובך הרבה יותר, מהסיבה שיש לממש בו מספר מסוים של Threads, ובהתאם יש גם לתת את הדעת לכל הבעייתיות שכרוכה בטיפול ב-Threads (synchronization, בעיית ה-Deadlock וכו').

 

בחירת אורך התור

כאשר בקשה לייצור קשר נשלחת מתכנית לקוח אל תכנית שרת, הקישור החדש נוצר רק אם השרת מחכה ליצירת קישור באמצעות המתודה accept(). מסיבה זו, בקשות שמגיעות אל תכנית השרת מתכניות לקוח כאשר accept() לא מופעלת לקראתם מועברות אל תור (הן לא נתקלות בסירוב מוחלט. הן מועברות לתור שבו יהיה עליהן להמתין עד שהשרת יתפנה לקבל אותן). ה- TCP/IP אחראי לכך שהדברים יתנהלו באופן זה. אורך התור נקרא גם בשם queue depth או backlog. אחד ה-constructors שקיימים במחלקה ServerSocket מאפשר לייצור אובייקט מטיפוס ServerSocket שאורך התור שבו הוא כפי שאנו רוצים.

בקשה ליצירת קשר שהתקבלה על ידי המתודה accept() איננה חלק ממניין הבקשות שממתינות בתור.

ברגע שהתור מתמלא, בקשות חדשות ליצירת קשר שמגיעות אל השרת כלל לא מטופלות, והשרת פשוט מתעלם מהן.

אם במהלך ניסיונותיו של הלקוח לייצור socket שיקושר לשרת האמור התור ממשיך להיות מלא, וחולף פרק הזמן שנקבע לכך על ידי המחשב שבו הוא פועל אז פעולת יצירת הsocket- נכשלת. ה-constructor נכשל בפעולתו ו- IOException בצירוף ההודעה : “Connection timed out” נזרק.

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

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

כשהשרת מקבילי, השימוש שיש לתור פוחת כיוון שיותר ויותר בקשות לקשר מטופלות באופן מיידי.

 

בחירת הפרוטוקול

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

מקובל לקבוע פרוטוקול שבו צד אחד פונה בשאלה לצד השני, והצד השני שולח בחזרה תשובה.

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

טבעו של הקשר בין הצדדים

על הפרוטוקול לקבוע באופן מדויק מהו העיתוי שבו כל צד יוכל לפנות לצד השני בשאלה/בתשובה. קיימות שתי גישות עיקריות שבאחת מהן יש לבחור.

       1.         "stop & wait"
צד אחד שולח לצד השני בקשה, והצד השני מבצע בעקבות הבקשה שקיבל חישובים ושולח בחזרה את תשובתו. בזמן ביצוע החישובים אף צד לא שולח שום דבר.  שם נוסף שמקובל לתת לפרוטוקול שכזה הוא:
“walkie-talkie protocol”. פרוטוקולים מסוג זה קלים למימוש ומשום כך הם גם מאד נפוצים.

       2.         “asynchronous protocol"
פרוטוקול שמאפשר להמשיך ולשלוח אינפורמציה גם בפרק הזמן שבו אחד משני הצדדים מבצע חישובים לאחר שקיבל בקשה מהצד השני. ניצול משאבי ה-
CPU וזמן התקשורת יעיל יותר בסוג זה של פרוטוקולים מאשר בפרוטוקולים מהסוג הראשון. פרוטוקול זה מחייב שבצד של השרת יהיה יותר מthread- אחד, ומשום כך מימושו מסובך יותר ממימוש הפרוטוקול : “stop and wait". 
למרות הקושי במימוש פרוטוקולים מטיפוס זה יש להם עדיפות ברורה על פני הפרוטוקולים מהטיפוס הראשון:
sockets אשר פועלים על פי TCP/IP  מאפשרים שליחת מידע מסוג OOB (Out Of Band) או מידע דחוף אחר. JAVA לא מאפשרת גישה לשירותים שמאפשרים שליחת מידע מסוג זה, למרות הפונקציונליות שקיימת באפשרות הזו (למשל שליחת הודעה מהלקוח אל שרת שיפסיק את ביצועה של שאילתה ארוכה). כיוון שניתן להשיג תוצאות דומות באמצעות פרוטוקולים מסוג asynchronous מסתמן בכך יתרונם.

פורמט המידע שנשלח

את המידע שנשלח ברחבי הרשת בין תהליכים שונים במחשבים שונים ניתן לשלוח במספר פורמטים:
Binary

פורמט בסיסי זה מתאים במיוחד כאשר מדובר בכמות מידע גדולה, ובייחוד אם המידע נשלח מכווץ. חיסרון בולט בשימוש בפורמט זה מופיע כאשר התקשורת לא נעשית עם תכנית שכתובה ב-JAVA. במקרה זה, אופן הייצוג של הטיפוסים הבסיסיים עשוי להיות שונה מאשר ב-JAVA, ושוני זה עלול לעורר בעיה בתרגום הנתונים שנשלחים בפורמט של bytes לערכים מטיפוס בסיסי.

ASCII

שירותים רבים ב-Internet משתמשים בפורמט זה.  יתרונו הברור של פורמט זה הוא האפשרות לבצע debugging פשוט באמצעות שירות ה-telnet (נבחן אותו בהמשך). כאשר מפעילים את תכנית ה telnet כדי לייצור קשר עם שרת, רואים על מסך ה-telnet את כל המידע שמגיע בחזרה מהשרת. אם המידע שמגיע בחזרה מהשרת הוא בפורמט ASCII אז ניתן גם להבין את מה שרואים על המסך. ניסיון להפעלת תכנית השרת הסדרתי שהצגנו קודם לכן בשורת הפקודה (חלון MSDOS), ולאחר מכן הפעלת תכנית הלקוח המתאימה באמצעות telnet ידגים את האמור. כדי להפעיל את תכנית הלקוח באמצעות telnet ניתן לגשת לתפריט “Start”, לבחור ב- “run” ובשורה הריקה לרשום: telnet localhost 1300.

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

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

החסרון של פורמט זה הוא באיטיותו היחסית.

Unicode

למרות שהבחירה להשתמש בפורמט זה עשויה להתאים לעובדה שJAVA עושה בו שימוש, יש לה חיסרון בולט, והוא הקושי לבצע debugging. חיסרון נוסף הוא העובדה שיש מערכות אשר אינן תומכות בו (מערכות שלא פועלות ב-JAVA). חיסרון נוסף שקיים בשימוש בפורמט זה הוא העובדה שהוא משתמש בייצוג של 16 ביטים, וזה פי 2 מאשר קוד ASCII.

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

UTF

פורמט זה יכול לייצג את כל התווים שניתן לייצג בפורמט Unicode. פורמט ה-UTF תופס מספר משתנה של בתים, בהתאם לתו שמיוצג. התווים שבטבלת ה-ASCII מיוצגים ללא שינוי באותו אופן כמו בטבלת קודי ה- ASCII באמצעות byte אחד. תווים שכדי לייצגם יש צורך ב-8 ביטים ורבים מהתווים בטבלת ה-Unicode מיוצגים באמצעות 2 בתים. כל התווים האחרים מיוצגים באמצעות 3 בתים.

היתרון הבולט בפורמט זה הוא שכל עוד מדובר בתווים ששייכים לטבלת ה-ASCII רוחב פס התקשורת שדרוש הוא 8 ביטים (ולא 16 באופן קבוע כפי שהיה קורה אילו היה מדובר בפורמט Unicode). אם המידע שמועבר כולל בתוכו הרבה אותיות אסייתיות פורמט זה לא יתאים (ייצוגן של רוב האותיות האסייתיות בפורמט UTF דורש 3 בתים).

יתרון נוסף שיש לשימוש בפורמט זה הוא האפשרות לבצע debugging באמצעות telnet,כל עוד מדובר בתווים ששייכים לטבלת ה-ASCII כמובן.

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

 

 

שרת מקבילי מבוסס TCP/IP

 

בשרת סדרתי השתמשנו ב-thread העיקרי של התכנית (או שאפילו יצרנו thread יחיד נפרד) אשר כלל הפעלה של המתודה accept() על אובייקט ServerSocket וביצוע כל הטיפול בקישור שנוצר בתוך לולאה אינסופית.   בשרת מקבילי יוצרים מספר מסוים של threads אשר פועלים במקביל, וכל אחד מהם מסוגל לבצע את מה שבשרת סדרתי ביצע ה-thread העיקרי לבדו.

יש לשים לב לכך שיצירת thread חדש לכל בקשה ליצירת קשר שמגיעה איננה גישה טובה כיוון שהיא עלולה לגרום די מהר לקריסת ה-JVM בגלל בעיות זיכרון. יש מערכות שבהן ה-Garbage Collector לא אוסף את שטחי הזיכרון של ה-threads שהסתיימו (ומסיבה זו גם לא את שטחי הזיכרון שהreferences שלהם מוחזק על ידי ה-thread).

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

 

הדוגמא הבאה מציגה שרת מקבילי פשוט שבנוי עפ"י מה שהסברנו:

 

 

 

//filename:TCPParallelServer.java

 

//Copyright (c) 2000 Haim Michael & Zindell Publishing House, Ltd.

//All rights reserved. No part of the contents of this program may be

//reproduced or transmitted in any form or by any means without the

//written permission of the publisher.

 

 

import java.net.*;

import java.io.*;

import java.util.*;

 

 

public class TCPParallelServer

{

      public static void main(String args[]) throws Exception

      {

            TCPParallelServerImp ps = new TCPParallelServerImp();

            ps.go();

      }

}

 

 

class TCPParallelServerImp implements Runnable

{

      private ServerSocket serverSocket;

 

      public void go() throws Exception

      {

            serverSocket = new ServerSocket(1300, 5);

 

          Thread t1 = new Thread(this, "A");

          Thread t2 = new Thread(this, "B");

          Thread t3 = new Thread(this, "C");

          Thread t4 = new Thread(this, "D");   

 

          t1.start();

      t2.start();

      t3.start();

      t4.start();

     

      }

 

      public void run()

      {

      Socket soc = null;

      BufferedWriter bw = null;

      String myName = Thread.currentThread().getName();

 

      while(true)

            {

                  try

                  {

                  System.out.println("thread " + myName + " is waiting ...");

                        serverSocket.setSoTimeout(4000);

                  soc = serverSocket.accept();

 

                        System.out.println("thread " + myName + " has accepted a connection");

       

                        bw = new BufferedWriter(new OutputStreamWriter(soc.getOutputStream()));

                  bw.write("The thread that in action now is : " + myName);

                        bw.newLine();

                        bw.write("The date is " + new Date());

                        bw.newLine();

                  // Thread.sleep(5000);

                  bw.write("This is the last line to be recieved from " + myName);

                        bw.newLine();

                  bw.close();

           

                        System.out.println("thread " + myName + " has terminated the connection");          

                  }

        catch(InterruptedIOException exc)

        {

            System.out.println("The accept method in " + myName +

            " stoped from waiting since a timeout value has been set");

        }              

      catch (Exception ex)

        {

        ex.printStackTrace();

      }

    }

  }

}

 

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

בתכנית זו כל אחד מה-Threads פועל על אותו אובייקט Runnable. עם זאת, כדאי לשים לב שבכל Thread יש ערכים שספציפיים רק לו : משתנה ה-Socket, משתנה ה-BufferedWriter ומשתנה ה-String שמתאר את השם של ה-Thread. 

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

 

כיוון שיצירתו של Thread כרוכה במשאבי CPU רבים, כדאי לייצור את כל ה-Thredas של התכנית בהתחלתה.

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

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

 

 

פרוטוקול התקשורת UDP

 

מבוא לUDP-

 

כפי שכבר הוסבר בחלקו הראשון של הפרק, קיימות שלוש שכבות: השכבה הפיזית, שכבת ה-IP והשכבה השלישית. השכבה השלישית כוללת מספר שכבות שמתקיימות במקביל. באחת מהן, שכבת ה-TCP, כבר עסקנו. כעת נעסוק בשכבה נוספת שמתקיימת בשכבה השלישית, בשכבת הUDP-.

UDP = User Datagram Protocol

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

ניתן לראות את דימוי המעטפה שהוצג בחלק שעסק בפרוטוקול הTCP- גם פה. פרוטוקול ה-UDP לוקח את האינפורמציה הנשלחת, מכניס אותה למעטפה, על המעטפה רושם את מספר ה-port המיועד ומעביר אותה אל השכבה שמתחת, אל שכבת ה-IP. שכבת ה-IP מקבלת את האינפורמציה בתוך מעטפה כשמספר ה-port המיועד רשום עליה.

 

שכבת ה-IP (השכבה השניה) שמעבירה מידע אל השכבה השלישית יודעת עפ"י ה-datagram שהגיע אליה, אם עליה לייעד אותו לשכבת ה-UDP או לשכבת ה-TCP (מבנה ה-datagram שמיועד לשכבת ה-UDP שונה ממבנה ה-datagram שמיועד לשכבת ה-TCP ושכבת ה-IP יודעת להבחין בין השניים).

 

שמו של הפרוטוקול ניתן לו על שום שהוא מקבל את ה-datagram משכבת ה-IP, ומספק סביבה כללית עם מעט מאד מגבלות שמאפשרת כתיבת תכניות לטיפול באותם datagrams שנשלחים/מתקבלים (הכוונה במילה user בראשי התיבות UDP היא לתהליך שמשתמש ב-datagrams).

פרוטוקול ה-UDP כולל בתוכו את האפשרות לבדוק אם המידע שהגיע הושחת. בדרך כלל לא עושים שימוש באפשרות זו.

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

 

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

כאשר משתמשים בפרוטוקול UDP כל היתרונות האלה נעלמים, ובאחריותו של המתכנת לפתור בעיות אלה.

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

 

למרות כל היתרונות של השימוש ב-TCP במקום בUDP-, קיימים עדיין מספר יתרונות לזכותו של הפרוטוקול UDP:

       ü          זהו פרוטוקול פשוט לשימוש

       ü          צורך פחות זמן CPU מאשר הפרוטוקול TCP

       ü          כרוך בשליחת פחות אינפורמציה נלווית

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

 

 

תכנית שרת-לקוח  מבוססת UDP

 

השימוש בפרוטוקול זה כרוך בשימוש במחלקות : DatagramSocket  ו- DatagramPacket.  ה- packet הוא הודעה שכוללת בתוכה את הפרטים הבאים : פרטי השולח, אורך ההודעה וההודעה עצמה.

 

המחלקה DatagramPacket

מחלקה זו כוללת שני סוגים של constructors. האחד ליצירת packet שמיועד לשליחת הודעה, והשני ליצירת packet שמיועד לקבלת הודעה.

 

DatagramPacket (byte[] recvBuf, int readLength)

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

 

DatagramPacket (byte [] sendBuf, int sendLength, InetAddress iaddr, int iport)

constructor שמשמש ליצירתUDPpacket  שמיועד לשליחה. בדומה ל-constructor הקודם sendBuf הוא מערך של bytes אשר כולל את ההודעה שנשלחת. הערך המספרי, sendLength חייב להיות קטן או שווה לגודלו של מערך ה-bytes . הערך השלישי שנשלח ל-constructor , iaddr , הוא reference לאובייקט שמתאר את הכתובת שאליה ההודעה תישלח. הערך הרביעי שנשלח ל-constructor, iport, הוא מספר ה-port שאליו ההודעה תופנה במחשב היעד.

 

להלן מספר מתודות שימושיות במחלקה:

 

public InetAddress getAddress()

מתודה זו מחזירה את הכתובת (ה-IP Address) של המחשב שממנו (או אליו) ה-packet הגיע (או נשלח).

 

public int getPort()

מתודה זו מחזירה את מספר ה-port שבמחשב המרוחק אשר ממנו הגיע ה-packet או שאליו ה-packet נשלח.

 

public byte[] getData()

מתודה זו מחזירה את ההודעה שנמצאת בתוך ה-packet (ההודעה שהגיעה/נשלחה).

 

public int getLength()

מתודה זו מחזירה את אורך ההודעה שנמצאת בתוך ה-packet שהגיע/נשלח.

 

 

המחלקה DatagramSocket

מחלקה זו מתארת socket שמשמש לכתיבת/קריאת UDP packets. ה-constructors הבולטים במחלקה הם:

 

DatagramSocket()

ליצירת socket שיקושר לכל port פנוי שבנמצא.

 

DatagramSocket(int port)

ליצירת socket שיקושר ל-port הספציפי שמצוין.

 

DatagramSocket(int port, InetAddress iaddr)

ליצירת socket  שיקושר ל-port הספציפי בכתובת המצוינת.

 

המתודות השימושיות במחלקה:

public InetAddress getInetAddress()

מתודה שמחזירה reference מטיפוס InetAddress לאובייקט InetAddress אשר מתאר את הכתובת שאליה מחובר ה-socket. המתודה מחזירה null אם ה-socket עדיין לא מחובר לאף כתובת באינטרנט.

 

public int getPort()

מתודה שמחזירה את מספר ה-port שאליו ה-socket מחובר. אם ה-socket עדיין לא מחובר אז היא מחזירה –1.

 

public void receive(DatagramPacket p) throws IOException

מתודה זו משמשת לקבלת datagram packet. כאשר המתודה מסיימת את פעולתה אובייקט ה-DatagramPacket שעליו היא פעלה מקבל תוכן: הוא מכיל את כתובת ה-IP של השולח, את מספר ה-port שממנו ה-packet הגיע במחשב ששלח ומערך ה-bytes שקשור אליו מתמלא בתוכן ההודעה שהגיעה). מתודה זו פועלת בדומה למתודה accept() במחלקה ServerSocket. כל עוד היא לא הסתיימה ה-Thread שבמסגרתו היא פעלה נעצר.

השדה length באובייקט ה-DatagramPacket ערכו נקבע להיות כאורכה של ההודעה שהגיעה (האורך נמדד ב-bytes). אם ההודעה ארוכה מאורך מערך ה-bytes שמוחזק ב-packet אז היא נחתכת.

המתודה מסוגלת לזרוק IOException, והיא עושה זאת כאשר מתרחשות בפעולתה תקלות שקשורות בקלט/פלט של המחשב. לעתים קיים security manager אשר יכול למנוע את שליחתו של ה-packet.

public void send(DatagramPacket p) throws IOException, SecurityException

מתודה זו שולחת packet. ה-packet מכיל אינפורמציה שכוללת את המידע הנשלח, אורכו, כתובת ה-IP של המחשב המרוחק ומספר ה-port שאליו ההודעה תגיע.

אם קיימת בעיית קלט/פלט ייזרק IOException, ואם קיימת בעיה של הרשאת גישה אז ייזרק SecurityException.

 

בדוגמא לעיל ניתן לראות את תכנית הלקוח ואת תכנית השרת אשר מיישמות את המתודות שנסקרו:

 

תכנית השרת:

//filename:UDPServer.java

 

//Copyright (c) 2000 Haim Michael & Zindell Publishing House, Ltd.

//All rights reserved. No part of the contents of this program may be

//reproduced or transmitted in any form or by any means without the

//written permission of the publisher.

 

 

import java.io.*;

import java.net.*;

import java.util.*;

 

 

public class UDPServer

{

      public byte[] getMsg(String name)

      {

            return ("HELLO " + name + " FROM UDP SERVER").getBytes();

      }

     

      public void go()throws IOException

      {

            DatagramSocket datagramSocket;

            DatagramPacket inputDatagramPacket;

            DatagramPacket outputDatagramPacket;

            InetAddress clientAdd;

            int clientPort;

            byte[] incomingMsg = new byte[100];

            byte[] outgoingMsg = new byte[100];

 

            System.out.println("The server is ON");        

           

            datagramSocket = new DatagramSocket(2000);

            System.out.println("Server is listening to port 2000");

           

            while(true)

            {

                  inputDatagramPacket = new

DatagramPacket(incomingMsg, incomingMsg.length);

 

                  datagramSocket.receive(inputDatagramPacket);

     

                  String rcvMsg = new

String(inputDatagramPacket.getData(),0,

                 

inputDatagramPacket.getLength());

                       

                  System.out.println("The server has received : " +

rcvMsg);

           

                  clientAdd = inputDatagramPacket.getAddress();

                  clientPort = inputDatagramPacket.getPort();

                 

                  byte result[] = getMsg(rcvMsg);

                  outputDatagramPacket =

                        new DatagramPacket(result,result.length,

                        clientAdd, clientPort);

                 

                  datagramSocket.send(outputDatagramPacket);

            }

      }

     

      public static void main(String args[])

      {

            UDPServer udpServer = new UDPServer();

           

            try

            {

                  udpServer.go();

            }

            catch(IOException e)

            {

                  System.out.println("IOException has occured : ");

                  e.printStackTrace();

                  System.exit(1);

            }

            catch(Exception e)

            {

                  System.out.println("general Exception occured:");

                  e.printStackTrace();

                  System.exit(1);

            }

      }

}

 

תכנית הלקוח:

//filename:UDPClient.java

 

//Copyright (c) 2000 Haim Michael & Zindell Publishing House, Ltd.

//All rights reserved. No part of the contents of this program may be

//reproduced or transmitted in any form or by any means without the

//written permission of the publisher.

 

 

import java.io.*;

import java.net.*;

import java.util.*;

 

 

public class UDPClient

{

      public static void main(String args[])

      {

            UDPClient udpClient = new UDPClient();

            if (args.length==0)

                  System.out.println("You should enter your name !");

            else

            {

                  try

                  {

                        udpClient.go(args[0]);

                  }

                  catch(Exception e)

                  {

                        System.out.println("Exception has occured

with socket");

                        e.printStackTrace();

                        System.exit(1);

                  }

            }

      }

     

      public void go(String name)

throws IOException,UnknownHostException

      {

            DatagramSocket datagramSocket = new DatagramSocket();

           

            DatagramPacket outputDataPacket, inputDataPacket;

           

            InetAddress serverAddress;

           

            String recievingMsg;

           

            byte [] msg = new byte[100];

           

            byte [] temp = name.getBytes();

           

            for (int i=0; i<temp.length; i++)

                  msg[i] = temp[i];

           

            serverAddress = InetAddress.getLocalHost(); 

            //the server works in our example on the same machine

            //that the client works on

           

            outputDataPacket = new DatagramPacket(msg, temp.length,

                  serverAddress, 2000);

           

            datagramSocket.send(outputDataPacket);

            inputDataPacket = new DatagramPacket(msg, msg.length);

           

            datagramSocket.receive(inputDataPacket);

           

            recievingMsg = new String(

                  inputDataPacket.getData(), 0,

inputDataPacket.getLength());

           

            System.out.println("The received message from server is:"

+ recievingMsg);

           

            datagramSocket.close();

      }

}

 

 

פרוטוקול SMTP (שליחת email)

 

כדי לשלוח email יש לעשות שימוש בפרוטוקול SMTP. בברירת המחדל, פרוטוקול זה פועל בפורט מספר 25. ראשי התיבות של SMTP הן SiMple Transport Protocol. פרוטוקול זה פועל באמצעות הפרוטוקול TCP\IP (הוא נמצא בשכבה אחת מעליו). כדי לשלוח email בפרוטוקול זה יש ליצור connection מסוג TCP\IP אל שרת ה-email (השרת שדרכו ניתן לשלוח email) בפורט 25. לאחר יצירת ה-socket  יש להפעיל עליו את המתודה getOutputStream() אשר מחזירה reference מטיפוס OutputStream ל-stream שנתונים שייכתבו אליו ישלחו, למעשה, אל שרת ה-email. בהמשכו של ה-output stream  יש להרכיב print writer. אל צינור ה-print writer  יש לכתוב את הטקסט שרוצים שיגיע אל שרת ה-email. טקסט זה יכלול את הפקודות השונות שמשמעותן תהיה שליחתו של email.

כך, למשל, כדי לשלוח email ל-haim_Michael@zindell יש לכתוב את שורות הקוד הבאות:

Socket socket = new Socket(emailServer,25);

PrintWriter writer = new PrintWriter(socket.getOutputStream());

String hostname = InetAddress.getLocalHost().getHostName();

writer.println(“HELO “ + hostname);

writer.println(“MAIL FROM: “ + senderEmailAddress);

writer.println(“RCPT TO: “ + “haim_michael@zindell.com”);

writer.println(“DATA”);

writer.println(“bla bla bal …     “);

writer.println(“.”);

writer.close();

socket.close();

 

על אובייקט ה-socket שנוצר ניתן להפעיל את המתודה getInputStream() ולקבל reference ל-stream שדרכו מגיעה תשובתו של השרת ל-email  שקיבל. את הנתונים שמגיעים דרך stream זה ניתן לקרוא ולהדפיס למסך.

 

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

שמו של השרת שאליו ניתן להתחבר כדי לשלוח email.

כתובת הדואר האלקטרוני של השולח.

כתובת הדואר האלקטרוני של הנמען.

תוכן ההודעה.

 

//filename: HelloToHaim.java

 

 

//Copyright (c) 2000 Haim Michael & Zindell Publishing House, Ltd.

//All rights reserved. No part of the contents of this program may be

//reproduced or transmitted in any form or by any means without the

//written permission of the publisher.

 

 

 

import java.io.*;

import java.net.*;

 

 

public class SendEmail

{

            static Socket socket = null;

            static PrintWriter output = null;

 

            public static void main(String args[])

            {

                        if(args.length==4)

                        {

                                    try

                                    {

                                                socket = new Socket(args[0], 25);

                                                output =  new PrintWriter(socket.getOutputStream());

                                                String hostName = InetAddress.getLocalHost().getHostName();

                                                System.out.println("InetAddress.getLocalHost().getHostName()="+InetAddress.getLocalHost().getHostName());

                                                System.out.println("hostName:"+hostName);

                                                System.out.println("args[0],eMailServer:"+args[0]);

                           send(null);

                                                send("HELO " + hostName);

                            send("MAIL FROM: " + args[1]);

                                                System.out.println("args[1],from:"+args[1]);

                                                send("RCPT TO: " + args[2]);

                                                System.out.println("args[2],to:"+args[2]);

                                                send("DATA");

                                                send(args[3]);

                                                System.out.println("args[3],message:"+args[3]);

                                                send(".");

                                    }

                                    catch (IOException e)

                                    {

                                                e.printStackTrace();

                                    }

                                    finally

                                    {

                                                try

                                                {

                                                            socket.close();

                                                }

                                                catch(IOException e)

                                                {

                                                            e.printStackTrace();

                                                }

                                    }

                        }

            }

 

            public static void send(String s) throws IOException

            {

                        if (s != null)

                        {

                                    output.println(s);

                                    output.flush();

                        }

            }

 

}

 

 

 

 

 

 

 

 

 

 

 

2000 © All the rights reserved to

Haim Michael & Zindell Publishing House Ltd.

 

No parts of the contents of this paper may be reproduced or transmitted

in any form by any means without the written permission of the publisher ! 

This book can be used for personal use only !!!

 

 

 

Brought to you by ZINDELL

http://www.zindell.com

 

 

 

 

לנוחיותך, להלן תוכן העניינים של הספר:

 

פרק 1: ההתחלה Let's start  -

פרק 2: הבסיס Basic -

פרק 3: מחלקות Classes -
פרק 4: מערכים ומחרוזות תוויםArrays & Strings -

פרק 5: הורשהInheritance -

פרק 6: ניתוח מרכיביה של מחלקהReflection -

פרק
7: מחלקות פנימיותInner Classes -
פרק 8: יישומוניםApplets -

פרק 9: תכנות מקביליThreads -

פרק 10: ממשק משתמש גרפי בסיסי Graphic User Interface using AWT -

פרק 11: הטיפול באירועים Events Handling -

פרק 12: הטיפול בשגיאות Exceptions Handling -

פרק 13: פעולות קלט/פלט I\O Streams -
פרק 14: תקשורתNetworking -

פרק 15: ממשק משתמש גרפי מתקדם Graphic User Interface using SWING -
פרק 16: מבני נתונים Data Structures  -

פרק 17: מחלקות מוכנות לתיאור מבני נתונים Collections API  -

פרק 18:  קישור לבסיס נתונים - JDBC

פרק 19:  הפעלת מתודות מרחוק RMI -

פרק 20:  הסבת התכנית למדינות אחרותInternationalization -

פרק 21:  שילוב קוד בC- Native Methods (JNI) -

פרק 22:  שימוש בXML-

פרק 23:  אבטחת מידע Security -

פרק 24:  גרפיקה דו ממדית 2D Graphics -

פרק 25:  ניהול הזיכרון Memory Management -

 

 


נספחים:

נספח א': אופן הפעלת ה-javadoc

נספח ב': הכרות ראשונית עם UML

נספח ג': הכרות ראשונית עם Design Patterns

נספח ד': שיפור הביצועים של התכנית

נספח ה':  כללי תחביר בכתיבת קוד מקור

נספח ו':  פקודות שכיחות ב-SQL