Java--Socket通信


下面內容是Java開發內容的高級知識點,需要對Java中的面向對象、IO、多線程、以及網絡相關知識有一定的基礎。(知識永遠都有深度,本章節長期更新內容)

 

1、網絡基礎知識

 

網絡通信的條件:1、兩個通信的端都要有各自的IP地址作為唯一標識,簡單的來說IP地址用來區分不同機器(計算機)。2、語言要相通。3、如何辨別不同程序的通信需要端口號來區別,簡單的來說端口號就是用來標識不同的應用程序。

TCP/IP是目前世界上使用最廣泛的協議,是以TCP和IP為基礎的不同層次上多個協議的集合,也稱TCP/IP協議族 或 TCP/IP 協議棧。

TCP:Transmission Control Protocol 傳輸控制協議
IP:Internet Protocol 互聯網協議

TCP/IP模型

 

>>IP和端口<<
1、用於區分不同應用程序。
2、端口號范圍為0~65535,其中0~1023為系統所保留。如果自定義端口號,所以建議用1024之后的端口號。
3、IP地址和端口號組成了所謂的Socket,Socket是網絡上運行程序之間雙向通信鏈路的終結點,是TCP和UDP的基礎。


常用的端口號需要記一下:http:80    ftp:21    telnet:23

 

——————————Java中的網絡支持—————————
針對網絡通信的不同層次,Java提供的網絡功能有四大類:
>>1、InetAddress:用於標識網絡上的硬件資源
>>2、URL:統一資源定位符 通過URL可以直接讀取或寫入網絡上的數據
>>3、Socket:使用TCP協議實現網絡通信的Socket相關的類。
>>4、Datagram:使用UDP協議,將數據保存在數據報中,通過網絡進行通信。

 


2、InetAddress類

查看I-net-Address的API文檔,發現沒有構造方法,也就是不能通過new來創建。所以肯定有靜態的方法來創建。

 1 import java.net.InetAddress;
 2 import java.net.UnknownHostException;
 3 import java.util.Arrays;
 4 
 5 public class Test1{
 6     public static void main(String[] args) throws UnknownHostException{
 7         // 獲取本機的InetAdresss實例
 8         InetAddress address = InetAddress.getLocalHost();
 9         System.out.println("計算機名:"+address.getHostName()+"\nIP地址:"+address.getHostAddress());
10 
11         // 獲取字節數組形式的IP地址
12         byte[] bytes = address.getAddress();
13         System.out.println("字節數組形式的IP:"+Arrays.toString(bytes));
14         System.out.println(address);
15 
16         // 也可以通過機器名來獲取InewAdress
17         InetAddress address2 = InetAddress.getByName("MacBook-Air-2.local");
18         System.out.println("通過計算機名字創建的InetAddress對象:"+address2);
19         System.out.println("計算機名:"+address2.getHostName());
20         System.out.println("IP地址:"+address2.getHostAddress());
21 
22         // 也可以通過IP地址來獲取InewAdress
23         InetAddress address3 = InetAddress.getByName("192.168.1.102");
24         System.out.println("通過計算機IP地址創建的InetAddress對象:"+address3);
25         System.out.println("計算機名:"+address3.getHostName());
26         System.out.println("IP地址:"+address3.getHostAddress());
27 
28 
29     }
30 }

輸出結果:

 

 
3、URL

 URL(Uniform Resource Locator)統一資源定位符,表示Internet上某一資源的地址。 俗稱就是網址。

 URL由兩部分組成:協議名稱+資源名稱。

 在Java.net包中,提供了URL類來表示URL。

 1 import java.net.MalformedURLException;
 2 import java.net.URL;
 3 
 4 public class Test1{
 5     public static void main(String[] args){
 6         
 7         try {
 8             // 創建一個URL實例
 9             URL imoocURL = new URL("http://www.imooc.com");
10             URL url = new URL(imoocURL,"/index.html?username=tom#test");
11             // ?后面表示參數,#后面表示的是錨點
12 
13             // 創建URL對象之后,可以根據這個對象獲取相關的信息
14             System.out.println("協議:"+url.getProtocol());
15             System.out.println("主機:"+url.getHost());
16             // 如果未指定端口號,則使用默認的端口號,此時getPort()方法返回值為-1
17             System.out.println("端口:"+url.getPort());
18             System.out.println("文件路徑:"+url.getPath());
19             System.out.println("文件名:"+url.getFile());
20             System.out.println("相對路徑:"+url.getRef());// 實際上就是#錨點后面的內容
21             System.out.println("查詢字符串:"+url.getQuery());
22 
23         } catch (MalformedURLException e) {
24             e.printStackTrace();
25         }
26 
27 
28     }
29 }

