java網絡編程



網絡基本知識:

在java中網絡程序有兩種協議:TCP和UDP,TCP通過握手協議進行可靠的連接,UDP則是不可靠連接。

IP地址:用於標記一臺計算機的身份證。

IP地址由網絡地址(確定網絡)和主機地址(網絡中的主機)組成。

子網掩碼:爲了區分網絡地址和主機地址。

IP地址分爲A類地址、B類地址、C類地址(常用)、D類地址、E類地址。

127.0.0.1(localhost)是本機地址。

IPV4和IPV6

IPV4使用4個十進制數表示,即32位二進制。

SMTP是簡單郵件傳輸協議,端口號是25.


telnet用於連接遠程計算機或者因特網計算機提供的服務。每個服務都會設定一個端口。

給出類似     telnet ip    port    即可和特定的服務進行通信

如果要連接因特網的服務,不僅要給出端口,還要給出計算機的名稱,只有給出IP地址和端口號時,才能夠請求服務,並接收到應答。


URL和URI

URI:統一資源標識符,用於標識一個web資源,包含了兩個部分。

(1)URL:統一資源定位符。能夠精確的定位數據的URI

(2)URN:統一資源名稱。除了URL的URI

在java中URI和URL是分開的兩個類,URI類專門用於解析,URL用於通信。

URL

1.URI分類

絕對和相對:

(1)絕對URI是指有確定的協議。比如http,ftp。後面以/進行分隔

(2)相對URI是沒有scheme的。

透明和不透明:

(1)不透明URI是不能夠被解析的URI。不透明URI是絕對URI。scheme後面的部分不是以/進行分割。

分層和不分層:

(1)分層是絕對透明URI或相對URI。

所有的網頁端口都是80.

2.URI的作用:

(1)解析

URI的格式:

