14Java進階網路程式設計API
語義表示要做什麼,語法表示要怎麼做,時序表示做的順序。
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(); } } }