輸出:

 下面再通過URL讀取網頁內容:

 1 import java.net.MalformedURLException;
 2 import java.net.URL;
 3 import java.io.InputStream;
 4 import java.io.InputStreamReader;
 5 import java.io.BufferedReader;
 6 import java.io.IOException;
 7 
 8 /*
 9  * 使用URL讀取網頁頁面內容
10  */
11 public class Test1{
12     public static void main(String[] args){
13         
14         try {
15             // 創建一個URL實例
16             URL url = new URL("http://www.baidu.com");
17             // 通過URL的openStream方法獲取URL對象所表示的資源的字節輸入流
18             InputStream is = url.openStream();
19             // 將字節輸入流轉換為字符輸入流
20             InputStreamReader isr = new InputStreamReader(is,"utf-8");// 如果沒有指明編碼可能會出現中文亂碼
21             // 為字符輸入流添加緩沖
22             BufferedReader br = new BufferedReader(isr);
23             String data = br.readLine();// 讀取數據
24             while(data != null){
25                 System.out.println(data);// 輸出數據
26                 data = br.readLine();
27             }
28             // 最后按照上面對象倒序關閉
29             br.close();
30             isr.close();
31             is.close();
32         } catch (MalformedURLException e) {
33             e.printStackTrace();
34         } catch (IOException e) {
35             e.printStackTrace();
36         } 
37     }
38 }

輸入:

 


4、TCP編程

4-1、Socket簡介

TCP協議是面向連接、可靠的、有序的,以字節流的方式發送數據
基於TCP協議實現網絡通信的類:
>>1、客戶端的Socket類
>>2、服務器端的ServerSocket類

基於Socket的TCP通信模型

 

 Socket通信實現步驟:1、創建ServerSocket和Socket。2、打開連接到Socket的輸入/輸出流。3、按照協議對Socket進行讀/寫操作。4、關閉輸入輸出流、關閉Socket。
通過在線API文檔:http://tool.oschina.net/apidocs/apidoc?api=jdk-zh 中查詢到的結果:

4-2、編程實現基於TCP的Socket服務器端和客戶端的通信

服務器端:
    1、創建ServerSocket對象,綁定監聽端口。
    2、通過accept()方法監聽客戶端請求。
    3、鏈接建立后,通過輸入流讀取客戶端發送的請求信息。
    4、通過輸出流向客戶端發送響應信息。
    5、關閉相關資源。

 1 package com.heyang;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStream;
 6 import java.io.InputStreamReader;
 7 import java.net.ServerSocket;
 8 import java.net.Socket;
 9 
10 /*
11  * 基於TCP協議的Socket通信,實現用戶登錄
12  * 服務器端
13  */
14 public class Server {
15     public static void main(String[] args) {
16         try {
17             // 1、創建一個服務器Socket,即ServerSocket,指定綁定的端口,並監聽此端口
18             ServerSocket serverSocket = new ServerSocket(8888);
19             // 2、調用()方法開始監聽,等待客戶端的連接
20             System.out.println("***服務器即將啟動,等待客戶端的連接***");
21             Socket socket = serverSocket.accept();// 就會處於阻塞的狀態,等待監聽
22             // 3、獲取輸入流,病讀取客戶端信息
23             InputStream is = socket.getInputStream();// 字節輸入流
24             // 將字節流轉換為字符流
25             InputStreamReader isr = new InputStreamReader(is);
26             // 為輸入流添加緩沖
27             BufferedReader br = new BufferedReader(isr);
28             String info = null;
29             while((info = br.readLine())!=null){
30                 System.out.println("我是服務器,讀取客戶端發過來的信息:"+info);
31             }
32             socket.shutdownInput();//關閉輸入流
33             
34             // 關閉資源
35             br.close();
36             isr.close();
37             is.close();
38             socket.close();
39             serverSocket.close();
40             
41         } catch (IOException e) {
42             // TODO Auto-generated catch block
43             e.printStackTrace();
44         }
45     }
46 }



