一、基礎知識
1.TCP:傳輸控制協議。
2.UDP:用戶數據報協議。
二、IP地址封裝
1.InetAddress類的常用方法
getLocalHost() 返回本地主機的InetAddress對象 InetAddress類型 getByName(String host) 獲取指定主機名稱的IP地址 InetAddress類型 getHostName() 獲取此主機名 String getHostAddress() 獲取主機IP地址 String isReachable(int timeout) 在timeout指定的毫秒時間內,測試IP地址是否可達 Boolean
2.示例1:測試IP地址從“192.168.131.1”到“192.168.131.150”范圍內所有可以訪問的主機的名稱,如果對方沒有安裝防火牆,並且網絡連接正常的話,都可以訪問的。從輸出可以看出,192.168.131.1是本地主機的IP,其他地址都不能聯通,可能是因為1000毫秒太短或者對方主機裝有防火牆的原因。
package bigjunoba.bjtu.iptoname; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; public class IPToName { public static void main(String[] args) { String IP = null; for (int i = 1; i <= 150; i++) { IP = "192.168.131." + i; //生成IP字符串 try { InetAddress host; host = InetAddress.getByName(IP); //獲取IP封裝對象 if (host.isReachable(1000)) { //用1秒的時間測試IP地址是否可達 String hostName = host.getHostName(); System.out.println("IP地址" + IP + "的主機名稱是:" + hostName); } } catch (UnknownHostException e) { //捕獲未知主機異常 e.printStackTrace(); } catch (IOException e) { //捕獲輸入輸出異常 e.printStackTrace(); } } System.out.println("搜索完畢!"); } }
IP地址192.168.131.1的主機名稱是:BigjunOba
搜索完畢!
三、套接字
套接字(Socket)是代表計算機之間網絡連接的對象,用於建立計算機之間的TCP連接,使計算機之間可以建立連接並實現網絡通信。
1.服務器端套接字
服務器端套接字是SercerSocket類的實例對象,用於實現服務器緩存。ServerSocket類將監視指定的端口,並建立客戶端到服務器端套接字的連接,也就是客戶負責呼叫任務。
(1)創建服務器端套接字
創建服務器端套接字可以使用4種構造方法。
①ServerScoket()
默認構造方法,可以創建未綁定端口號的服務器套接字。服務器套接字的所有構造方法都需要處理IOException異常。
一般格式為:
try { ServerSocket server = new ServerSocket(); } catch (IOException e) { e.printStackTrace(); }
②ServerScoket(int port)
該構造方法將創建綁定到port參數指定端口的服務器套接字對象,默認的最大連接隊列長度為50,也就是說哦如果連接數量超過50個,將不會再接收新的連接請求。
一般格式為:
try { ServerSocket server = new ServerSocket(9527); } catch (IOException e) { e.printStackTrace(); } }
③ServerScoket(int port, int backlog)
使用port參數指定的端口號和backlog參數指定的最大連接長度創建服務器端套接字對象,這個構造方法可以指定超出50的連接數量,如300。
一般格式為:
try { ServerSocket server = new ServerSocket(9527, 300); } catch (IOException e) { e.printStackTrace(); } }
④public ServerSocket(int port, int backlog, InerAddress bindAddr)
使用port參數指定的端口號和backlog參數指定的最大連接隊列長度創建服務器端套接字對象。如果服務器有多個IP地址,可以使用bindAddr參數指定創建服務器套接字的地址;如果服務器只有一個IP地址,那么沒有必要使用該構造方法。
try {
InetAddress address = InetAddress.getByName("192.168.1.128"); ServerSocket server = new ServerSocket(9527, 300, address); } catch (IOException e) { e.printStackTrace(); } }
(2)接收客戶端套接字連接
當服務器建立ServerSocket套接字對象以后,就可以使用該對象的accept()方法接收客戶端請求的套接字連接。
語法格式為:
serverSocket.accept();
該方法被調用之后,將等待客戶端的連接請求,在接收客戶端的套接字連接請求以后,該方法將返回Socket對象,這個Socket對象是已經和客戶端建立好連接的套接字,通過這個Socket對象獲取客戶端的輸入輸出流來實現數據發送與接收。
該方法可能會產生IOException異常,所以在調用accept()方法時必須捕獲並處理該異常。
一般形式為:
try { server,accept(); } catch (IOException e) { e.printStackTrace(); } }
accept()方法將阻塞當前線程,直到接收到客戶端的連接請求為止,該方法之后的任何語句都不會執行,必須有客戶端發送連接請求;accpet()方法返回Socket套接字以后,當前線程才會繼續運行,accpet()方法之后的代碼才會被執行。
示例1:System.out.println("已經建立連接!");在server對象接收到客戶端的連接請求之前永遠都不會執行,這樣會導致程序的main主線程阻塞。
package bigjunoba.bjtu.serverSocketTest; import java.io.IOException; import java.net.ServerSocket; public class ServerSocketTest { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(9527); serverSocket.accept(); System.out.println("已經建立連接!"); } catch (IOException e) { e.printStackTrace(); } } }
示例2:將上述語句放在一個新的線程中,作為main主線程的子線程,在新的線程中完成等待客戶端連接請求並獲取客戶端Socket對象的任務,這樣就不會阻塞main主線程。
package bigjunoba.bjtu.serverSocketTest; import java.io.IOException; import java.net.ServerSocket; public class ServerSocketTest { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { try { ServerSocket serverSocket = new ServerSocket(9527); serverSocket.accept(); System.out.println("已經建立連接!"); } catch (IOException e) { e.printStackTrace(); } } }; Thread thread = new Thread(runnable); thread.start(); } }
2.客戶端套接字
Socket類是實現客戶端套接字的基礎。它采用TCP建立計算機之間的連接,並包含了Java語言所有對TCP有關的操作方法,如建立連接、傳輸數據、斷開連接等。
(1)創建客戶端套接字
Socket類定義了多個構造方法,它們可以根據InetAddress對象或者字符串指定的IP地址和端口號創建示例。
①Socket(InetAddress address, int port);
使用address參數傳遞的IP封裝對象和port參數指定的端口號創建套接字實例對象。Socket類的構造方法可能會產生UnknownHostException和IOException異常,在使用該構造方法創建Socket對象時必須捕獲和處理這兩個異常。
一般形式為:
try { InetAddress address = InetAddress.getByName("BigjunOba"); int port = 33; Socket socket = new Socket(address, port); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
②Socket(String host, int port);
使用host參數指定的IP地址字符串和port參數指定的整數類型端口號創建套接字實例對象。
一般形式為:
try { int port = 33; Socket socket = new Socket("192.168.131.1", port); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
③Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
創建一個套接字並將其連接到指定遠程地址上的指定遠程端口。
一般格式為:
try { InetAddress localHost = InetAddress.getLocalHost(); InetAddress address = InetAddress.getByName("192.168.131.1"); int port1 = 33; int port2 = 44; Socket socket = new Socket(address, port1, localHost, port2) ; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
④Socket(String host, int port, InetAddress localAddr, int localPort)
創建一個套接字並將其連接到指定遠程主機上的指定端口。
一般形式為:
try { InetAddress localHost = InetAddress.getLocalHost(); int port1 = 33; int port2 = 44; Socket socket = new Socket("192.168.131.1", port1, localHost, port2) ; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
示例1:檢測本地計算機中被使用的端口,端口的檢測范圍是1~256
package bigjunoba.bjtu.clientSocketTest; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; public class CheckPort { public static void main(String[] args) { for (int i = 1; i <= 256; i++) { try { InetAddress localHost = InetAddress.getLocalHost(); Socket socket = new Socket(localHost, i); //如果不產生異常,輸出該端口被使用 System.out.println("本機已經使用了端口:" + i); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { // e.printStackTrace(); } } System.out.println("執行完畢!"); }
本機已經使用了端口:135
執行完畢!
(2)發送和接收數據
Socket對象創建成功以后,代表和對方的主機已經建立了連接,可以接受與發送數據了。Socket提供了兩個方法分別獲取套接字的輸入流和輸出流,可以將要發送的數據寫入輸出流,實現發送功能,或者從輸入流讀取對方發送的數據,實現接受功能。
①接受數據
Socket對象從數據輸入流中獲取數據,該輸入流包含對方發送的數據,這些數據可能是文件、圖片、音頻或視頻。所以,在實現接受數據之前,必須使用getInputStream()方法獲取輸入流。
語法格式為:
socket.getInputStream()
②發送數據
Socket對象使用輸出流,向對方發送數據,所以,在實現數據發送以前,必須使用getOutputStream()方法獲取套接字的輸出流。
語法格式為:
socket.getOutputStream()
3.創建一些Socket套接字小程序。
示例1:創建服務器Server程序和客戶端Client程序,並實現簡單的Socket通信程序。
創建Server服務器類:
package bigjunoba.bjtu.CSTest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { try { //創建服務器套接字 ServerSocket server = new ServerSocket(9527); System.out.println("服務器啟動完畢!"); //等待客戶端連接,返回的是已經連接到服務器的客戶端socket對象 Socket socket = server.accept(); System.out.println("客戶端已經連接上服務器啦!"); //獲取客戶端socket對象的輸入流 InputStream inputStream = socket.getInputStream(); InputStreamReader reader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(reader); while (true) { String string = bufferedReader.readLine(); //如果接收到exit就退出服務器 if (string.equals("exit")) { break; } //如果接收到的不是exit,就輸出接收到的內容 System.out.println("接受內容為:" + string); } System.out.println("連接斷開!"); bufferedReader.close(); reader.close(); inputStream.close(); socket.close(); server.close(); } catch (IOException e) { e.printStackTrace(); } } }
創建client客戶端類:
package bigjunoba.bjtu.CSTest; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) { try { //創建連接服務器的Socket Socket socket = new Socket("localhost", 9527); //獲取Socket輸出 OutputStream outputStream = socket.getOutputStream(); //向服務器發送數據(將字符串轉化成字節數組) outputStream.write("這是客戶端發送給服務器的文字\n".getBytes()); //發送退出信息 outputStream.write("exit\n".getBytes()); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
先啟動服務器:
服務器啟動完畢!
再啟動客戶端:
服務器啟動完畢!
客戶端已經連接上服務器啦!
接受內容為:這是客戶端發送給服務器的文字
連接斷開!
示例2:示例1中的程序在運行完一次后,程序就會自動結束,因為Socket socket = server.accept();會阻塞當前的main線程,而且客戶端連接一次后,程序執行完之后就直接退出了,這樣的效果很不好。為了實現客戶端能夠實時多次發送數據的功能,考慮將Socket socket = server.accept();這行代碼更換到一個更適當的位置,並考慮使用多線程技術來實現多次發送的功能,使得程序更加完善。
(1)首先需要解釋一下DataInputStream與InputStream的區別:
DataInputStream類繼承了InputStream類,比普通的InputStream多了一些方法,DataInputStream、DataOutputStream並沒有改變InputStream或OutputStream的行為,讀入或寫出時的動作還是InputStream、OutputStream負責。DataInputStream、DataOutputStream只是在實現對應的方法時,動態地為它們加上類型判斷功能。readUTF()的作用,是從輸入流中讀取UTF-8編碼的數據,並以String字符串的形式返回。writeUTF(value:String)的作用是 :將String字符串轉換成UTF-8編碼的字節流並寫入輸出流。
(2)內部類:如果內部類和main方法在同一個類中,那么因為靜態方法不能調用動態方法,所以可以將內部類定義成靜態static,然后在創建類時就已經new出了實例,因此不需要new語句,調用的時候直接調用即可。
(3)明確服務器端和客戶端的發送和接收
客戶端:從輸入流中接收數據,使用輸出流發送數據。即dataOutputStream.writeUTF(line);是客戶端發送給服務器的數據,dataInputStream.readUTF()是客戶端接收服務器發來的數據。
服務器端:從輸入流中接收數據,使用輸出流發送數據。即dataOutputStream.writeUTF(replyMessage);是服務器發送給客戶端的數據,dataInputStream.readUTF()服務器接收客戶端發來的數據。
(3)server服務器端程序修改為:
package bigjunoba.bjtu.CSTest2; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { try { //創建服務器套接字 @SuppressWarnings("resource") ServerSocket server = new ServerSocket(8023); System.out.println("服務器啟動完畢!"); while (true) { //等待客戶端連接,只有當一個客戶端連接上才會返回已經連接到服務器的客戶端socket對象 Socket socket = server.accept(); System.out.println("客戶端已經連接上服務器啦!"); //客戶端連接上服務器后,就開啟通信線程 new CommunicationThread(socket).start(); } } catch (IOException e) { e.printStackTrace(); } } public static class CommunicationThread extends Thread{ Socket socket; DataInputStream dataInputStream; DataOutputStream dataOutputStream; public CommunicationThread(Socket socket) { this.socket = socket; try { dataInputStream = new DataInputStream(socket.getInputStream()); dataOutputStream = new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } public void run() { super.run(); String message = null; try { //接收從客戶端發來的數據 while ((message = dataInputStream.readUTF()) != null) { System.out.println("客戶端發來的信息是:" + message); String replyMessage = "OK!我已經收到了:" + message; //發送回應數據給客戶端 dataOutputStream.writeUTF(replyMessage); System.out.println("服務器發送給客戶端的回應:" + replyMessage); } } catch (IOException e) { e.printStackTrace(); } } } }
(4)client客戶端程序修改為:
package bigjunoba.bjtu.CSTest2; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.util.Scanner; public class Client { public static void main(String[] args) { try { @SuppressWarnings("resource") //連接到服務器 Socket socket = new Socket("localhost", 8023); System.out.println("客戶端啟動完畢!"); DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); @SuppressWarnings("resource") Scanner scanner = new Scanner(System.in); String line = null; listenServerReply(dataInputStream); //讀取從鍵盤輸入的第一行 while ((line = scanner.nextLine()) != null) { //發送數據給服務器端 dataOutputStream.writeUTF(line); System.out.println("客戶端發送給服務器的信息是:" + line); } } catch (Exception e) { e.printStackTrace(); } } //監聽服務器端發送回來的信息 public static void listenServerReply(final DataInputStream dataInputStream) { new Thread() { public void run() { super.run(); String line = null; try { //接收從服務器端發送回來的回應信息 while ((line = dataInputStream.readUTF()) != null) { System.out.println("客戶端從服務器接收到的回應是:" + line); } } catch (IOException e) { e.printStackTrace(); } } }.start(); } }
(5)首先啟動服務器端,服務器端打印程序為:
服務器啟動完畢!
(6)然后啟動客戶端,
客戶端輸出為:
客戶端啟動完畢!
服務器端輸出為:
服務器啟動完畢!
客戶端已經連接上服務器啦!
(7)在客戶端控制台出打印一些信息
客戶端輸出為:
客戶端啟動完畢!
Hello,你好啊!
客戶端發送給服務器的信息是:Hello,你好啊!
客戶端從服務器接收到的回應是:OK!我已經收到了:Hello,你好啊!
服務器端輸出為:
服務器啟動完畢!
客戶端已經連接上服務器啦!
客戶端發來的信息是:Hello,你好啊!
服務器發送給客戶端的回應:OK!我已經收到了:Hello,你好啊!
四、數據報
Java語言可以使用TCP和UDP兩種通信協議實現網絡通信,其中TCP通信由Socket套接字實現,而UDP通信需要使用DatagramSocket類實現。
和TCP通信不同,UDP傳遞信息的速度更快,但是沒有TCP的高可靠性,當用戶通過UDP發送信息之后,無法確定能否正確地傳送到目的地。雖然UDP是一種不可靠的通信協議,但是大多數場合並不需要嚴格的、高可靠性的通信,它們需要的是快速的信息發送,並能容忍一些小的錯誤,那么使用UDP通信來實現會更合適一些。
UDP將數據打包,也就是通信中所傳遞的數據包,然后將數據包發送到指定目的地,對方會接收數據包,然后查看數據包中的數據。
1.DatagramPacket
該類是UDP鎖傳遞的數據包,即打包后的數據。數據包用來實現無連接包投遞的服務,每個數據包僅根據包中包含的信息從一台計算機傳送到另一台計算機,傳送的多個包可以選擇不同的路由,也可能按不同的順序到達。
(1)DatagramPacket(byte[] buf, int length)
該構造方法用來創建數據包實例,這個數據包實例將接收長度為length的數據包。
語法格式為:
DatagramPacket(byte[] buf, int length)
(2)DatagramPacket(byte[] buf, int length, InetAddress address, int port)
創建數據包實例,用來將長度為length的數據包發送到address參數指定地址和port參數指定端口號的主機。length參數必須小於等於buf數組的長度。
語法格式為:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
2.DatagramSocket
該類是用於發送和接收數據的數據包套接字。數據包套接字是數據包傳送服務的發送或接收點。要實現UDP通信的數據就必須創建數據包套接字。
常用的構造方法有3個:
(1)DatagramSocket()
默認的構造方法,該構造方法將使用本機任何可用的端口創建數據包套接字實例。在創建DatagramSocket類的實例時,有可能會產生SocketException異常,所以,在創建數據包套接字時,應該捕獲並處理該異常。
一般格式為:
try { DatagramSocket datagramSocket = new DatagramSocket(); } catch (SocketException e) { e.printStackTrace(); }
(2)DatagramSocket(int port)
創建數據包套接字並將其綁定到port參數指定的本機端口,端口號取值必須在0~65535.
一般格式為:
try { DatagramSocket datagramSocket = new DatagramSocket(8023); } catch (SocketException e) { e.printStackTrace(); }
(3)DatagramSocket(int port, InetAddress Iaddr)
綁定數據包套接字,將其綁定到Iaddr參數指定的本機地址和port參數指定的本機端口號。本機端口號取值必須在0~65535之間。
一般格式為:
try { InetAddress localHost = InetAddress.getLocalHost(); DatagramSocket datagramSocket = new DatagramSocket(8023, localHost); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); }
3.數據報通信程序
示例:使用兩個雷實現UDP通信程序設計。
創建服務器端程序:
package bigjunoba.bjtu.UDPTest; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class Server { public static void main(String[] args) { byte[] buf = new byte[1024]; DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); try { @SuppressWarnings("resource") DatagramSocket datagramSocket = new DatagramSocket(8023); System.out.println("服務器啟動完畢!"); datagramSocket.receive(datagramPacket); int length = datagramPacket.getLength(); String message = new String(datagramPacket.getData(), 0, length); String ip = datagramPacket.getAddress().getHostAddress(); System.out.println("從" + ip + "發送來了信息:" + message); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
創建客戶端程序:
package bigjunoba.bjtu.UDPTest; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; public class Client { public static void main(String[] args) { try { InetAddress address = InetAddress.getByName("127.0.0.1"); @SuppressWarnings("resource") DatagramSocket datagramSocket = new DatagramSocket(); System.out.println("客戶端啟動完畢!"); byte[] data = "hello,我是客戶端,我來訪問服務器了!".getBytes(); DatagramPacket datagramPacket = new DatagramPacket(data, data.length, address, 8023); datagramSocket.send(datagramPacket); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
先啟動服務器程序,然后再啟動客戶端程序,服務器控制台的輸出為:
服務器啟動完畢!
從127.0.0.1發送來了信息:hello,我是客戶端,我來訪問服務器了!
五、網絡聊天程序開發
使用Swing設置程序UI界面,並結合Java語言多線程技術使網絡聊天程序更加符合實際需求,即可以不間斷地收發多條信息。
(1)創建ClientFrame類,該類包含多個成員變量。