[scheme:]scheme-specific-part[#fragment]

scheme表示用的協議,可以是http\https\ftp\file等。

scheme-specific-part是其餘部分。

進一步細分:

[scheme:][//authority][path][?query][#fragment]

常用方法:

  • getScheme()獲得scheme;
  • getSchemeSpecificPart()
  • getPath()
  • getAuthority()

(2)相對標識符和絕對標識符的轉換

resolve和relative函數。

示例代碼:

任務1:取得特定網址的html代碼。

任務2:分析地址信息。

任務3:絕對地址和相對地址轉換

[java] view plaincopy
  1. package org.core;  
  2.   
  3. import java.net.*;  
  4. import java.util.*;  
  5.   
  6. public class URLTest1 {  
  7.   
  8.     public static void main(String[] args) throws Exception {  
  9.         URL url = new URL("http://www.ecnu.edu.cn");  
  10.         Scanner in = new Scanner(url.openStream());  
  11.         while (in.hasNextLine()) {  
  12.             String str = in.nextLine();  
  13.             System.out.println(str);  
  14.         }  
  15.   
  16.         URI uri = new URI("http://blog.csdn.net/xiazdong");  
  17.         System.out.println(uri.getScheme());  
  18.         System.out.println(uri.getSchemeSpecificPart());  
  19.         System.out.println(uri.getAuthority());  
  20.         System.out.println(uri.getUserInfo());  
  21.         System.out.println(uri.getHost());  
  22.         System.out.println(uri.getPort());  
  23.         System.out.println(uri.getPath());  
  24.         System.out.println(uri.getQuery());  
  25.         System.out.println(uri.getFragment());  
  26.   
  27.         String str = "/article/details/6705033";  
  28.         URI combined = uri.resolve(str);// 根據uri的路徑把str變成絕對地址  
  29.         System.out.println(combined.getScheme()  
  30.                 + combined.getSchemeSpecificPart());  
  31.   
  32.         URI relative = uri.relativize(new URI(str));  
  33.         System.out.println(relative.getSchemeSpecificPart());  
  34.   
  35.     }  
  36.   
  37. }  


URL和URLConnection

URL的作用

1.如果想要獲取某個網頁的html源代碼,比如http://blog.csdn.net/xiazdong 則只需要:

(1)URL url = new URL("http://blog.csdn.net/xiazdong");

(2)Scanner in = new Scanner(url.openStream());

即可.

2.獲取消息頭信息

  • URLConnection connection = url.openConnection();
  • connection.getHeaderFields()返回一個Map<String,List<String>>
  • connection.getContentLength();
  • connection.getContentType();
  • connection.setDoOutput(true)獲得輸出流
  • connection.getOutputStream();
  • connection.getInputStream();

代碼示例:

[java] view plaincopy
  1. package org.core;  
  2.   
  3. import java.net.*;  
  4. import sun.misc.*;  
  5. import java.util.*;  
  6. import java.io.*;  
  7.   
  8. public class URLConnectionTest {  
  9.   
  10.     public static void main(String[] args) throws Exception {  
  11.         String urlName = "http://java.sun.com";  
  12.         URL url = new URL(urlName);  
  13.         URLConnection connection = url.openConnection();  
  14.         Map<String, List<String>> map = connection.getHeaderFields();  
  15.         for (Map.Entry<String, List<String>> entry : map.entrySet()) {  
  16.             String key = entry.getKey();  
  17.             List<String> value = entry.getValue();  
  18.             System.out.println(key + ":" + value);  
  19.         }     
  20.   
  21.     }  
  22.   
  23. }  



在網頁中如果要提交數據給web服務器,通常要把數據發送給web服務器,然後web服務器委派一個腳本對數據進行處理,返回一個相應。

通常發送數據的方法有兩種:get和post。

(1)get方法是直接把數據跟在url的後面,以name=value進行傳輸,

每個數據之間用&進行分割,value中的空格用+替換,非字母數字用%替換,並後跟兩個16進制數,這種編碼方式稱爲URL編碼。URLEncoder和URLDecoder

(2)post方法是通過URLConnection發送給服務器,編碼方式和get一樣。URLEncoder.encode(VALUE,"UTF-8");

一般在傳輸中文時會運用編碼和解碼。

示例:通過URLEncoder和URLDecoder編碼和解碼


InetAddress  根據域名得到IP地址或名稱

沒有構造方法,通過:

(1)InetAddress i1 = InetAddress.getByName(String)返回一個InetAddress實例。

(2)如果一個地址有多個ip地址,比如google,有3個ip地址,就調用InetAddress[] i2 = InetAddress.getAllByName(String);

InetAddress.getLocalhost()獲得本機的InetAddress實例。

代碼實例:

 

[java] view plaincopy
  1. package org.core;  
  2.   
  3. import java.net.InetAddress;  
  4.   
  5. public class InetAddressTest {  
  6.   
  7.     public static void main(String[] args) throws Exception{  
  8.         InetAddress local = InetAddress.getLocalHost();  
  9.         System.out.println("本機地址:"+local.getHostAddress());  
  10.         System.out.println("本機名稱:"+local.getHostName());  
  11.         InetAddress[] remote = InetAddress.getAllByName("www.google.com");  
  12.         for(InetAddress a : remote)  
  13.         {  
  14.             System.out.println("地址:"+a.getHostAddress());  
  15.             System.out.println("名稱:"+a.getHostName());  
  16.         }     
  17.     }  
  18. }  


 

 

Socket(TCP)

Socket是一個用於機器之間通信的類。

Socket客戶端:

(1)Socket s = new Socket(ip,port);打開一個套接字,發送請求    

(2)InputStream istream = s.getInputStream();接收數據

(3)OutputStream ostream  = s.getOutputStream();發送數據

需要用PrintWriter和Scanner進行包裝,並且注意PrintWriter的自動緩衝。

Socket服務器:注意多個客戶端同時訪問服務器的問題:多線程

(1)ServerSocket server = new ServerSocket(port);創建一個端口

(2)Socket s = server.accept();        只有當有客戶端請求並連接,函數纔會返回

(3)InputStream istream = s.getInputStream();接收數據

(4)OutputStream ostream  = s.getOutputStream();發送數據

需要用PrintWriter和Scanner進行包裝,並且注意PrintWriter的自動緩衝。

我們在使用PrintWriter時需要使用println()函數;


當服務器或客戶端任意一方請求結束通信,則立刻停止。

問題1:在套接字中會發生阻塞的地方:

(1)實例化Socket時,會阻塞。

(2)在in.nextLine()類似操作時會阻塞。

解決方法:

(1)對於第一個問題,解決方法:

  • Socket s = new Socket();建立無連接socket
  • s.connect(new InetSocketAddress(host,port),timeout);設置超時。

(2)對於第二個問題,解決方法是設置s.setSoTimeout(long)設置超時時間

問題2:當客戶端想要關閉套接字時,但卻不能確定服務器是否還在發送數據,但是隻要一關閉就立刻斷開。

解決方法:

socket.shutdownOutput()關閉輸出流

socket.shutdownInput()關閉輸入流

半關閉示例代碼:客戶端發送hello給服務器,同時關閉輸出流,服務器接收到後關閉輸入流,等待5秒發送ECHO hello給客戶端。

 Client:

[java] view plaincopy
  1. import java.net.*;  
  2. import java.io.*;  
  3. import java.util.*;  
  4. public class shutdownOutputClient {  
  5.   
  6.     public static void main(String[] args)throws Exception {  
  7.         Socket s = new Socket("localhost",8819);  
  8.         Scanner in = new Scanner(s.getInputStream());  
  9.         PrintWriter out = new PrintWriter(s.getOutputStream(),true);  
  10.         out.println("Hello");//輸出hello  
  11.         s.shutdownOutput(); //關閉輸出流  
  12.         System.out.println("關閉連接");  
  13.         while(in.hasNextLine()){  
  14.             System.out.println(in.nextLine());  
  15.         }  
  16.         s.close();  
  17.     }  
  18.   
  19. }  

Server:

[java] view plaincopy
  1. import java.net.*;  
  2. import java.io.*;  
  3. import java.util.*;  
  4. public class shutdownOutputServer {  
  5.   
  6.     public static void main(String[] args)throws Exception {  
  7.         ServerSocket server = new ServerSocket(8819);  
  8.         Socket s = server.accept();  
  9.         Scanner in = new Scanner(s.getInputStream());  
  10.         PrintWriter out = new PrintWriter(s.getOutputStream(),true);  
  11.         String str = in.nextLine();  
  12.         System.out.println(str);  
  13.         s.shutdownInput();  
  14.         System.out.println("關閉輸入流");  
  15.           
  16.         Thread.sleep(5000);  
  17.         out.println("Echo:"+str);  
  18.         s.close();  
  19.     }  
  20.   
  21. }  


綜合代碼舉例:實現一個簡單的對等通信程序,通過多線程,一個線程接收數據,一個線程發送數據。

用戶1:

[java] view plaincopy
  1. import java.util.*;  
  2. import java.io.*;  
  3. import java.net.*;  
  4. public class Client{  
  5.     public static void main(String[]args)throws Exception{  
  6.         Socket s = new Socket("localhost",8819);  
  7.         PrintWriter out = new PrintWriter(s.getOutputStream(),true);  
  8.         Thread t = new Thread(new Receive(s));  
  9.         t.start();  
  10.         //以下代碼用於發送數據  
  11.         Scanner in = new Scanner(System.in);//鍵盤輸入  
  12.         while(in.hasNextLine()){    //一直不斷  
  13.             out.println(in.nextLine()); //發送鍵盤輸入數據  
  14.         }  
  15.     }  
  16. }  
  17. class Receive implements Runnable   //這個類用於接收數據  
  18. {  
  19.     private Socket s;  
  20.     public Receive(Socket s)  
  21.     {  
  22.         this.s = s;  
  23.     }  
  24.     public void run()  
  25.     {  
  26.         try{  
  27.             Scanner in = new Scanner(s.getInputStream());   //in:接收數據  
  28.           
  29.             String str = null;  
  30.             while(true)  
  31.             {  
  32.                 str = in.nextLine();      
  33.                 System.out.println("服務器說:"+str);    //打印接收數據  
  34.             }  
  35.               
  36.         }  
  37.         catch(Exception e){}  
  38.     }  
  39. }  

用戶2:

[java] view plaincopy
  1. import java.util.*;  
  2. import java.io.*;  
  3. import java.net.*;  
  4. public class Server{  
  5.     public static void main(String[]args)throws Exception{  
  6.         ServerSocket server = new ServerSocket(8819);  
  7.         Socket s = server.accept();  
  8.         PrintWriter out = new PrintWriter(s.getOutputStream(),true);  
  9.         Thread t = new Thread(new Receive1(s));  
  10.         t.start();  
  11.         //以下代碼用於發送數據  
  12.         Scanner in = new Scanner(System.in);//鍵盤輸入  
  13.         while(in.hasNextLine()){    //一直不斷  
  14.             out.println(in.nextLine()); //發送鍵盤輸入數據  
  15.         }  
  16.     }  
  17. }  
  18. class Receive1 implements Runnable  //這個類用於接收數據  
  19. {  
  20.     private Socket s;  
  21.     public Receive1(Socket s)  
  22.     {  
  23.         this.s = s;  
  24.     }  
  25.     public void run()  
  26.     {  
  27.         try{  
  28.             Scanner in = new Scanner(s.getInputStream());   //in:接收數據  
  29.           
  30.             String str = null;  
  31.             while(true)  
  32.             {  
  33.                 str = in.nextLine();      
  34.                 System.out.println("客戶端說:"+str);    //打印接收數據  
  35.             }  
  36.               
  37.         }  
  38.         catch(Exception e){}  
  39.     }  
  40. }  


以上的程序屬於C/S,需要同時維護客戶端和服務器的代碼。

B/S:瀏覽器和服務器,只需要維護一方代碼即可。


聊天工具使用UDP非常多,因爲我們通常也會遇到我們發給另一個人一條消息,另一個人卻沒有收到的情況。

DatagramPacket和DatagramSocket  數據報

代碼舉例:實現服務器發送數據報到客戶端。

Client:

[java] view plaincopy
  1. package org.core;  
  2.   
  3. import java.net.*;  
  4. import java.io.*;  
  5. public class DatagramClient {  
  6.   
  7.     public static void main(String[] args) throws Exception{  
  8.         byte[]buf = new byte[1024];  
  9.         DatagramPacket packet = new DatagramPacket(buf,1024);  
  10.         DatagramSocket client = new DatagramSocket(9000);  
  11.         client.receive(packet);  
  12.         String str = new String(buf,0,packet.getLength());  
  13.         System.out.println(packet.getAddress().getHostName()+":"+str);  
  14.         client.close();  
  15.     }  
  16.   
  17. }  

Server:

[java] view plaincopy
  1. package org.core;  
  2.   
  3. import java.net.DatagramPacket;  
  4. import java.net.DatagramSocket;  
  5. import java.net.InetAddress;  
  6.   
  7. public class DatagramServer {  
  8.   
  9.     public static void main(String[] args)throws Exception {  
  10.         DatagramSocket server = new DatagramSocket(3000);  
  11.         String str = "hello world";  
  12.         DatagramPacket packet = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getLocalHost(),9000);  
  13.         server.send(packet);  
  14.         server.close();  
  15.     }  
  16. }  

QQ聊天應用

Server端

[java] view plaincopy
  1. package org.xiazdong.server;  
  2.   
  3.   
  4. import java.awt.BorderLayout;  
  5. import java.awt.event.ActionEvent;  
  6. import java.awt.event.ActionListener;  
  7. import java.io.BufferedReader;  
  8. import java.io.InputStreamReader;  
  9. import java.io.PrintStream;  
  10. import java.net.InetAddress;  
  11. import java.net.ServerSocket;  
  12. import java.net.Socket;  
  13.   
  14. import javax.swing.JButton;  
  15. import javax.swing.JFrame;  
  16. import javax.swing.JPanel;  
  17. import javax.swing.JScrollPane;  
  18. import javax.swing.JTextArea;  
  19. import javax.swing.JTextField;  
  20.   
  21. public class Server3 extends JFrame{  
  22.     static JTextArea area;  
  23.     JTextField field;  
  24.     JButton button;  
  25.     static PrintStream writer;  
  26.     public Server3(){  
  27.         this.setTitle("服務器");  
  28.         this.setSize(400,500);  
  29.         area = new JTextArea(25,30);  
  30.         area.setEditable(false);  
  31.         field = new JTextField(20);  
  32.         button = new JButton("提交");  
  33.         JPanel panel = new JPanel();  
  34.         JScrollPane sp = new JScrollPane(area);  
  35.         this.add(sp,BorderLayout.CENTER);  
  36.         panel.add(field);  
  37.         panel.add(button);  
  38.         this.add(panel,BorderLayout.SOUTH);  
  39.         this.setVisible(true);  
  40.         this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  41.           
  42.         button.addActionListener(new ActionListener(){  
  43.   
  44.             @Override  
  45.             public void actionPerformed(ActionEvent e) {  
  46.                 String text = field.getText();  
  47.                 writer.println(text);  
  48.                 area.append("我:"+text+"\n");  
  49.                 field.setText("");  
  50.             }  
  51.               
  52.         });  
  53.     }  
  54.     public static void main(String[] args) throws Exception {  
  55.         Server3 s = new Server3();  
  56.         ServerSocket server = new ServerSocket(8899);  
  57.         System.out.println("開始監聽...");  
  58.         Socket socket = server.accept();  
  59.         InetAddress address = socket.getInetAddress();  
  60.         String name = address.getLocalHost().getHostName();  
  61.         System.out.println(name+"已連接");  
  62.         BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
  63.         writer = new PrintStream(socket.getOutputStream(), true);  
  64.         while (true) {  
  65.             String line = null;  
  66.             line = reader.readLine();  
  67.             if (line != null) {   
  68.                     area.append("客戶端:"+line+"\n");  
  69.             }  
  70.   
  71.         }  
  72.     }  
  73.   
  74. }  

Client端

[java] view plaincopy
  1. package org.xiazdong.client;  
  2. import java.awt.BorderLayout;  
  3. import java.awt.event.ActionEvent;  
  4. import java.awt.event.ActionListener;  
  5. import java.io.BufferedReader;  
  6. import java.io.InputStreamReader;  
  7. import java.io.OutputStream;  
  8. import java.io.PrintStream;  
  9. import java.io.PrintWriter;  
  10. import java.net.Socket;  
  11.   
  12. import javax.swing.JButton;  
  13. import javax.swing.JFrame;  
  14. import javax.swing.JPanel;  
  15. import javax.swing.JScrollPane;  
  16. import javax.swing.JTextArea;  
  17. import javax.swing.JTextField;  
  18.   
  19. public class Client3 extends JFrame{  
  20.   
  21.     static JTextArea area;  
  22.     JTextField field;  
  23.     JButton button;  
  24.     static PrintWriter writer;  
  25.     public Client3(){  
  26.         this.setTitle("客戶端");  
  27.         this.setSize(400,500);  
  28.         area = new JTextArea(25,30);  
  29.         area.setEditable(false);  
  30.         field = new JTextField(20);  
  31.         button = new JButton("提交");  
  32.         JScrollPane sp = new JScrollPane(area);  
  33.         JPanel panel = new JPanel();  
  34.         this.add(sp,BorderLayout.CENTER);  
  35.         panel.add(field);  
  36.         panel.add(button);  
  37.         this.add(panel,BorderLayout.SOUTH);  
  38.         this.setVisible(true);  
  39.         this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  40.           
  41.         button.addActionListener(new ActionListener(){  
  42.   
  43.             @Override  
  44.             public void actionPerformed(ActionEvent e) {  
  45.                 String text = field.getText();  
  46.                 writer.println(text);  
  47.                 area.append("我:"+text+"\n");  
  48.                 field.setText("");  
  49.             }  
  50.               
  51.         });  
  52.     }  
  53.     public static void main(String[] args) throws Exception{  
  54.         Client3 c = new Client3();  
  55.         Socket socket = new Socket("127.0.0.1",8899);  
  56.         OutputStream out = socket.getOutputStream();  
  57.         BufferedReader reader1 = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
  58.         writer = new PrintWriter(out,true);  
  59.         System.out.println("已經成功和服務器連接...");  
  60.         while(true){  
  61.             String line = reader1.readLine();  
  62.             area.append("服務器:"+line+"\n");  
  63.         }  
  64.     }  
  65.   
  66. }  


PrintWriter的autoflush

 

如果PrintWriter writer = new PrintWriter(out,true);

則調用println()、printf()、format()函數時會自動刷新,其他函數都不會,比如write()、print()函數時不會自動刷新

 

 網絡編程常見異常


第1個異常是java.net.BindException:Address already in use: JVM_Bind。該異常發生在服務器端進行new ServerSocket(port)(port是一個0,65536的整型值)操作時。異常的原因是以爲與port一樣的一個端口已經被啓動,並進行監聽。此時用netstat –an命令,可以看到一個Listending狀態的端口。只需要找一個沒有被佔用的端口就能解決這個問題。 

第2個異常是java.net.ConnectException: Connection refused: connect。該異常發生在客戶端進行new Socket(ip, port)操作時,該異常發生的原因是或者具有ip地址的機器不能找到(也就是說從當前機器不存在到指定ip路由),或者是該ip存在,但找不到指定的端口進行監聽。出現該問題,首先檢查客戶端的ip和port是否寫錯了,如果正確則從客戶端ping一下服務器看是否能ping通,如果能ping通(服務服務器端把ping禁掉則需要另外的辦法),則看在服務器端的監聽指定端口的程序是否啓動,這個肯定能解決這個問題。 

第3個異常是java.net.SocketException: Socket is closed,該異常在客戶端和服務器均可能發生。異常的原因是己方主動關閉了連接後(調用了Socket的close方法)再對網絡連接進行讀寫操作。 

第4個異常是java.net.SocketException: (Connection reset或者Connect reset by peer:Socket write error)。該異常在客戶端和服務器端均有可能發生,引起該異常的原因有兩個,第一個就是如果一端的Socket被關閉(或主動關閉或者因爲異常退出而引起的關閉),另一端仍發送數據,發送的第一個數據包引發該異常(Connect reset by peer)。另一個是一端退出,但退出時並未關閉該連接,另一端如果在從連接中讀數據則拋出該異常(Connection reset)。簡單的說就是在連接斷開後的讀和寫操作引起的。 

第5個異常是java.net.SocketException: Broken pipe。該異常在客戶端和服務器均有可能發生。在第4個異常的第一種情況中(也就是拋出SocketExcepton:Connect reset by peer:Socket write error後),如果再繼續寫數據則拋出該異常。前兩個異常的解決方法是首先確保程序退出前關閉所有的網絡連接,其次是要檢測對方的關閉連接操作,發現對方關閉連接後自己也要關閉該連接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章