客戶端:
    1、創建Socket對象,指明需要連接的服務器的地址和端口號。
    2、連接建立后,通過輸出流向服務器端發送請求信息。
    3、通過輸入流獲取服務器響應的信息。
    4、關閉相關資源。

 1 package com.heyang;
 2 
 3 import java.io.IOException;
 4 import java.io.OutputStream;
 5 import java.io.PrintWriter;
 6 import java.net.Socket;
 7 import java.net.UnknownHostException;
 8 
 9 /*
10  * 客戶端
11  */
12 public class Client {
13     public static void main(String[] args) {
14         // 1、創建客戶端Socket,指定服務器地址和端口
15         try {
16             Socket  socket = new Socket("localhost", 8888);
17             // 2、獲取輸出流,向服務器端發送信息
18             OutputStream os = socket.getOutputStream();// 獲取字節輸出流
19             // 將輸出流包裝為打印流
20             PrintWriter pw = new PrintWriter(os);
21             pw.write("用戶名:admin 密碼:123");
22             pw.flush();
23             socket.shutdownInput();//關閉輸出流
24             
25             // 3、關閉資源
26             pw.close();
27             os.close();
28             socket.close();
29             
30         } catch (UnknownHostException e) {
31             // TODO Auto-generated catch block
32             e.printStackTrace();
33         } catch (IOException e) {
34             // TODO Auto-generated catch block
35             e.printStackTrace();
36         }
37     }
38 }

輸出:

 

 4-3、完善客戶端登陸之后服務器響應客戶端

代碼邏輯深化分析:

  在前面簡單的代碼的基礎上,我們需要完善客戶端登陸之后服務器響應客戶端的邏輯。

  事實上,站在客戶端的角度,對外(這里指的是服務器)發出數據流,也就是需要用輸出流來輸出數據流。

  反過來,站在服務器端的角度,就要不斷的監聽外部(這里指的是外部客戶端)輸入進來的數據流,所以就需要輸入流來接收輸入進來的數據流。

  那么,當服務器端收到客戶端傳輸過來的數據流之后,就應該響應,那么這時候,站在服務器端的角度,要對外進行響應,就需要用輸出流來輸出響應回饋的信息,這時候就和客戶端一開始的輸出流代  碼一致了。

  回到客戶端,由於需要接收和讀取服務器端的發出的響應,就需要輸入流來接收服務器發過來的回饋信息了,這時候就和服務器端一開始的輸入流代碼一致了。

 

服務器代碼:

 1 package com.heyang;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStream;
 6 import java.io.InputStreamReader;
 7 import java.io.OutputStream;
 8 import java.io.PrintWriter;
 9 import java.net.ServerSocket;
10 import java.net.Socket;
11 
12 /*
13  * 基於TCP協議的Socket通信,實現用戶登錄
14  * 服務器端
15  */
16 public class Server {
17     public static void main(String[] args) {
18         try {
19             // 1、創建一個服務器Socket,即ServerSocket,指定綁定的端口,並監聽此端口
20             ServerSocket serverSocket = new ServerSocket(8888);
21             // 2、調用()方法開始監聽,等待客戶端的連接
22             System.out.println("***服務器即將啟動,等待客戶端的連接***");
23             Socket socket = serverSocket.accept();// 就會處於阻塞的狀態,等待監聽
24             // 3、獲取輸入流,病讀取客戶端信息
25             InputStream is = socket.getInputStream();// 字節輸入流
26             // 將字節流轉換為字符流
27             InputStreamReader isr = new InputStreamReader(is);
28             // 為輸入流添加緩沖
29             BufferedReader br = new BufferedReader(isr);
30             String info = null;
31             while((info = br.readLine())!=null){
32                 System.out.println("我是服務器,讀取客戶端發過來的信息:"+info);
33             }
34             socket.shutdownInput();//關閉輸入流
35             
36             // 4、作為服務器端,就需要響應客戶端的請求,使用輸出流來響應
37             OutputStream os = socket.getOutputStream();
38             PrintWriter pw = new PrintWriter(os);
39             pw.write("歡迎您!");
40             pw.flush();//調用flush()方法將緩沖輸出
41             
42             
43             // 5、關閉資源
44             pw.close();
45             os.close();
46             br.close();
47             isr.close();
48             is.close();
49             socket.close();
50             serverSocket.close();
51             
52         } catch (IOException e) {
53             // TODO Auto-generated catch block
54             e.printStackTrace();
55         }
56     }
57 }

