·
הקדמה
כדי להבין פרק זה יש, תחילה, להתמקד במושג:
"פרוטוקול". "פרוטוקול" הינו סדרה של כללים. פרוטוקול תקשורת,
לפיכך, הינו סדרה של כללים שעל פיהם מתבצע תהליך של העברת נתונים בין שני מחשבים.
התקשורת בין שתי תכניות (שבשני מחשבים) נעשית ב-JAVA בדרך כלל עפ"י הפרוטוקולים TCP\IP (הפרוטוקולים שמשמשים לתקשורת ברשת האינטרנט). פרוטוקול ה-TCP\IP הוא שם כולל לפרוטוקולים שונים, שמשמשים להעברת קבצים, התחברות
למחשבים מרחוק ולפעולות תקשורת אחרות.
פרק זה נחלק לשלושה. כל חלק מתמקד בפרוטוקול תקשורת אחר.
החלק הראשון מתמקד בפרוטוקול התקשורת TCP\IP. בתחילה מובא הסבר מקיף לפרוטוקול התקשורת TCP\IP, אשר משמש ליצירת תקשורת ב-JAVA. חלקים מהסבר זה יהיו רלוונטיים גם לחלקיו האחרים של הפרק. בהמשך,
לאחר ההסבר, יוצגו מספר טכניקות מקובלות ביצירת תכניות שרת-לקוח מבוססות TCP\IP.
החלק השני עוסק בפרוטוקול התקשורת UDP. בתחילה מוסבר הפרוטוקול, ולאחר מכן מוצגות מספר טכניקות מקובלות
לשימוש בו.
החלק השלישי מציג את הפרוטוקול שמקובל בשליחת email, הפרוטוקול SMTP
ומדגים כיצד להשתמש בו.
מבוא ל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-
כפי
שכבר הוסבר בחלקו הראשון של הפרק, קיימות שלוש שכבות: השכבה הפיזית, שכבת ה-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();
}
}
כדי
לשלוח 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
נספח ו':
פקודות
שכיחות ב-SQL
