21.1 網絡程序設計基礎
網絡程序設計編寫的是與其他計算機進行通信的程序。Java已經將網絡程序所需要的元素封裝成不同的類,用戶只要創建這些類的對象,使用相應的方法,即使不具備有關的網絡知識,也可以編寫出高質量的網絡通信程序。
21.1.1局域網與互聯網
為了實現兩臺計算機的通信,必須用一個網絡線路連接兩臺計算機。
21.1.2網絡協議
網絡協議規定了計算機之間連接的物理、機械(網線與網卡的連接規定)、電氣(有效的電平范圍)等特征,計算機之間的相互尋址規則,數據發送沖突的解決方式,長數據如何分段傳送與接收等內容。就像不同的國家有不同的法律一樣,目前網絡協議也有多種。下面簡單地介紹幾個常用的網絡協議。
1.IP協議
IP 是Internet Protocol的簡稱,是一種網絡協議。Internet網絡采用的協議是TCP/IP協議,其全稱是Transmission Control Protocol/Internet Protocol。Internet依靠TCP/IP協議,在全球范圍內實現了不同硬件結構、不同操作系統、不同網絡系統間的互聯。在Internet網絡上存在著數以億計的主機,每臺主機都用網絡為其分配的Internet地址代表自己,這個地址就是IP地址。
TCP/IP模式是一種層次結構,共分為四層,各層實現特定的功能,提供特定的服務和訪問接口。
2.TCP與UDP 協議
在TCP/IP協議棧中,有兩個高級協議是網絡應用程序編寫者應該了解的,即傳輸控制協議(Transmission Control Protocol,TCP)與用戶數據報協議(User Datagram Protocol,UDP)。
TCP協議是一種以固接連線為基礎的協議,它提供兩臺計算機間可靠的數據傳送。TCP可以保證數據從一端送至連接的另一端時,能夠確實送達,而且抵達的數據的排列順序和送出時的順序相同。因此,TCP協議適合可靠性要求比較高的場合。就像撥打電話,必須先撥號給對方,等兩端確定連接后,相互才能聽到對方說話,也知道對方回應的是什么。
HTTP、FTP 和 Telnet等都需要使用可靠的通信頻道。例如,HTTP從某個URL讀取數據時,如果收到的數據順序與發送時不相同,可能就會出現一個混亂的HTML文件或是一些無效的信息。
UDP是無連接通信協議,不保證數據的可靠傳輸,但能夠向若干個目標發送數據,或接收來自若干個源的數據。UDP以獨立發送數據包的方式進行。這種方式就像郵遞員送信給收信人,可以寄出很多信給同一個人,且每一封信都是相對獨立的,各封信送達的順序并不重要,收信人接收信件的順序也不能保證與寄出信件的順序相同。
UDP協議適合于一些對數據準確性要求不高,但對傳輸速度和時效性要求非常高的網站,如網絡聊天室、在線影片等。這是由于TCP協議在認證上存在額外耗費,可能使傳輸速度減慢,而UDP協議即使有一小部分數據包遺失或傳送順序有所不同,也不會嚴重危害該項通信。
注意一些防火墻和路由器會設置成不允許UDP數據包傳輸,因此若遇到UDP連接方面的問題,應先確定所在網絡是否允許UDP協議。
21.1.3端口與套接字
一般而言,一臺計算機只有單一的連到網絡的物理連接(PhysicalConnection),所有的數據都通過此連接對內、對外送達特定的計算機,這就是端口。網絡程序設計中的端口(port)并非真實的物理存在,而是一個假想的連接裝置。端口被規定為一個在0~65535的整數。HTTP服務一般使用80端口, FTP服務使用21端口。假如一臺計算機提供了HTTP、FTP等多種服務,那么客戶機會通過不同的端口來確定連接到服務器的哪項服務上,如圖21.3所示。
通常,0~1023 的端口數用于一些知名的網絡服務和應用,用戶的普通網絡應用程序應該使用1024以上的端口數,以避免端口號與另一個應用或系統服務所用端口沖突。
網絡程序中的套接字(Socket)用于將應用程序與端口連接起來。套接字是一個假想的連接裝置,就像插座一樣可連接電器與電線,如圖21.4所示。Java將套接字抽象化為類,程序設計者只需創建Socket類對象,即可使用套接字。
21.2 TCP程序
TCP 網絡程序設計是指利用Socket類編寫通信程序。利用TCP協議進行通信的兩個應用程序是有主次之分的,一個稱為服務器程序,另一個稱為客戶機程序,兩者的功能和編寫方法大不一樣。
?
21.2.1InetAddress類
jae包中的InetAddress類是與IP地址相關的類,利用該類可以獲取IP地址、主機地址等信息。
?
import java.net.*; //導入java.net包
public class Address { //創建類public static void main(String[] args) {InetAddress ip; //創建InetAddress對象try { //使用try語句塊捕捉可能出現的異常ip = InetAddress.getLocalHost(); //實例化對象String localname = ip.getHostName(); //獲取本機名String localip = ip.getHostAddress(); //獲取本機IP地址System.out.println("本機名:" + localname); //將本機名輸出System.out.println("本機IP地址:" + localip); //將本機IP地址輸出} catch (UnknownHostException e) {e.printStackTrace(); //輸出異常信息}}
}
?
21.2.2SeverSockt類
java.net包中的ServerSocket類用于表示服務器套接字,其主要功能是等待來自網絡上的“請求”。
它可通過指定的端口來等待連接的套接字。服務器套接字一次可以與一個套接字連接。如果多臺客戶機同時提出連接請求,服務器套接字會將請求連接的客戶機存入列隊中,然后從中取出一個套字。與服務器新建的套接字連接起來。若請求連接數大于最大容納數,則多出的連接請求被拒絕。默認大小是50。
ServerSocket類的構造方法通常會拋出IOException異常,具體有以下幾種形式:
??? ?ServerSocket():創建非綁定服務器套接字。?? ?
?? ? ServerSocket(int port):創建綁定到特定端口的服務器套接字。
?? ? ServerSocket(int port, int backlog):利用指定的backlog創建服務器套接字,并將其綁定到指定的本地端口號上。
?? ? ServerSocket(int port, int backlog, InetAddress bindAddress):使用指定的端口、偵聽backlog和要綁定到的本地IP地址創建服務器。這種情況適用于計算機上有多塊網卡和多個IP地址的情況,用戶可以明確規定ServerSocket在哪塊網卡或哪個IP地址上等待客戶的連接請求。 ServerSocket類的常用方法如表 21.2所示。
調用 ServerSocket 類的accept()方法,會返回一個和客戶端 Socket 對象相連接的 Socket 對象。服務器端的Socket 對象使用getOutputStream()方法獲得的輸出流,將指向客戶端 Socket 對象使用 getinputStream()方法獲得的那個輸入流;同樣,服務器端的 Socket 對象使用 getlnputStream()方法獲得的輸入流,將指向客戶端 Socket 對象使用getOutputStream()方法獲得的那個輸出流。也就是說,當服務器向輸出流寫入信息時,客戶端通過相應的輸入流就能讀取,反之亦然。?
21.2.3TCP網絡程序設計
TCP通信方式呢 ?主要的通訊方式是一對一的通訊方式,也有著優點和缺點 ?它的優點對比于UDP來說就是更可靠 因為它的通訊方式是需要先發送消息 看看客戶端是否能夠接收到消息 如果沒有回復消息的話 服務端 ?就不會發出文件 等待客戶端回復消息,這個握手模式的話 ?就會非常可靠 ?以下代碼進行講解:
?
import java.io.*;
import java.net.*;public class MyServer {private ServerSocket server; // 服務器套接字private Socket socket; // 客戶端套接字void start() {// 啟動服務器try {server = new ServerSocket(8998); // 服務器啟用8998端口System.out.println("服務器套接字已經創建成功");while (true) {System.out.println("等待客戶端的連接");socket = server.accept(); // 服務器監聽客戶端連接// 根據套接字字節流創建字符輸入流BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));while (true) {// 循環接受信息String message = reader.readLine();// 讀取一行文本if ("exit".equals(message)) {// 如果客戶端發來的內容為“exit”System.out.println("客戶端退出");break;// 停止接受信息}System.out.println("客戶端:" + message);}reader.close(); // 關閉流socket.close(); // 關閉套接字}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {MyServer tcp = new MyServer();tcp.start(); // 啟動服務器}
}
?
21.3 UPD程序
用戶數據報協議(UDP)是網絡信息傳輸的另一種形式。基于UDP的通信和基于TCP的通信不同,基于UDP的信息傳遞更快,但不提供可靠性保證。使用UDP傳遞數據時,用戶無法知道數據能否正確地到達主機,也不能確定到達目的地的順序是否和發送的順序相同。雖然UDP是一種不可靠的協議,但如果需要較快地傳輸信息,并能容忍小的錯誤,可以考慮使用UDP。
基于 UDP通信的基本模式如下:
?將數據打包(稱為數據包),然后將數據包發往目的地。
?接收別人發來的數據包,然后查看數據包。
發送數據包的步驟如下:
(1)使用DatagramSocket()創建一個數據包套接字。
(2)使用DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)創建要發的數據包。
(3)使用DatagramSocket類的send()方法發送數據包。
接收數據包的步驟如下:
(1)使用DatagramSocket(int port)創建數據包套接字,綁定到指定的端口。
(2)使用DatagramPacket(byte[] buf, int length)創建字節數組來接收數據包。
(3)使用DatagramPacket類的receive(方法接收UDP包。
注意
DatagramSocket類的receive()方法接收數據時,如果還沒有可以接收的數據,在正常情況下 receive()方法將阻害,一直等到網絡上有數據傳來,receive()方法接收該數據并返回。如果網絡上沒有數據發送過來,receive()方法也沒有阻塞,肯定是程序有問題,大多數情況下是因為使用了一個被其他程序占用的端口號。
21.3.1DatagramPacket類
avanet 包的 DatagramPacket類用來表示數據包。DatagramPacket類的構造方法如下:?DatagramPacke1(byte]buf, int length).
?DatagramPackel(byse[] buf,int length,InetAddress address, int port).
第一種構造方法在創建DatagramPacket對象時,指定了數據包的內存空間和大小。第二種構造方法不僅指定了數據包的內存空間和大小,還指定了數據包的目標地址和端口。在發送數據時,必須指定接收方的Socket地址和端口號,因此使用第二種構造方法可創建發送數據的DatagramPacket對象。
21.3.2DatagramSocket類
java.net 包中的DatagramSocket類用于表示發送和接收數據包的套接字。該類的構造方法如 下:? ?DatagramSocket()。
?DatagramSocket(int port)。
?DatagramSocket(int port, InetAddress addr)。
第一種構造方法創建DatagramSocket對象,構造數據報套接字,并將其綁定到本地主機任何可用的端口上。第二種構造方法創建DatagramSocket對象,創建數據報套接字,并將其綁定到本地主機的指定端口上。第三種構造方法創建DatagramSocket對象,創建數據報套接字,并將其綁定到指定的端口和指定的本地地址上。第三種構造函數適用于有多塊網卡和多個IP地址的情況。
最主要的是TCP和UDP兩個部分 ?他們兩主要的區別就是一個是一對一通信 ?一個是一對多通信 ? 當然兩者都有各自的優勢和劣勢,接下來先講解 TCP部分
21.3.3UPD網絡程序設計
下面創建一個廣播數據報程序
import java.io.IOException;
import java.net.*;public class Notification extends Thread {String weather = "節目預報:八點有大型晚會,請收聽";// 發送的消息int port = 9898; // 端口InetAddress iaddress = null;MulticastSocket socket = null; // 多點廣播套接字Notification() {try {iaddress = InetAddress.getByName("224.255.10.0"); // 實例化InetAddress,指定地址socket = new MulticastSocket(port); // 實例化多點廣播套接字socket.setTimeToLive(1); // 指定發送范圍是本地網絡socket.joinGroup(iaddress); // 加入廣播組} catch (IOException e) {e.printStackTrace(); // 輸出異常信息}}public void run() {while (true) {DatagramPacket packet = null; // 數據包byte data[] = weather.getBytes(); // 字符串消息的字節數組packet = new DatagramPacket(data, data.length, iaddress, port); // 將數據打包System.out.println(weather); // 控制臺打印消息try {socket.send(packet); // 發送數據sleep(3000); // 線程休眠} catch (IOException e) {e.printStackTrace(); } catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) { Notification w = new Notification();w.start(); // 啟動線程}
}