תכנות שקעים בג'אווה: הדרכה

מדריך זה הוא מבוא לתכנות שקעים בג'אווה, החל מדוגמת שרת לקוח פשוטה המדגימה את התכונות הבסיסיות של Java I / O. תוצג בפניך גם java.io החבילה המקורית  וגם NIO, java.nioממשקי ה- API של I / O ( ) שאינם חוסמים שהוצגו ב- Java 1.4. לבסוף תראה דוגמה המדגימה את רשתות Java כפי שהיא מיושמת מ- Java 7 קדימה, ב- NIO.2.

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

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

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

במדריך זה אני מציג בפניכם תכנות שקעים בג'אווה. אני מציג סדרה של דוגמאות של שרת לקוחות שמדגימות תכונות ממסגרת ה- I / O המקורית של Java, ואז עוברת בהדרגה לשימוש בתכונות שהוצגו ב- NIO.2.

שקעי ג'אווה מבית הספר הישן

ביישומים שקדמו ל- NIO, קוד שקע לקוח Java TCP מטופל על ידי java.net.Socketהמחלקה. הקוד הבא פותח חיבור לשרת:

 שקע שקע = שקע חדש (שרת, יציאה); 

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

InputStream in = socket.getInputStream (); OutputStream out = socket.getOutputStream ();

מכיוון שמדובר בזרמים רגילים, אותם זרמים בהם נשתמש לקרוא ולכתוב לקובץ, אנו יכולים להמיר אותם לצורה המשרתת בצורה הטובה ביותר את מקרה השימוש שלנו. לדוגמא, נוכל לעטוף את ה- OutputStreamA PrintStreamכך שנוכל לכתוב טקסט בקלות בשיטות כמו println(). לדוגמא אחרת, נוכל לעטוף את ה- InputStreamA BufferedReaderבאמצעות Via InputStreamReaderכדי לקרוא טקסט בקלות בשיטות כמו readLine().

הורד הורד את קוד המקור קוד המקור ל"תכנות שקע בג'אווה: הדרכה. " נוצר על ידי סטיבן היינס עבור JavaWorld.

דוגמא לקוח שקע Java

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

  1. צור שקע לשרת האינטרנט המאזין ביציאה 80.
  2. השג א PrintStreamלשרת ושלח את הבקשה GET PATH HTTP/1.0, היכן PATHנמצא המשאב המבוקש בשרת. לדוגמא, אם היינו רוצים לפתוח את השורש של אתר אינטרנט, הנתיב יהיה /.
  3. השג InputStreamלשרת, עוטף אותו עם a BufferedReaderוקרא את התגובה שורה אחר שורה.

רישום 1 מציג את קוד המקור לדוגמא זו.

רישום 1. SimpleSocketClientExample.java

חבילה com.geekcap.javaworld.simplesocketclient; ייבא java.io.BufferedReader; ייבא java.io.InputStreamReader; ייבא java.io.PrintStream; ייבא java.net.Socket; class class SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Usage: SimpleSocketClientExample"); System.exit (0); } שרת מחרוזות = טענות [0]; נתיב מחרוזת = טענות [1]; System.out.println ("טוען תוכן של כתובת אתר:" + שרת); נסה {// התחבר לשרת שקע שקע = שקע חדש (שרת, 80); // צור זרמי קלט ופלט לקריאה וכתיבה לשרת PrintStream out = PrintStream חדש (socket.getOutputStream ()); BufferedReader in = BufferedReader חדש (InputStreamReader חדש (socket.getInputStream ())); // עקוב אחר פרוטוקול HTTP של GET HTTP / 1.0 ואחריו שורה ריקה outprint.println ("GET" + נתיב + "HTTP / 1.0"); out.println (); // קרא נתונים מהשרת עד שנסיים לקרוא את המסמך String line = in.readLine (); בעוד (קו! = null) {System.out.println (קו); קו = in.readLine (); } // סגור את הזרמים שלנו ב- .close (); out.close (); socket.close (); } לתפוס (חריג e) {e.printStackTrace (); }}}

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

קבל PATH HTTP / 1.0 

לדוגמה:

GET / HTTP / 1.0 

מה קרה הרגע?

כאשר אתה מאחזר דף אינטרנט משרת אינטרנט, כגון www.google.com, לקוח HTTP משתמש בשרתי DNS כדי למצוא את כתובת השרת: זה מתחיל בבקשה משרת הדומיין ברמה העליונה את comהתחום שבו שרת שמות הדומיין הסמכותי הוא עבור www.google.com. ואז הוא מבקש ששרת שם הדומיין עבור כתובת ה- IP (או הכתובות) עבור www.google.com. לאחר מכן הוא פותח שקע לשרת זה ביציאה 80. (לחלופין, אם ברצונך להגדיר יציאה אחרת, תוכל לעשות זאת על ידי הוספת נקודתיים ולאחריה מספר היציאה, למשל:. :8080) לבסוף, לקוח HTTP מבצע והמתודה שצוין, כגון GET, POST, PUT, DELETE, HEAD, או OPTI/ONS. לכל שיטה תחביר משלה. כפי שמוצג בתצלומי הקוד לעיל, GETהשיטה דורשת נתיב ואחריוHTTP/version numberושורה ריקה. אם היינו רוצים להוסיף כותרות HTTP היינו יכולים לעשות זאת לפני שנכנס לשורה החדשה.

