經過計算機網絡可使多臺計算機實現鏈接,位於同一個網絡中的計算機在進行鏈接和通訊時須要遵照必定的規則,這就比如在道路中行駛的汽車必定要遵照交通規則同樣。在計算機網絡中,這些鏈接和通訊的規則被稱爲網絡通訊協議,它對數據的傳輸格式、傳輸速率、傳輸步驟等作了統一規定,通訊雙方必須同時遵照才能完成數據交換。java
網絡通訊協議有不少種,目前應用最普遍的是TCP/IP協議(Transmission Control Protocal/Internet Protoal傳輸控制協議/英特網互聯協議),它是一個包括TCP協議和IP協議,UDP(User Datagram Protocol)協議和其它一些協議的協議組,在學習具體協議以前首先了解一下TCP/IP協議組的層次結構。數組
在進行數據傳輸時,要求發送的數據與收到的數據徹底同樣,這時,就須要在原有的數據上添加不少信息,以保證數據在傳輸過程當中數據格式徹底一致。TCP/IP協議的層次結構比較簡單,共分爲四層,如圖所示。安全
上圖中,TCP/IP協議中的四層分別是應用層、傳輸層、網絡層和鏈路層,每層分別負責不一樣的通訊功能,接下來針對這四層進行詳細地講解。服務器
鏈路層:鏈路層是用於定義物理傳輸通道,一般是對某些網絡鏈接設備的驅動協議,例如針對光纖、網線提供的驅動。網絡
網絡層:網絡層是整個TCP/IP協議的核心,它主要用於將傳輸的數據進行分組,將分組數據發送到目標計算機或者網絡。多線程
傳輸層:主要使網絡程序進行通訊,在進行網絡通訊時,能夠採用TCP協議,也能夠採用UDP協議。socket
應用層:主要負責應用程序的協議,例如HTTP協議、FTP協議等。ide
要想使網絡中的計算機可以進行通訊,必須爲每臺計算機指定一個標識號,經過這個標識號來指定接受數據的計算機或者發送數據的計算機。學習
在TCP/IP協議中,這個標識號就是IP地址,它能夠惟一標識一臺計算機,目前,IP地址普遍使用的版本是IPv4,它是由4個字節大小的二進制數來表示,如:00001010000000000000000000000001。因爲二進制形式表示的IP地址很是不便記憶和處理,所以一般會將IP地址寫成十進制的形式,每一個字節用一個十進制數字(0-255)表示,數字間用符號「.」分開,如 「192.168.1.100」。spa
隨着計算機網絡規模的不斷擴大,對IP地址的需求也愈來愈多,IPV4這種用4個字節表示的IP地址面臨枯竭,所以IPv6 便應運而生了,IPv6使用16個字節表示IP地址,它所擁有的地址容量約是IPv4的8×1028倍,達到2128個(算上全零的),這樣就解決了網絡地址資源數量不夠的問題。
經過IP地址能夠鏈接到指定計算機,但若是想訪問目標計算機中的某個應用程序,還須要指定端口號。在計算機中,不一樣的應用程序是經過端口號區分的。端口號是用兩個字節(16位的二進制數)表示的,它的取值範圍是0~65535,其中,0~1023之間的端口號用於一些知名的網絡服務和應用,用戶的普通應用程序須要使用1024以上的端口號,從而避免端口號被另一個應用或服務所佔用。
接下來經過一個圖例來描述IP地址和端口號的做用,以下圖所示。
從上圖中能夠清楚地看到,位於網絡中一臺計算機能夠經過IP地址去訪問另外一臺計算機,並經過端口號訪問目標計算機中的某個應用程序。
瞭解了IP地址的做用,咱們看學習下JDK中提供了一個InetAdderss類,該類用於封裝一個IP地址,並提供了一系列與IP地址相關的方法,下表中列出了InetAddress類的一些經常使用方法。
上圖中,列舉了InetAddress的四個經常使用方法。其中,前兩個方法用於得到該類的實例對象,第一個方法用於得到表示指定主機的InetAddress對象,第二個方法用於得到表示本地的InetAddress對象。經過InetAddress對象即可獲取指定主機名,IP地址等,接下來經過一個案例來演示InetAddress的經常使用方法,以下所示。
1 public class Example01 { 2 public static void main(String[] args) throws Exception { 3 InetAddress local = InetAddress.getLocalHost(); 4 InetAddress remote = InetAddress.getByName("www.itcast.cn"); 5 System.out.println("本機的IP地址:" + local.getHostAddress()); 6 System.out.println("itcast的IP地址:" + remote.getHostAddress()); 7 System.out.println("itcast的主機名爲:" + remote.getHostName()); 8 } 9 }
在介紹TCP/IP結構時,提到傳輸層的兩個重要的高級協議,分別是UDP和TCP,其中UDP是User Datagram Protocol的簡稱,稱爲用戶數據報協議,TCP是Transmission Control Protocol的簡稱,稱爲傳輸控制協議。
UDP是無鏈接通訊協議,即在數據傳輸時,數據的發送端和接收端不創建邏輯鏈接。簡單來講,當一臺計算機向另一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,一樣接收端在收到數據時,也不會向發送端反饋是否收到數據。
因爲使用UDP協議消耗資源小,通訊效率高,因此一般都會用於音頻、視頻和普通數據的傳輸例如視頻會議都使用UDP協議,由於這種狀況即便偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。
可是在使用UDP協議傳送數據時,因爲UDP的面向無鏈接性,不能保證數據的完整性,所以在傳輸重要數據時不建議使用UDP協議。UDP的交換過程以下圖所示。
TCP協議是面向鏈接的通訊協議,即在傳輸數據前先在發送端和接收端創建邏輯鏈接,而後再傳輸數據,它提供了兩臺計算機之間可靠無差錯的數據傳輸。在TCP鏈接中必需要明確客戶端與服務器端,由客戶端向服務端發出鏈接請求,每次鏈接的建立都須要通過「三次握手」。第一次握手,客戶端向服務器端發出鏈接請求,等待服務器確認,第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了鏈接請求,第三次握手,客戶端再次向服務器端發送確認信息,確認鏈接。整個交互過程以下圖所示。
因爲TCP協議的面向鏈接特性,它能夠保證傳輸數據的安全性,因此是一個被普遍採用的協議,例如在下載文件時,若是數據接收不完整,將會致使文件數據丟失而不能被打開,所以,下載文件時必須採用TCP協議
前面介紹了UDP是一種面向無鏈接的協議,所以,在通訊時發送端和接收端不用創建鏈接。UDP通訊的過程就像是貨運公司在兩個碼頭間發送貨物同樣。在碼頭髮送和接收貨物時都須要使用集裝箱來裝載貨物,UDP通訊也是同樣,發送和接收的數據也須要使用「集裝箱」進行打包,爲此JDK中提供了一個DatagramPacket類,該類的實例對象就至關於一個集裝箱,用於封裝UDP通訊中發送或者接收的數據。
想要建立一個DatagramPacket對象,首先須要瞭解一下它的構造方法。在建立發送端和接收端的DatagramPacket對象時,使用的構造方法有所不一樣,接收端的構造方法只須要接收一個字節數組來存放接收到的數據,而發送端的構造方法不但要接收存放了發送數據的字節數組,還須要指定發送端IP地址和端口號。
接下來根據API文檔的內容,對DatagramPacket的構造方法進行逐一詳細地講解。
使用該構造方法在建立DatagramPacket對象時,指定了封裝數據的字節數組和數據的大小,沒有指定IP地址和端口號。很明顯,這樣的對象只能用於接收端,不能用於發送端。由於發送端必定要明確指出數據的目的地(ip地址和端口號),而接收端不須要明確知道數據的來源,只須要接收到數據便可。
使用該構造方法在建立DatagramPacket對象時,不只指定了封裝數據的字節數組和數據的大小,還指定了數據包的目標IP地址(addr)和端口號(port)。該對象一般用於發送端,由於在發送數據時必須指定接收端的IP地址和端口號,就好像發送貨物的集裝箱上面必須標明接收人的地址同樣。
上面咱們講解了DatagramPacket的構造方法,接下來對DatagramPacket類中的經常使用方法進行詳細地講解,以下表所示。
DatagramPacket數據包的做用就如同是「集裝箱」,能夠將發送端或者接收端的數據封裝起來。然而運輸貨物只有「集裝箱」是不夠的,還須要有碼頭。在程序中須要實現通訊只有DatagramPacket數據包也一樣不行,爲此JDK中提供的一個DatagramSocket類。DatagramSocket類的做用就相似於碼頭,使用這個類的實例對象就能夠發送和接收DatagramPacket數據包,發送數據的過程以下圖所示。
在建立發送端和接收端的DatagramSocket對象時,使用的構造方法也有所不一樣,下面對DatagramSocket類中經常使用的構造方法進行講解。
該構造方法用於建立發送端的DatagramSocket對象,在建立DatagramSocket對象時,並無指定端口號,此時,系統會分配一個沒有被其它網絡程序所使用的端口號。
該構造方法既可用於建立接收端的DatagramSocket對象,又能夠建立發送端的DatagramSocket對象,在建立接收端的DatagramSocket對象時,必需要指定一個端口號,這樣就能夠監聽指定的端口。
上面咱們講解了DatagramSocket的構造方法,接下來對DatagramSocket類中的經常使用方法進行詳細地講解。
講解了DatagramPacket和DatagramSocket的做用,接下來經過一個案例來學習一下它們在程序中的具體用法。
要實現UDP通訊須要建立一個發送端程序和一個接收端程序,很明顯,在通訊時只有接收端程序先運行,才能避免因發送端發送的數據沒法接收,而形成數據丟失。所以,首先須要來完成接收端程序的編寫。
l UDP完成數據的發送
1 package com.xujingyang.UDP; 2 3 import java.net.DatagramPacket; 4 import java.net.DatagramSocket; 5 import java.net.InetAddress; 6 import java.net.Socket; 7 import java.net.UnknownHostException; 8 9 public class UDPSendDemo { 10 public static void main(String[] args) { 11 DatagramSocket socket = null; 12 try { 13 //創建基站 14 socket = new DatagramSocket(); 15 byte[] buf = "hello,UDP".getBytes(); 16 InetAddress address; 17 address = InetAddress.getByName("localhost"); 18 //創建倉庫 19 DatagramPacket packet = new DatagramPacket(buf, buf.length, address,12345); 20 //發送數據 21 socket.send(packet); 22 } catch (Exception e) { 23 e.printStackTrace(); 24 }finally{ 25 //關閉基站 26 socket.close(); 27 } 28 } 29 }
l UDP完成數據的接收
1 package com.xujingyang.UDP; 2 3 import java.net.DatagramPacket; 4 import java.net.DatagramSocket; 5 import java.net.InetAddress; 6 7 public class UDPReceiveDemo { 8 public static void main(String[] args) { 9 DatagramSocket socket = null; 10 try { 11 socket = new DatagramSocket(12345);//創建基站 12 byte[] buf = new byte[1024]; 13 DatagramPacket packet=new DatagramPacket(buf, buf.length);//創建機房 14 socket.receive(packet);//開始接受數據 15 16 //獲取對方的主機信息 17 InetAddress address = packet.getAddress(); 18 System.out.println(address.getHostAddress()); 19 //獲取數據內容 20 byte[] data = packet.getData(); 21 System.out.println("數據內容:"+new String(data,0,packet.getLength())); 22 //獲取數據長度 23 int length = packet.getLength(); 24 System.out.println("數據長度:"+length); 25 26 //獲取接收端口號 27 int port = packet.getPort(); 28 System.out.println("接收端口號是:"+port); 29 30 } catch (Exception e) { 31 e.printStackTrace(); 32 }finally{ 33 socket.close(); 34 } 35 } 36 37 }
TCP通訊同UDP通訊同樣,都能實現兩臺計算機之間的通訊,通訊的兩端都須要建立socket對象。
區別在於,UDP中只有發送端和接收端,不區分客戶端與服務器端,計算機之間能夠任意地發送數據。
而TCP通訊是嚴格區分客戶端與服務器端的,在通訊時,必須先由客戶端去鏈接服務器端才能實現通訊,服務器端不能夠主動鏈接客戶端,而且服務器端程序須要事先啓動,等待客戶端的鏈接。
在JDK中提供了兩個類用於實現TCP程序,一個是ServerSocket類,用於表示服務器端,一個是Socket類,用於表示客戶端。
通訊時,首先建立表明服務器端的ServerSocket對象,該對象至關於開啓一個服務,並等待客戶端的鏈接,而後建立表明客戶端的Socket對象向服務器端發出鏈接請求,服務器端響應請求,二者創建鏈接開始通訊。
經過前面的學習知道,在開發TCP程序時,首先須要建立服務器端程序。JDK的java.net包中提供了一個ServerSocket類,該類的實例對象能夠實現一個服務器段的程序。經過查閱API文檔可知,ServerSocket類提供了多種構造方法,接下來就對ServerSocket的構造方法進行逐一地講解。
使用該構造方法在建立ServerSocket對象時,就能夠將其綁定到一個指定的端口號上(參數port就是端口號)。
接下來學習一下ServerSocket的經常使用方法,如表所示。
ServerSocket對象負責監聽某臺計算機的某個端口號,在建立ServerSocket對象後,須要繼續調用該對象的accept()方法,接收來自客戶端的請求。當執行了accept()方法以後,服務器端程序會發生阻塞,直到客戶端發出鏈接請求,accept()方法纔會返回一個Scoket對象用於和客戶端實現通訊,程序才能繼續向下執行。
講解了ServerSocket對象能夠實現服務端程序,但只實現服務器端程序還不能完成通訊,此時還須要一個客戶端程序與之交互,爲此JDK提供了一個Socket類,用於實現TCP客戶端程序。
經過查閱API文檔可知Socket類一樣提供了多種構造方法,接下來就對Socket的經常使用構造方法進行詳細講解。
使用該構造方法在建立Socket對象時,會根據參數去鏈接在指定地址和端口上運行的服務器程序,其中參數host接收的是一個字符串類型的IP地址。
該方法在使用上與第二個構造方法相似,參數address用於接收一個InetAddress類型的對象,該對象用於封裝一個IP地址。
在以上Socket的構造方法中,最經常使用的是第一個構造方法。
接下來學習一下Socket的經常使用方法,如表所示。
方法聲明 |
功能描述 |
int getPort() |
該方法返回一個int類型對象,該對象是Socket對象與服務器端鏈接的端口號 |
InetAddress getLocalAddress() |
該方法用於獲取Socket對象綁定的本地IP地址,並將IP地址封裝成InetAddress類型的對象返回 |
void close() |
該方法用於關閉Socket鏈接,結束本次通訊。在關閉socket以前,應將與socket相關的全部的輸入/輸出流所有關閉,這是由於一個良好的程序應該在執行完畢時釋放全部的資源 |
InputStream getInputStream() |
該方法返回一個InputStream類型的輸入流對象,若是該對象是由服務器端的Socket返回,就用於讀取客戶端發送的數據,反之,用於讀取服務器端發送的數據 |
OutputStream getOutputStream() |
該方法返回一個OutputStream類型的輸出流對象,若是該對象是由服務器端的Socket返回,就用於向客戶端發送數據,反之,用於向服務器端發送數據 |
在Socket類的經常使用方法中,getInputStream()和getOutStream()方法分別用於獲取輸入流和輸出流。當客戶端和服務端創建鏈接後,數據是以IO流的形式進行交互的,從而實現通訊。
接下來經過一張圖來描述服務器端和客戶端的數據傳輸,以下圖所示。
瞭解了ServerSocket、Socket類的基本用法,爲了讓你們更好地掌握這兩個類的使用,接下來經過一個TCP通訊的案例來進一步學習。
*服務端
1 package com.xujingyang.TCP; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 8 public class TCPServer { 9 public static void main(String[] args) { 10 ServerSocket socket = null; 11 OutputStream outputStream = null; 12 try { 13 //創建基站 14 socket = new ServerSocket(8899); 15 //開始開啓接收模式,接到後返回客戶端的socket對象 16 Socket client = socket.accept(); 17 //獲取向客戶端發送消息的對象流 18 outputStream = client.getOutputStream(); 19 //向客戶端寫數據 20 outputStream.write("你連上了服務器...".getBytes()); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 } finally { 24 try { 25 outputStream.close(); 26 socket.close(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 } 32 }
*客戶端
1 package com.xujingyang.TCP; 2 3 import java.io.InputStream; 4 import java.net.Socket; 5 6 public class TCPClient { 7 public static void main(String[] args) { 8 Socket socket = null; 9 InputStream inputStream = null; 10 try { 11 //創建基站獲取連接地址及端口號 12 socket = new Socket("localhost", 8899); 13 //獲取服務器發過來的字節流 14 inputStream = socket.getInputStream(); 15 16 //開始解析字節流 17 byte[] b = new byte[1024]; 18 String str = ""; 19 int length = -1; 20 while ((length = inputStream.read(b, 0, b.length)) != -1) { 21 str += new String(b, 0, length); 22 } 23 System.out.println(str); 24 /* 25 * int length = inputStream.read(b); 26 System.out.println(new String(b, 0, length)); 27 */ 28 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } finally { 32 //最後關閉 33 try { 34 inputStream.close(); 35 socket.close(); 36 } catch (Exception e2) { 37 e2.printStackTrace(); 38 } 39 } 40 } 41 }
1 package com.xujingyang.TCPImg; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.net.InetAddress; 9 import java.net.ServerSocket; 10 import java.net.Socket; 11 12 public class Server { 13 public static void main(String[] args) { 14 ServerSocket socket = null; 15 Socket client=null;; 16 InputStream in = null; 17 FileOutputStream fOutputStream = null; 18 OutputStream out = null; 19 try { 20 socket = new ServerSocket(8899);//建立服務器,等待客戶端鏈接 21 client = socket.accept();//獲取客戶端鏈接 22 InetAddress clientInetAddress = client.getInetAddress();//獲取客戶端信息 23 System.out.println(clientInetAddress.getHostAddress()+" 已經鏈接."); 24 25 in = client.getInputStream();//獲取socket輸入流 26 27 //===========將輸入流寫入文件 28 fOutputStream = new FileOutputStream(new File("1.png")); 29 byte[] b = new byte[1024]; 30 int lenght=-1; 31 while((lenght = in.read(b))!=-1){ 32 fOutputStream.write(b, 0, lenght); 33 } 34 35 //像客戶端反饋信息 36 out = client.getOutputStream(); 37 out.write("圖片上傳成功ok!".getBytes()); 38 } catch (IOException e) { 39 e.printStackTrace(); 40 }finally{ 41 try { 42 out.close(); 43 fOutputStream.close(); 44 in.close(); 45 client.close(); 46 socket.close(); 47 } catch (Exception e2) { 48 e2.printStackTrace(); 49 } 50 } 51 52 } 53 }
1 package com.xujingyang.TCPImg; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.InputStream; 6 import java.io.OutputStream; 7 import java.net.Socket; 8 9 public class Client { 10 11 public static void main(String[] args) { 12 Socket socket = null; 13 OutputStream out = null; 14 FileInputStream fInputStream = null; 15 InputStream in = null; 16 try { 17 //建立socket,鏈接服務器 18 socket = new Socket("localhost", 8899); 19 //得到socket中的輸出流 20 out = socket.getOutputStream(); 21 //獲取本地文件,而且傳給服務器 22 fInputStream = new FileInputStream(new File("E:/在尚學堂/java基礎/day02/位運算.png")); 23 byte[] b=new byte[1024]; 24 int length=-1; 25 while((length = fInputStream.read(b))!=-1){ 26 out.write(b, 0, length);//寫給服務器 27 } 28 29 socket.shutdownOutput();//客戶端傳送完畢後,必須關閉傳送流,告知服務器 30 31 32 //獲取socket中的輸入流,來讀取服務器發過來的消息 33 in = socket.getInputStream(); 34 byte[] b1=new byte[1024]; 35 int len = in.read(b1); 36 System.out.println(new String(b1,0,len)); 37 38 39 } catch (Exception e) { 40 e.printStackTrace(); 41 }finally{ 42 try { 43 in.close(); 44 out.close(); 45 fInputStream.close(); 46 socket.close(); 47 } catch (Exception e2) { 48 e2.printStackTrace(); 49 } 50 } 51 } 52 }
1 package com.xujingyang.TCPImg; 2 3 import java.io.BufferedOutputStream; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.net.InetAddress; 9 import java.net.ServerSocket; 10 import java.net.Socket; 11 12 public class ThreadServer { 13 public static void main(String[] args) { 14 ServerSocket socket; 15 try { 16 socket = new ServerSocket(8899); 17 System.out.println("等待鏈接..."); 18 while (true) { 19 final Socket client = socket.accept(); 20 InetAddress inetAddress = client.getInetAddress(); 21 System.out.println(inetAddress.getHostAddress()+" 鏈接成功!"); 22 new Thread() { 23 public void run() { 24 try { 25 InputStream in = client.getInputStream(); 26 BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(System.currentTimeMillis()+".jpg")); 27 byte[] b=new byte[1024]; 28 int len=-1; 29 while((len=in.read(b))!=-1){ 30 bufferedOutputStream.write(b, 0, len); 31 } 32 33 34 OutputStream out = client.getOutputStream(); 35 out.write("圖片上傳成功".getBytes()); 36 out.close(); 37 38 bufferedOutputStream.close(); 39 in.close(); 40 client.close(); 41 42 } catch (Exception e) { 43 e.printStackTrace(); 44 } 45 }; 46 }.start(); 47 } 48 49 } catch (Exception e) { 50 e.printStackTrace(); 51 } 52 } 53 }
l IP地址:用來惟一表示咱們本身的電腦的,是一個網絡標示
l 端口號: 用來區別當前電腦中的應用程序的
l UDP: 傳送速度快,可是容易丟數據,如視頻聊天,語音聊天
l TCP: 傳送穩定,不會丟失數據,如文件的上傳、下載
l UDP程序交互的流程
1,建立DatagramSocket對象
2,建立DatagramPacket對象,並封裝數據
3,發送數據
4,釋放流資源
1,建立DatagramSocket對象
2,建立DatagramPacket對象
3,接收數據存儲到DatagramPacket對象中
4,獲取DatagramPacket對象的內容
5,釋放流資源
l TCP程序交互的流程
1,建立客戶端的Socket對象
2,獲取Socket的輸出流對象
3,寫數據給服務器
4,獲取Socket的輸入流對象
5,使用輸入流,讀反饋信息
6,關閉流資源
1,建立服務器端ServerSocket對象,指定服務器端端口號
2,開啓服務器,等待着客戶端Socket對象的鏈接,若有客戶端鏈接,返回客戶端的Socket對象
3,經過客戶端的Socket對象,獲取客戶端的輸入流,爲了實現獲取客戶端發來的數據
4,經過客戶端的輸入流,獲取流中的數據
5,經過客戶端的Socket對象,獲取客戶端的輸出流,爲了實現給客戶端反饋信息
6,經過客戶端的輸出流,寫數據到流中
7,關閉流資源