客戶端代碼:

 1 package com.heyang;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStream;
 6 import java.io.InputStreamReader;
 7 import java.io.OutputStream;
 8 import java.io.PrintWriter;
 9 import java.net.Socket;
10 import java.net.UnknownHostException;
11 
12 /*
13  * 客戶端
14  */
15 public class Client {
16     public static void main(String[] args) {
17         // 1、創建客戶端Socket,指定服務器地址和端口
18         try {
19             Socket  socket = new Socket("localhost", 8888);
20             // 2、獲取輸出流,向服務器端發送信息
21             OutputStream os = socket.getOutputStream();// 獲取字節輸出流
22             // 將輸出流包裝為打印流
23             PrintWriter pw = new PrintWriter(os);
24             pw.write("用戶名:admin 密碼:123");
25             pw.flush();
26             socket.shutdownOutput();//關閉輸出流
27             
28             // 3、獲取輸入流,並讀取服務器端的響應信息
29             InputStream is = socket.getInputStream();
30             BufferedReader br = new BufferedReader(new InputStreamReader(is));
31             String info = null;
32             while((info = br.readLine())!=null){
33                 System.out.println("我是客戶端,服務器跟我說:"+info);
34             }
35             
36             // 4、關閉資源
37             br.close();
38             is.close();
39             pw.close();
40             os.close();
41             socket.close();
42             
43         } catch (UnknownHostException e) {
44             // TODO Auto-generated catch block
45             e.printStackTrace();
46         } catch (IOException e) {
47             // TODO Auto-generated catch block
48             e.printStackTrace();
49         }
50     }
51 }

具體運行結果就不展示了。

 

4-4、使用多線程實現多客戶端的通信

 事實上,以上只實現了單個客戶端和單個服務器端進行socket通信。那么問題來了,實際應用程序是由一個服務器持續不斷的運行中,然后由多個客戶端異步通過服務器進行客戶端之間的收發信息,這該如何實現呢?

 基本步驟:

  1、服務器端創建ServerSocket,循環調用accept()等待客戶端連接。

  2、客戶端創建一個socket並請求和服務器端連接。

  3、服務器端接受客戶端請求,創建socket與該客戶端建立專線連接。

  4、建立連接的兩個socket在一個單獨的線程上對話。

  5、服務器端繼續等待新的連接。

服務器端的代碼:

 1 package com.heyang;
 2 
 3 
 4 import java.io.IOException;
 5 import java.net.InetAddress;
 6 import java.net.ServerSocket;
 7 import java.net.Socket;
 8 import com.heyang.ServerThread;;
 9 /*
10  * 基於TCP協議的Socket通信,實現用戶登錄
11  * 服務器端
12  */
13 public class Server {
14     public static void main(String[] args) {
15         try {
16             // 1、創建一個服務器Socket,即ServerSocket,指定綁定的端口,並監聽此端口
17             ServerSocket serverSocket = new ServerSocket(8888);
18             // 2、調用()方法開始監聽,等待客戶端的連接
19             
20             // 記錄客戶端的數量
21             int count = 0;
22             System.out.println("***服務器即將啟動,等待客戶端的連接***");
23             
24             while(true){
25                 // 調用accept()方法開始監聽,等待客戶端的鏈接
26                 Socket socket = serverSocket.accept();
27                 // 創建一個新的線程
28                 ServerThread serverThread = new ServerThread(socket);
29                 // 啟動線程·
30                 serverThread.start();
31                 
32                 count++;
33                 System.out.println("客戶端連接的數量:"+count+"個");
34                 
35                 // 獲取客戶端的IP地址等信息
36                 InetAddress address = socket.getInetAddress();
37                 System.out.println("當前客戶端的IP:"+address.getHostAddress());
38                 
39             }
40             
41             // 需要死循環持續監聽客戶端的信息發送
42 //            serverSocket.close();
43             
44         } catch (IOException e) {
45             // TODO Auto-generated catch block
46             e.printStackTrace();
47         }
48     }
49 }

