黑馬程式設計師 筆記(二十二)——網路程式設計
------- android培訓、java培訓、期待與您交流! ----------
網路程式設計
一、預備知識:
1、IP地址:
網際網路上連線了無數的伺服器和計算機,每個主機都有唯一的地址,作為該主機在網際網路上的標識,這個地址就是IP地址。IP地址是一種在Internet上給主機編址的方式,也稱為網際協議地址。IP地址有4個十進位制陣列成,每個數的取值範圍是0~255,各數之間用改一個點號(.)分割。例如:202.102.2.34。
2、埠:常用埠(Web服務:80 Tomcat:8080 Mysql:3306)
埠是計算機I/O資訊的介面。計算機煉乳通訊網路或者Internet需要一個埠,這個埠不是物理埠,而是一個有16位數標識的邏輯埠,而且這個埠是TCP/IP協議的一部分。通過這個埠就可以進行資訊的I/O。埠號是由一個16位2進位制的數,其取值範圍是0~65535。在實際中,計算機上的0~1024埠都被保留為系統服務,在程式中不應讓自己設計的服務佔用這些埠
3、通訊協議(TCP/UDP)
協議是描述資料交換時必須遵循的規則和格式。網路協議規定了在網路上傳輸的資料型別,以及怎樣解釋這些資料型別和怎樣請求傳輸這些資料。Internet的通訊協議是一種四層協議模型,從下至上分別是鏈路層(包括OSI七層模型中的物理層與資料鏈路層)、網路層、傳輸層、應用層。執行與計算機中的網路應用利用傳輸層協議——傳輸控制協議(Transmission Control Protocol,TCP)或使用者資料報協議(User Datagram Protocol,UDP)進行通訊。
TCP是一種基於連線的傳輸協議,它為兩個計算機之間提供了點到點的可靠資料流,保證從連線的一端傳送的資料能夠以正確的順序到達另一端。應用層的常用協議,如HTTP、FTP等都是需要可靠通訊通道的協議,資料在網路上的傳送和接收順序對於這些應用來說是至關重要的。
與TCP不同的是UDP不是基於連線的,而是為應用層提供了一種非常簡單、高效的傳輸服務。UDP從一個應用程式向另一個應用程式傳送獨立的資料報,但並不能保證這些資料報一定能夠到達對方,並且這些資料報的傳輸次序無保障,後傳送的資料報可能先到目的地。因此,使用UDP時,任何必需的可靠性都必須由應用程式自身提供。UDP適用於對通訊可靠性要求低且對通訊效能要求高的應用,例如域名系統DNS、路由資訊協議等都建立在UDP的基礎上。
UDP的特點(重點):
1、將資料及源和目的封裝在資料包中,不需要建立連線。
2、每個資料報的大小限制在64K內
3、因為是不需連線,所以不可靠
4、不需要建立連線,速度快。
TCP的特點(重點):
1、建立連線,形成傳輸資料的通道。
2、在連線中進行大量資料傳輸。
3、通過3次握手完成連線,是可靠協議
4、必須建立連線,效率較低。
4、網路模型
OSI模型:
①分層模型:
TCP模型:
①分層模型:
二、Java中對網路三要素的定義
InetAddress類:————IP
該類沒有建構函式,只能通過該類的靜態函式獲取InetAddress物件。
1、獲取本機的InetAddress
①static InetAddress getLocalHost() 通過此函式可以獲取本機的InetAddress物件,該物件的字串表現形式是主機名稱和IP地址。
②通過獲取的InetAddress物件可以呼叫getHostName()和getHostAddress()分別獲取該物件的名稱和IP地址。
2、獲取其他計算機的InetAddress物件
①通過 InetAddress getByName(String host)函式可以通過主機名稱獲取該主機的InetAddress物件。
②通過InetAddress[] getAllByName(String host)在給定主機名的情況下,根據系統上配置的名稱服務返回其 IP 地址所組成的陣列
Socket機制:
1、Socket就是為網路服務提供的一種機制。
2、通訊的兩端必須都要有Socket。
3、網路通訊其實就是Socket間通訊。
4、資料在兩個Socket間通過IO傳輸。
三、1不同協議建立Socket的方法(UDP Socket的建立)
1、UDP的Socket的建立————DatagramSocket類
此類表示用來發送和接收資料報的套接字。資料報套接字是包投遞服務的傳送或接收點。每個在資料報套接字上傳送或接收的包都是單獨編址和路由的。從一臺機器傳送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達。
通過void send(DatagramPacket p);方法傳送資料,通過void receive(DatagramPacket p);方法接收資料。
DatagramPacket類:此類表示資料報包。資料報包用來實現無連線包投遞服務。每條報文僅根據該包中包含的資訊從一臺機器路由到另一臺機器。從一臺機器傳送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達。不對包投遞做出保證。
在建立物件時,接收的DatagramPacket和傳送DatagramPacket的定義是不同的。
接收時的定義:DatagramPacket(byte[] buf, int length) ——
傳送時的定義:DatagramPacket(byte[] buf, int length, InetAddress address, int port)——所有的資料傳送時都是位元組資料,所以必須把傳送的東西轉換成位元組資料。還要將目的地址和埠傳進要封裝的物件。
2、建立傳送和連線的步驟:
傳送:
1、通過DatagramSocket類建立UDP服務。
2、確定要傳送的資料,將資料轉換成位元組流,裝在一個位元組數組裡。
3、將要傳送的資料封裝成DatagramPacket物件,在該物件裡還要包括髮送的大小、IP地址物件(InetAddress)、埠號
4、通過DatagramSocket物件的send方法傳送。
5、關閉UDP服務。
public class UDPSendDemo { public static void main(String[] args) throws Exception { //1、建立UDP服務,通過DatagramSocket物件 DatagramSocket UDPSend_socket = new DatagramSocket(); //2、確定傳送的資料,並封裝成物件 byte[] buf = "你好".getBytes(); //(DataramePacket的傳送應該在其中封裝要被傳輸的陣列、要傳送的資料長度、IP、傳送的埠) DatagramPacket UDPSend_packet = new DatagramPacket(buf,buf.length,InetAddress.getByName("117.139.158.68"),8000); //3、傳送資料 UDPSend_socket.send(UDPSend_packet); //4、關閉資源 UDPSend_socket.close(); } }
接收:
1、定義一個UDPSocket服務,監聽一個埠。
2、定義一個數據包,因為要儲存接收到的位元組資料。
因為資料包物件中有更多的功能可以提取位元組資料中的不同資料資訊。
3、通過Socket服務的receive方法將收到的資料存入已經定義好的資料包中。(該方法是阻塞式方法)
4、通過資料包物件的功能,將這些不同的資料取出。
5、關閉資源,並讓先前定義的UDPSocket的引用指向null。
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class UDPReceiveDemo { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub while(true) { DatagramSocket UDPRec_socket = new DatagramSocket(8001); byte[] buf = new byte[1024]; DatagramPacket UDPRec_packet = new DatagramPacket(buf,buf.length); UDPRec_socket.receive(UDPRec_packet); String ip = UDPRec_packet.getAddress().getHostAddress(); String string = new String(UDPRec_packet.getData(),0,UDPRec_packet.getLength()); System.out.println("傳送者的IP:"+ip+"\r\n傳送的內容是:"+string +"\r\n傳送的埠Port:"+UDPRec_packet.getPort()+"\r\n"); UDPRec_socket.close(); UDP_socket=null; } } }
利用UDP傳輸協議做一個聊天的小程式
原理:1、傳送訊息和接收訊息沒有先後,所以傳送訊息和接收訊息的程式,都要封裝在獨自的執行緒中。
2、接收訊息的程式要是迴圈接收的,這樣才能接收到對方發來的訊息。
程式:
import java.io.*; import java.net.*; class IS implements Runnable { private DatagramSocket dss; public IS(DatagramSocket dss) { this.dss = dss; } public void run() { try { BufferedReader ISd = new BufferedReader(new InputStreamReader(System.in)); for (String im=ISd.readLine(); im!=null; im=ISd.readLine()) { byte [] ISbuf = im.getBytes(); DatagramPacket ISpacket = new DatagramPacket(ISbuf,ISbuf.length,InetAddress.getByName("10.11.116.12"),9090); dss.send(ISpacket); } } catch (Exception e) { } } } class IR implements Runnable { private DatagramSocket dsr; public IR(DatagramSocket dsr) { this.dsr = dsr; } public void run() { try { while (true) { byte[] IRbuf = new byte[1024]; DatagramPacket IRpacket = new DatagramPacket(IRbuf,IRbuf.length); dsr.receive(IRpacket); String ip = IRpacket.getAddress().getHostAddress(); String data =new String( IRpacket.getData(),0,IRpacket.getLength()); System.out.println(ip+"發來:"+data); } } catch (Exception e1) { } } } class Chat { public static void main(String[] args) throws Exception { DatagramSocket ds = new DatagramSocket(); DatagramSocket dr = new DatagramSocket(9090); new Thread(new IS(ds)).start(); new Thread(new IR(dr)).start(); } }
2、 TCP傳輸(Socket)
1、TCP的傳輸依賴於連線,分為客戶端(Socket)和服務端(ServerSocket)。
1、客服端:通過查閱Socket物件,發現在建立客戶端的時候,就可以去連線指定的主機。因為TCP是面向連線的,所以在Socket服務時,就要有服務端的存在,並連線成功。形成通路後,在該通道進行資料的傳輸。
步驟:
1、建立 Socket服務,並指定要連線的主機和埠。在連線成功後會自動建立兩個流(傳送和讀取)
可以通過Socket(InetAddress address,int port)
也可以通過Socket(String host, int port)
2、通過建立的Socket物件呼叫getOutputStream();獲取輸出流。
3、該輸出流將要傳輸的資料寫入。
4、通過Socket物件呼叫getInputStream()方法,獲取輸入流,來接收服務端反饋的資訊。
5、關閉服務。
import java.io.*; import java.net.*; class TCPClient { public static void main(String[] args) throws Exception { //建立客戶端的Socket Socket tcpc = new Socket("10.11.116.12",9090); while(true) { //獲取該服務的輸出流 OutputStream tcpout = tcpc.getOutputStream(); tcpout.write("TCP傳輸".getBytes()); //關閉連線 tcpc.close(); } } }
2、服務端:
步驟:
1、建立ServerSokect服務,並監聽一個埠。
2、通過ServerSokect的accept方法獲取客戶端的Socket,如果沒有連線就等(阻塞式方法)
3、客戶端如果發過來資料,那麼服務端要使用對應的客戶端物件,並獲取到該客服端物件的讀取流發過來的資料。
4、關閉
import java.io.*; import java.net.*; class TCPServer { public static void main(String[] args)throws Exception { ServerSocket tcps = new ServerSocket(9090); while(true) { Socket tcpc = tcps.accept(); String ip = tcpc.getInetAddress().getHostName(); System.out.println("TCP連線"+ip); InputStream in = tcpc.getInputStream(); byte[] buf =new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); tcpc.close(); } } }
注意:
1、如果傳輸的是字元資料,使用BufferedRead()方法和BufferedWriter()方法時,在傳輸時,必須在用newLine()方法去寫入一個換行符,否則服務端和接收端,都會一直等下去。
2、在迴圈讀寫的時候,可以加結束標記,來是結束讀取的迴圈操作。
3、還可以使用客戶端的Socket物件,呼叫shutdownOutput()方法。結束客戶端的輸出流相當於,給流中加上一個結束標記。
2、利用TCP傳輸的原理編的小程式
1、進行網路複製的小程式
要求:
從客戶端上傳一個圖片到服務端,服務接收後把圖片儲存到伺服器定義的資料夾,並反饋給服務端“上傳成功“的字樣。
原理:和TCP傳輸原理一樣,只是讀取的檔案是從硬碟上,並且服務端將讀取的檔案儲存到硬碟上。其實就是多出兩個流(1、讀取硬碟上檔案的讀取流,2、往硬碟上儲存檔案的輸出流)
程式:
import java.io.*; import java.net.*; //定義上傳圖片的客戶端PicClient class PicClient { public static void main(String[] args)throws Exception { while(true) { //1、建立服務端的Socekt物件並繫結在指定的ip地址和指定的埠 Socket picc = new Socket("127.0.0.1",9090); //2、將要讀取的圖片封裝在一個檔案物件中 File mp3 = new File("F:\\合同.jpg"); //3、建立讀取檔案的流物件 BufferedInputStream picin = new BufferedInputStream(new FileInputStream(mp3)); //4、獲取上傳到服務端的流物件 OutputStream piccpw = picc.getOutputStream(); //5、讀取檔案,並上傳到服務端 for (int i=picin.read();i!=-1 ;i=picin.read() ) { piccpw.write(i); } //6、給服務端傳送上傳完畢的指令 picc.shutdownOutput(); //7、獲取讀取伺服器反饋資訊的流物件 BufferedReader ini =new BufferedReader( new InputStreamReader(picc.getInputStream())); //8、讀取反饋的內容,並列印在控制檯上 for (String string=ini.readLine();string!=null ;string =ini.readLine() ) { System.out.println(string); } //9、關閉用於讀取檔案的流物件和客戶端 picin.close(); picc.close(); } } } //服務端 class PicServer { public static void main(String[] args)throws Exception { //1、建立服務端的ServerSocket物件,並繫結監視的埠 ServerSocket s = new ServerSocket(9090); while(true) { //2、獲取與該伺服器連線的客戶端 Socket picc = s.accept(); //3、打印出客戶端的IP地址 System.out.println(picc.getInetAddress().getHostAddress()); //4、獲取讀取客戶端上傳的資料的流物件 BufferedInputStream bis = new BufferedInputStream( picc.getInputStream()); //5、建立輸出流,通過此流將讀取到的資料儲存在硬碟上 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("server1.jpg")); //6、讀取並寫入到輸出流儲存到硬碟 for (int i=bis.read(); i!=-1;i=bis.read() ) { bos.write(i); bos.flush(); } //7、獲取到客戶端的輸出流用於給客戶端反饋訊息。 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(picc.getOutputStream())); PrintWriter pr = new PrintWriter(bw,true); pr.println("上傳成功"); //8、給客戶端傳送反饋資訊完畢的指令 picc.shutdownOutput(); //9、關閉客戶端的連線 picc.close(); } } }
2、TCP客戶端併發登入
要求:
1、客戶端輸入使用者名稱,如果使用者名稱存在則顯示歡迎,如果不存在,則顯示不存在。
2、如果輸入錯誤超過3次,則客戶端自動關閉。
3、使用者名稱資訊存貯在了一個文字檔案中
原理:同上。
程式:
import java.io.*; import java.net.*; class Login { public static void main(String[] args)throws Exception { Socket client = new Socket("127.0.0.1",9090); BufferedReader buf = new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(client.getOutputStream(),true); BufferedReader bufin = new BufferedReader(new InputStreamReader(client.getInputStream())); for (int x=0;x<3 ;++x ) { String line = buf.readLine(); if ("".equals(line)) { System.out.println(line+"line"); break; } System.out.println(line+"line"); out.println(line); String info = bufin.readLine(); System.out.println("info:"+info); if (info.contains("歡迎")) { break; } } buf.close(); } } class SerThread implements Runnable { private Socket ser; SerThread(Socket ser) { this.ser = ser; } public void run() { String ip = ser.getInetAddress().getHostAddress(); System.out.println(ip+"is conneting"); try { for (int x=0;x<3 ;++x ) { BufferedReader bufin = new BufferedReader(new InputStreamReader(ser.getInputStream())); String name = bufin.readLine(); BufferedReader bufr = new BufferedReader(new FileReader("name.txt")); PrintWriter out = new PrintWriter(ser.getOutputStream(),true); boolean flag=false; for (String name1=bufr.readLine(); name1!=null;name1=bufr.readLine() ) { if(name1.equals(name)) { flag = true; break; } } if (flag) { System.out.println(name+":已登入"); out.println(name+":歡飲光臨"); break; } else { System.out.println(name+":正在嘗試登入"); out.println("使用者名稱不存在"); } } ser.close(); } catch (Exception e ) { throw new RuntimeException("校驗失敗"); } } } class Ser { public static void main(String[] args) throws Exception { ServerSocket ser = new ServerSocket(9090); while (true) { Socket cli = ser.accept(); new Thread(new SerThread(cli)).start(); } } }
3、通過客戶端訪問伺服器
1、瀏覽器與自定義伺服器
1、自定義服務端:向連線上的客戶端反饋一個訊息(”歡迎“)。和之前服務端的定義的一樣。
2、瀏覽器:在位址列內輸入ip地址:埠號。即可訪問。
2、自定義客戶端與Tomcat伺服器
1、HTTP請求訊息頭——瀏覽器給伺服器傳送了什麼資訊,才會請求到伺服器的資訊呢?
自定義瀏覽器:自定義瀏覽器就需要將請求訊息頭髮送到伺服器。其他不變。
我們通過在服務端將瀏覽器發過來的資訊列印在控制檯上,來觀察。通過以下程式來將瀏覽器傳送的資料打印出來。
import java.io.*; import java.net.*; class TCPServer { public static void main(String[] args)throws Exception { ServerSocket tcps = new ServerSocket(9090); while(true) { Socket tcpc = tcps.accept(); String ip = tcpc.getInetAddress().getHostAddress(); System.out.println(ip+"連線"); InputStream in = tcpc.getInputStream(); byte[] buf =new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); PrintWriter pw = new PrintWriter(tcpc.getOutputStream(),true); pw.println("hello,客戶端"); tcpc.shutdownOutput(); tcpc.close(); } } }
在伺服器端列印的資訊有GET / HTTP/1.1 Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave -flash, application/xaml+xml, application/x-ms-xbap, application/x-ms-applicatio n, application/QVOD, application/QVOD, application/vnd.ms-xpsdocument, applicati on/msword, application/vnd.ms-powerpoint, application/vnd.ms-excel, */* Accept-Language: zh-cn User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET 4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2) Accept-Encoding: gzip, deflate Host: 127.0.0.1:9090 Connection: Keep-Alive
Accept:支援的檔案型別Accept-Language:zh-cn支援的語言
User-Agent:使用者的資訊
Accept-Encoding:支援的壓縮方式。
Host:訪問的主機名以及埠號。
Connection:
在訊息頭後要空兩行。
2、HTTP應答訊息頭——伺服器給瀏覽器傳送的應答訊息頭,通過以下自定義瀏覽器訪問Tomcat伺服器可獲取此訊息頭
import java.io.*; import java.net.*; class MyIE { public static void main(String[] args)throws Exception { Socket s = new Socket("192.168.1.254",8080); PrintWriter out = new PrintWriter(s.getOutputStream(),true); out.println("GET /myweb/demo.html HTTP/1.1"); out.println("Accept: */*"); out.println("Accept-Language: zh-cn"); out.println("Host: 192.168.1.254:11000"); out.println("Connection: closed"); out.println(); out.println(); BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; while((line=bufr.readLine())!=null) { System.out.println(line); } s.close(); } }
獲取到的應答訊息頭如下:HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Accept-Ranges: bytes ETag: W/"3282-1321936820000" Last-Modified: Tue, 22 Nov 2011 04:40:20 GMT Content-Type: text/html Content-Length: 3282 Date: Fri, 31 May 2013 06:53:07 GMT Connection: close
HTTP/1.1:http協議的版本。200是響應狀態碼,表示成功。Last-Modified:最後修改的日期
Content-Type:傳送的文字型別
Content-Length:文字的大小
Date:時間
Connection:連線狀態。
四、URL類與URLConnection
URL:在之前我們自定義瀏覽器的時候,必須把URL地址手動的拆成各個段。而URL類中有這些方法,使我們不必在手動的去拆分網路地址。
1、URL解析所用到的方法:
String getProtocol() : 獲取此 URL 的協議名稱。
String getFile() :獲取此 URL 的檔名。
String getHost() : 獲取此 URL 的主機名(如果適用)。
String getPath() :獲取此 URL 的路徑部分。
int getPort() : 獲取此 URL 的埠號。
String getProtocol() : 獲取此 URL 的協議名稱。
String getQuery() : 獲取此 URL 的查詢部
注意:當我們沒有指定訪問的埠號時,則getPort返回的值是-1。所以在程式中我們需要判斷一下,如果沒有指定埠getPort==-1,就將其預設為80埠。
方法示例:
import java.net.*; class URLDemo { public static void main(String[] args) throws MalformedURLException { URL url = new URL("http://127.0.0.1/myweb/index.html?name=haha&age=30"); System.out.println("getProtocol() :"+url.getProtocol()); System.out.println("getHost() :"+url.getHost()); System.out.println("getPort() :"+url.getPort()); System.out.println("getPath() :"+url.getPath()); System.out.println("getFile() :"+url.getFile()); System.out.println("getQuery() :"+url.getQuery()); int port = getPort(); if(port==-1) port = 80; } }
URLConnection物件:
1、URLConnection
是所有類的超類,它代表應用程式和 URL 之間的通訊連結。此類的例項可用於讀取和寫入此 URL 引用的資源。通常,建立一個到 URL 的連線需要幾個步驟:2、URLConnection內容包括協議,目的地址。在其內部建立了連線。
- 通過在 URL 上呼叫
openConnection
方法建立連線物件。- 處理設定引數和一般請求屬性。
- 使用
connect
方法建立到遠端物件的實際連線。- 遠端物件變為可用。遠端物件的頭欄位和內容變為可訪問。
我們可以通過URLConnection的物件來獲取輸入流獲取資訊。
——InputStream getInputStream() :返回從此開啟的連線讀取的輸入流。
——OutputStream getOutputStream() :返回寫入到此連線的輸出流。
3、所以在建立應用層的網路通訊的時候不用在建立Socket物件。直接通過Connection物件的方法獲取。
例:
import java.net.*; import java.io.*; class URLConnectionDemo { public static void main(String[] args) throws Exception { //1、建立URL物件 URL url = new URL("http://127.0.0.1:8080/myweb/index.html"); //2、通過URL物件的openConnection()方法獲取連線物件URLConnection URLConnection conn = url.openConnection(); System.out.println(conn); //3、通過連線物件獲取輸入流來獲取伺服器反饋的資訊 InputStream in = conn.getInputStream(); //4、定義一個數組來裝這些資料 byte[] buf = new byte[1024]; int len = in.read(buf); //5、列印獲取到的資訊 System.out.println(new String(buf,0,len)); } }