ברשימה 1 שלפנו את האריזה OutputStreamועטפנו אותה PrintStreamכך שנוכל לבצע את הפקודות מבוססות הטקסט שלנו ביתר קלות. הקוד שלנו קיבל InputStream, עטף את זה ב- an InputStreamReader, שהמיר אותו ל- a Reader, ואז עטף את זה ב- a BufferedReader. השתמשנו ב- PrintStreamכדי לבצע את GETהשיטה שלנו ואז השתמשנו BufferedReaderבכדי לקרוא את התגובה שורה אחר שורה עד שקיבלנו nullתגובה, מה שמעיד על כך שהשקע היה סגור.

עכשיו בצע את המעמד הזה והעביר לו את הטיעונים הבאים:

java com.geekcap.javaworld.simplesocketclient.SimpleSocketClient דוגמה www.javaworld.com / 

אתה אמור לראות פלט דומה למה שלמטה:

טוען תוכן של כתובת האתר: www.javaworld.com HTTP / 1.1 200 אישור תאריך: א ', 21 בספטמבר 2014 22:20:13 GMT שרת: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X- בישול-עם: בנזין-מקומי X-בנזין-גיל: 8 אורך תוכן: 168 שונה לאחרונה: יום שלישי, 24 בינואר 2012 00:09:09 GMT תג: "60001b-a8-4b73af4bf3340" סוג תוכן : text / html משתנה: חיבור קבלת קידוד: סגור דף בדיקת בנזין

הַצלָחָה

פלט זה מציג דף בדיקה באתר JavaWorld. זה השיב בחזרה שהוא מדבר ב- HTTP גרסה 1.1 והתגובה היא 200 OK.

דוגמה לשרת שקעי Java

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

  1. צור a ServerSocket, וציין יציאה להאזנה.
  2. להפעיל את ServerSocketים" accept()שיטת להקשיב על הנמל המוגדר לחיבור הלקוח.
  3. כאשר לקוח מתחבר לשרת, accept()השיטה מחזירה Socketדרכה השרת יכול לתקשר עם הלקוח. זה אותו Socketמעמד בו השתמשנו עבור הלקוח שלנו, ולכן התהליך זהה: להשיג InputStreamקריאה מהלקוח OutputStreamוכתיבה ללקוח.
  4. אם השרת שלך צריך להיות מדרגי, תרצה להעביר את Socketהשרשור לתהליך אחר כדי שהשרת שלך יוכל להמשיך להאזין לחיבורים נוספים.
  5. התקשר ServerSocketים" accept()שיטה שוב להקשיב לחיבור אחר.

כפי שתראו בקרוב, הטיפול ב- NIO בתרחיש זה יהיה שונה במקצת. לעת עתה, עם זאת, אנו יכולים ליצור ישירות א על ServerSocketידי העברת יציאה להאזנה (עוד על ServerSocketFactorys בפרק הבא):

 ServerSocket serverSocket = ServerSocket חדש (יציאה); 

ועכשיו נוכל לקבל חיבורים נכנסים באמצעות accept()השיטה:

שקע שקע = serverSocket.accept (); // לטפל בחיבור ...

תכנות רב-הברגה עם שקעי Java

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

While the example in Listing 2 isn't complicated it does anticipate some of what's coming up in the next section on NIO. Pay special attention to the amount of threading code we have to write in order to build a server that can handle multiple simultaneous requests.

Listing 2. SimpleSocketServer.java

package com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.I/OException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class SimpleSocketServer extends Thread { private ServerSocket serverSocket; private int port; private boolean running = false; public SimpleSocketServer( int port ) { this.port = port; } public void startServer() { try { serverSocket = new ServerSocket( port ); this.start(); } catch (I/OException e) { e.printStackTrace(); } } public void stopServer() { running = false; this.interrupt(); } @Override public void run() { running = true; while( running ) { try { System.out.println( "Listening for a connection" ); // Call accept() to receive the next connection Socket socket = serverSocket.accept(); // Pass the socket to the RequestHandler thread for processing RequestHandler requestHandler = new RequestHandler( socket ); requestHandler.start(); } catch (I/OException e) { e.printStackTrace(); } } } public static void main( String[] args ) { if( args.length == 0 ) { System.out.println( "Usage: SimpleSocketServer " ); System.exit( 0 ); } int port = Integer.parseInt( args[ 0 ] ); System.out.println( "Start server on port: " + port ); SimpleSocketServer server = new SimpleSocketServer( port ); server.startServer(); // Automatically shutdown in 1 minute try { Thread.sleep( 60000 ); } catch( Exception e ) { e.printStackTrace(); } server.stopServer(); } } class RequestHandler extends Thread { private Socket socket; RequestHandler( Socket socket ) { this.socket = socket; } @Override public void run() { try { System.out.println( "Received a connection" ); // Get input and output streams BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); PrintWriter out = new PrintWriter( socket.getOutputStream() ); // Write out our header to the client out.println( "Echo Server 1.0" ); out.flush(); // Echo lines back to the client until the client closes the connection or we receive an empty line String line = in.readLine(); while( line != null && line.length() > 0 ) { out.println( "Echo: " + line ); out.flush(); line = in.readLine(); } // Close our connection in.close(); out.close(); socket.close(); System.out.println( "Connection closed" ); } catch( Exception e ) { e.printStackTrace(); } } }