服務器線程代碼:

 1 package com.heyang;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStream;
 6 import java.io.InputStreamReader;
 7 import java.io.OutputStream;
 8 import java.io.PrintWriter;
 9 import java.net.Socket;
10 
11 /*
12  * 服務器端 線程處理類 
13  */
14 public class ServerThread extends Thread {
15     // 創建和本線程相關的socket
16     Socket socket = null;
17     
18     public ServerThread(Socket socket){
19         this.socket = socket;
20     }
21     
22     // 指向線程的操作,響應服務器端的請求
23     public void run(){
24         
25         InputStream is = null;
26         InputStreamReader isr = null;
27         BufferedReader br = null;
28         OutputStream os = null;
29         PrintWriter pw = null;
30         try {
31             // 3、獲取輸入流,病讀取客戶端信息
32             is = socket.getInputStream();// 字節輸入流
33             // 將字節流轉換為字符流
34             isr = new InputStreamReader(is);
35             // 為輸入流添加緩沖
36             br = new BufferedReader(isr);
37             String info = null;
38             while ((info = br.readLine()) != null) {
39                 System.out.println("我是服務器,讀取客戶端發過來的信息:" + info);
40             }
41             socket.shutdownInput();//關閉輸入流
42 
43             // 獲取輸出流
44             os = socket.getOutputStream();
45             pw = new PrintWriter(os);
46             pw.write("歡迎您!");
47             pw.flush();//調用flush()方法將緩沖輸出
48         } catch (Exception e) {
49             // TODO: handle exception
50         }finally{
51             try {
52                 // 5、關閉資源
53                 if (pw != null) {
54                     pw.close();
55                 }
56                 if (os != null) {
57                     os.close();
58                 }
59                 if (br != null) {
60                     br.close();
61                 }
62                 if (isr != null) {
63                     isr.close();
64                 }
65                 if (is != null) {
66                     is.close();
67                 }
68                 if (socket != null) {
69                     socket.close();
70                 } 
71             } catch (IOException e2) {
72                 // TODO: handle exception
73             }
74         }
75         
76         
77     }
78     
79 }

客戶端代碼:

 1 package com.heyang;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStream;
 6 import java.io.InputStreamReader;
 7 import java.io.OutputStream;
 8 import java.io.PrintWriter;
 9 import java.net.Socket;
10 import java.net.UnknownHostException;
11 
12 /*
13  * 客戶端
14  */
15 public class Client {
16     public static void main(String[] args) {
17         // 1、創建客戶端Socket,指定服務器地址和端口
18         try {
19             Socket  socket = new Socket("localhost", 8888);
20             // 2、獲取輸出流,向服務器端發送信息
21             OutputStream os = socket.getOutputStream();// 獲取字節輸出流
22             // 將輸出流包裝為打印流
23             PrintWriter pw = new PrintWriter(os);
24             pw.write("用戶名:admin 密碼:123");
25             pw.flush();
26             socket.shutdownOutput();//關閉輸出流
27             
28             // 3、獲取輸入流,並讀取服務器端的響應信息
29             InputStream is = socket.getInputStream();
30             BufferedReader br = new BufferedReader(new InputStreamReader(is));
31             String info = null;
32             while((info = br.readLine())!=null){
33                 System.out.println("我是客戶端,服務器跟我說:"+info);
34             }
35             
36             // 4、關閉資源
37             br.close();
38             is.close();
39             pw.close();
40             os.close();
41             socket.close();
42             
43         } catch (UnknownHostException e) {
44             // TODO Auto-generated catch block
45             e.printStackTrace();
46         } catch (IOException e) {
47             // TODO Auto-generated catch block
48             e.printStackTrace();
49         }
50     }
51 }

運行結果不展示。


5、UDP編程

UDP協議(用戶數據報協議)是無連接、不可靠的、無序的。

特點:傳輸速度相對比較快

UDP協議以數據報作為數據傳輸的載體

進行數據傳輸時,首先需要將要傳輸的數據定義成數據報(Datagram),在數據報中指明數據所要達到的Socket(主機地址和端口號),然后在將數據報發送出去。

 

相關操作的Java類

DatagramPacket:表示數據報包

DatagramSocket:進行端到端通信的類

 

 

 

5-1、編程實現基於UDP的Socket通信之服務器端

 

5-2、編程實現基於UDP的Socket通信之客戶端

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM