14Java進階網路編程API
- 2021 年 7 月 30 日
- 筆記
語義表示要做什麼,語法表示要怎麼做,時序表示做的順序。
2.網路OSI七層模型
OSI/RM 模型(Open System Interconnection/Reference Model)。它將電腦網路體系結構的通訊協議劃分為七層,自下而上依次為物理層(Physics Layer)、數據鏈路層(Data Link Layer)、網路層(Network Layer)、傳輸層(Transport Layer)、會話層(Session Layer)、表示層(Presentation Layer)、應用層(Application Layer)。
3.TCP四層模型簡介
自下而上依次為網路介面層(Network Interface Layer)、網路層(Network Layer)、傳輸層(Transport Layer)和應用層(Application Layer)。
還可以轉換為五層模型,即將網路介面層分為物理層和數據鏈路層。
3.1TCP四層作用
物理層規定了物理介質的各種特性,包括機械特性、電子特性、功能特性和規程特性。
數據鏈路層負責接收數據幀並通過網路發送,或從網路上接收物理幀再抽離出數據幀交給網路層。
網路層管理網路中的數據通訊,設法將數據從源端經過若干中間節點傳送到目的端,從而向傳輸層提供最基本的端到端的數據傳送服務。
傳輸層主要功能包括分割和重組數據並提供差錯控制和流量控制,以達到提供可靠傳輸的目的。為了實現可靠的傳輸,傳輸層協議規定接收端必須發送確認資訊以確定數據達到,假如數據丟失,必須重新發送。
應用層對應於 OSI 七層模型的會話層、表示層和應用層,該層向用戶提供一組常用的應用程式服務。
3.2傳輸層的常用協議
1.傳輸控制協議(Transmission Control Protocol,TCP),是一種可靠的面向連接的傳輸服務協議。在 TCP/IP 協議族中,TCP 提供可靠的連接服務,採用「三次握手」建立一個連接。
2.用戶數據報協議(User Datagram Protocol,UDP),是另外一個重要的協議,它提供的是無連接、面向事務的簡單不可靠資訊傳送服務。UDP 不提供分割、重組數據和對數據進行排序的功能,也就是說,當數據發送之後,無法得知其是否能安全完整地到達。
UDP是無連接的、不可靠的,資源消耗小,處理速度快,但是在網路不好的情況下丟包比較嚴重。
3.3應用層的常用協議
-
文件傳輸協議(File Transfer Protocol,FTP),上傳、下載文件可以使用 FTP 服務。
-
Telnet 協議,提供用戶遠程登錄的服務,使用明碼傳送,保密性差,但簡單方便。
-
域名解析服務(Domain Name Service,DNS),提供域名和 IP 地址之間的解析轉換。
-
簡單郵件傳輸協議(Simple Mail Transfer Protocol,SMTP),用來控制郵件的發送、中轉。
-
超文本傳輸協議(Hypertext Transfer Protocol,HTTP),用於實現互聯網中的 WWW 服務。
-
郵局協議的第三個版本(Post Office Protocol 3,POP3),它是規定個人電腦如何連接到互聯網上的郵件伺服器進行收發郵件的協議。
3.4數據的封裝與解封
從上向下,數據的傳輸需要加上相應的頭部和尾部,稱為數據的封裝。
從下向上,數據的傳輸需要去掉相應的頭部和尾部,稱為數據的解封。
4 IP地址及其表示
IP地址由兩部分組成:網路號和主機號
網路號表示該地址處於哪一個網路,主機號表示該地址的主機。
IP 地址有兩種表示方式,二進位表示和點分十進位表示,常見的是點分十進位表示的 IP 地址。IP 地址的長度為 32 位,每 8 位組成一個部分,一個 IP 地址可以分為四個部分。如果每個部分用十進位表示,其值的範圍為 0 ~ 255,不同部分之間用「.」分割開來。
5 域名簡介及其分類
域名可分為不同級別,包括頂級域名、二級域名等。
頂級域名又可分為以下兩類:
一類是國家頂級域名,200 多個國家都按照 ISO3166 國家程式碼分配了頂級域名,例如中國是 cn,美國是 us,韓國是 kr 等。
另一類是國際頂級域名,一般表示著註冊企業類別的符號,例如表示工商企業的 com,表示網路提供商的 net,表示非營利組織的 org 等。
二級域名是指頂級域名之下的域名,例如在國際頂級域名下,由域名註冊人申請註冊的網上名稱,例如 sohu、apple、microsoft 等。
6 InetAddress——獲取IP地址
-
InetAddress[] getAllByName(String host)
:通過主機名和配置名返回IP地址。 -
InetAddress getByAddress(byte[] addr)
:通過 IP 地址數組返回InetAddress
對象。 -
InetAddress getByAddress(String host, byte[] addr)
:根據提供的主機名 host 和位元組數組形式的 IP 地址 addr,創建InetAddress
對象。 -
InetAddress getByName(String host)
:給定主機名 host,返回InetAddress
對象。 -
InetAddress getLocalHost()
:返回本地主機InetAddress
對象。
InetAddress
類的其他常用方法有以下幾種:
-
byte[] getAddress()
:返回此InetAddress
對象的 IP 地址數組。 -
String getCanonicalHostName()
:返回此 IP 地址的完全限定域名。完全限定域名是指主機名加上全路徑,全路徑中列出了序列中所有域成員。 -
String getHostAddress()
:返回 IP 地址字元串。 -
String getHostName()
:返回此 IP 地址的主機名。
7 URL類——獲取網路資源的位置
構造方法:URL(地址)
獲取頁面的輸入位元組流:url.openStream()
使用IO方法對頁面的內容進行獲取和解析
8 URLConnection類——連接通訊
1.使用url.openConnection()獲取連接對象。
2.設置參數和一般請求屬性。
3.使用connect()方法進行遠程連接。
8.1 URLConnection的具體屬性
-
boolean doInput
:將doInput
標誌設置為 true,指示應用程式要從 URL 連接讀取數據,此屬性的默認值為 true。此屬性由setDoInput()
方法設置,其值由getDoInput()
方法返回。 -
boolean doOutput
:將doOutput
標誌設置為 true,指示應用程式要將數據寫入 URL 連接,此屬性的默認值為 false。此屬性由setDoOutput()
方法設置,其值由getDoOutput()
方法返回。 -
boolean useCaches
:如果其值為 true,則只要有條件就允許協議使用快取;如果其值為 false,則該協議始終必須獲得此對象的新副本,其默認值為上一次調用setDefaultUseCaches()
方法時給定的值。此屬性由setUseCaches()
方法設置,其值由getUseCaches()
方法返回。 -
boolean connected
:表示URL是否連接成功 -
URL url
:表示Connection類在網上打開的url對象。
9 使用Socket編程之TCP Socket
Socket:套接字,用於端到端的通訊。
ServerSocket:用於服務端對象的創建。伺服器會初始化一個埠號的Socket,監聽此埠的連接。如果客戶端建立連接,會分配一個帶有新的埠號的Socket,用來和客戶端對話。當連接結束時,會關閉Socket。
ServerSocket的accept()方法:ServerSocket的accept()方法從連接請求隊列中取出一個客戶的連接請求,然後創建與客戶連接的Socket對象,並將它返回。如果隊列中沒有連接請求,accept()方法就會一直等待,直到接收到了連接請求才返回。
伺服器為請求連接的客戶進程建立一個先進先出隊列,默認大小一般是50,如果調用accept()方法就會從隊列中取出連接請求,服務端為其建立一個socket。如果隊列已經滿,伺服器會拒絕新的連接請求。
9.1使用Socket創建CS連接
package two; import java.io.DataInputStream; import java.io.IOException; import java.net.ConnectException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class Test7 { } class TestServer{ public static void main(String[] args) { ServerSocket socket = null; try { //創建服務端的socket socket = new ServerSocket(8888); //創建一個socket,接受客戶端的套接字 Socket s = socket.accept(); //顯示 System.out.println("客戶端的IP:"+s.getInetAddress()+",客戶端的埠號:"+s.getPort()); s.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } class TestClient{ public static void main(String[] args) { try { //服務端和客戶端在同一區域網內。服務端的埠號是8888 Socket socket = new Socket("127.0.0.1",8888); //從socket中獲得客戶端的埠號和IP DataInputStream dis = new DataInputStream(socket.getInputStream()); if(dis.available()!=0)System.out.println(dis.readUTF()); dis.close(); socket.close(); }catch (ConnectException e) { e.printStackTrace(); System.err.println("伺服器連接失敗!"); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }
9.2使用socket進行圖片上傳
package org.lanqiao.service; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class UploadService { public static void main(String[] args) { try { ServerSocket server = new ServerSocket(10203); Socket socket = server.accept(); //獲取對客戶端寫入的數據輸出位元組流 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); File file = new File("timg.jpg"); //從本地圖片獲得輸入流 FileInputStream fis = new FileInputStream(file); //寫入輸出流 dos.write(fis.readAllBytes()); dos.close(); fis.close(); socket.close(); server.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } package org.lanqiao.client; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.Socket; public class UploadClient { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1", 10203); //獲取服務端發來的輸入流 DataInputStream dis = new DataInputStream(socket.getInputStream()); File file = new File("pic/mn.jpg"); FileOutputStream fos = new FileOutputStream(file); //將圖片數據寫入本地文件 fos.write(dis.readAllBytes()); System.out.println("上傳完成"); fos.close(); dis.close(); socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
9.3 使用多執行緒優化CS聊天室
package two; import jdk.net.Sockets; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class Test8 { public static void main(String[] args) { try { ServerSocket server = new ServerSocket(8888); while (true) { //只要服務端開啟,就一直接受客戶端的socket //ServerSocket的accept()方法從連接請求隊列中取出一個客戶的連接請求,然後創建與客戶連接的Socket對象,並將它返回。如果隊列中沒有連接請求,accept()方法就會一直等待,直到接收到了連接請求才返回。 //socket中存儲的是客戶端的ip地址和向客戶端進行通訊的埠號 Socket socket = server.accept(); //並為這個socket啟動新的服務端執行緒 ServerThread st = new ServerThread(socket); st.start(); } } catch (IOException e) { e.printStackTrace(); } } } class ServerThread extends Thread { Socket socket; Scanner sc = new Scanner(System.in); public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { InputStream is = null; OutputStream os = null; try { //獲取這個socket的輸入輸出流 is = socket.getInputStream(); os = socket.getOutputStream(); DataInputStream dis = new DataInputStream(is); DataOutputStream dos = new DataOutputStream(os); String str = null; while (true) { //一直通訊,直到客戶端斷開連接拋出異常 if ((str = dis.readUTF()) != null) { if (str.equals("e")) break; System.out.println("客戶端發來的消息:" + str); } System.out.println("請輸入要向客戶端發送的消息:"); String msg = sc.next(); dos.writeUTF(msg); System.out.println(); } dos.close(); dis.close(); socket.close(); //EOFException表示意外到達流的結尾,如流中斷 } catch (EOFException e) { System.out.println("客戶端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + "退出!"); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } class ClientThread extends Thread { public static void main(String[] args) { Scanner sc = new Scanner(System.in); try { //建立連接,獲取socket //socket中存儲的是服務端的ip地址和向服務端進行通訊的埠號 Socket socket = new Socket("127.0.0.1", 8888); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); DataInputStream dis = new DataInputStream(is); DataOutputStream dos = new DataOutputStream(os); while (true) { //一直通訊,直到用戶輸入退出連接 if (dis.available() != 0) System.out.println("伺服器發來消息:" + dis.readUTF()); System.out.println("請輸入要向伺服器發送的消息(發送e結束):"); String msg = sc.next(); if (msg.equals("e")) { System.out.println("已退出聊天"); break; } dos.writeUTF(msg); } dos.close(); dis.close(); os.close(); is.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
10使用Socket編程之UDP Socket
DatagramSocket類:接受和發送數據報的套接字。使用UDP廣播發送
DatagramPacket類:此類表示數據報包,它用來實現無連接包投遞服務。根據該包中包含的地址和埠等資訊,將報文從一台機器路由到另一台機器。
DatagramPacket的構造函數:
DatagramPacket(byte[] buf,int readLength):用於構建接受資訊的數據報包
DatagramPacket(byte[] buf,int readLength,InetAddress inet):用於構建發送資訊的數據報包,inet對象中需要指定IP和埠號
10.1使用UDP實現客戶端向服務端發送消息
解決中文輸入輸出亂碼:在發送端創建數據報包時,不要直接使用字元串或者字元串.getBytes()獲得的位元組數組作為數據。創建ByteArrayOutputStream和以baos為輸出流的DataOutputStream,使用dos的writeUTF()方法,再獲得baos使用的位元組數組。將這個數組作為參數。
在接收端創建接受數據包時,不要直接顯示buf創建的字元串,先創建以buf為輸入的ByteArrayInputStream和以bais為輸入流的DataInputStream,最後使用dis的readUTF()讀出這個位元組數組。這樣就會識別中文、韓文等語言。
package two; import java.io.*; import java.net.*; import java.util.Scanner; public class Test10 { } class UDPClient { public static void main(String[] args) { Scanner sc = new Scanner(System.in); try { DatagramSocket ds = new DatagramSocket(9999); System.out.println("客戶端:"); while (true) { String line = sc.next(); if (line.equals("bye")) break; //創建baos和dos將讀入數據輸出到位元組數組中 ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.writeUTF(line); byte[] buf = baos.toByteArray(); DatagramPacket packet = new DatagramPacket(buf,buf.length, new InetSocketAddress("127.0.0.1", 8888)); //datagramsocket.send(datagrampacket)方法發送數據報 ds.send(packet); } ds.close(); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } class UDPServer { public static void main(String[] args) { try { //接收端socket註冊埠號 DatagramSocket ds = new DatagramSocket(8888); System.out.println("伺服器端:"); while (true) { byte[] buf = new byte[1024]; //獲取接收端數據報 DatagramPacket packet = new DatagramPacket(buf, buf.length); //datagramsocket.receive(datagrampacket)方法接受數據報 ds.receive(packet); //packet.getData()獲取數據資訊,返回的是位元組數組,需要根據數組的長度構建字元串 //使用bais和dis讀取獲得的位元組數組。 ByteArrayInputStream bais = new ByteArrayInputStream(buf); DataInputStream dis = new DataInputStream(bais); System.out.println(dis.readUTF()); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }