Java入門——(8)網路編程,java入門網路編程
關鍵詞:IP地址、、連接埠、UDP、DatagramPacket類、DatagramSocket類、TCP、ServerSocket類、Socket類、檔案上傳 一、基本概念 1、基本概念 ①MAC:MAC(Media Access Control或者Medium Access Control)地址,意譯為媒體存取控制,或稱為物理地址、硬體地址,用來定義網路裝置的位置。在OSI模型中,第三層網路層負責IP地址 ,第二層資料連結層則負責 MAC地址。因此一個主機會有一個MAC地址,而每個網路地址會有一個專屬於它的IP地址。 ②IP地址:是指互連網協議地址(Internet Protocol Address,又譯為網際協議地址),是IP Address的縮寫。IP地址是IP協議提供的一種統一的地址格式,它為互連網上的每一個網路和每一台主機分配一個邏輯地址,以此來屏蔽物理地址的差異。目前還有些ip代理軟體,但大部分都收費。
|
TCP/IP 網路模式
|
|
應用程式層
如HTTP、FTP、DNS |
|
傳輸層
如TCP、UDP |
|
網路層
如IP、ICMP、IGMP |
|
鏈鋸層
如驅動程式、介面 |
③連接埠:可以認為是裝置與外界通訊交流的出口。 連接埠號碼是用兩個位元組(16位的位元)表示,它的取值範圍是0~65 535 ,其中0~1023之間的連接埠號碼用於以下知名的網路服務和應用,使用者的普通應用程式需要使用1024以上的連接埠號碼。連接埠分為物理連接埠和邏輯連接埠(軟體應用程式的數位識別碼)。
④TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)協議屬於傳輸層協議。
TCP協議是連線導向的通訊協定,提供IP環境下的資料可靠傳輸,它提供的服務包括資料流傳送、可靠性、有效流控、全雙工系統操作和多工。通過連線導向、端到端和可靠的資料包發送。通俗說,它是事先為所發送的資料開闢出串連好的通道,然後再進行資料發送;
⑤UDP是無線通訊協議,不為IP提供可靠性、流控或差錯恢複功能。
2、常見InetAddress類的常用方法樣本:
1 public class Example1 { 2 public static void main(String[] args) throws Exception{ 3 //建立一個表示本地主機的InetAddress對象 4 InetAddress localAddress = InetAddress.getLocalHost(); 5 //獲得指定主機的InetAddress對象 6 InetAddress remoteAddress = InetAddress.getByName("www.itcast.cn"); 7 //得到IP地址的主機名稱。 8 System.out.println("原生IP地址:"+localAddress.getHostName()); 9 //獲得字串格式的原始IP地址10 System.out.println("itcast的IP地址:"+remoteAddress.getHostAddress());11 //判斷指定的時間內地址是否可以到達12 System.out.println("3秒是否可達:"+remoteAddress.isReachable(3000));13 14 System.out.println("itcast的主機名稱為:"+remoteAddress.getHostName());15 }16 }
運行結果:
原生IP地址:wrt.localitcast的IP地址:123.57.45.993秒是否可達:falseitcast的主機名稱為:www.itcast.cn
二、UDP通訊
1、DatagramPacket
該類類似於集裝箱,在建立發送端和接收端的DatagramPacket對象時,使用的構造方法有所不同,接收端的構造方法只需要接收一個位元組數組來存放接收到的資料,而發送端的構造方法不但要接收存放了發送資料的位元組數組還需要制定發送端IP地址和連接埠號碼。
DatagramPacket構造方法:
①DatagramPacket(byte[] buf,int length)
用於接收端, 建立DatagramPacket對象時,指定了封裝資料的位元組數組和資料大小。
②DatagramPacket(byte[] buf,int length,InetAddress addr,int port)
用於發送端, 建立DatagramPacket對象時,指定了封裝資料的位元組數組、資料大小、資料包的目標IP地址(addr)以及連接埠號碼(port)。
③DatagramPacket(byte[] buf,int offset,int length)
用於接收端,建立DatagramPacket對象時,指定了封裝資料的位元組數組、資料大小,以及起始位置。offset 參數用於指定接收的資料在放入buf緩衝數組時是從offset處開始的。
④DatagramPacket(byte[] buf,int offset,int length,InetAddress addr,int port)
用於發送端, 建立DatagramPacket對象時,指定了封裝資料的位元組數組、資料大小、資料包的目標IP地址(addr)以及連接埠號碼(port)。offset 參數用於指定發送資料的位移量為offset,即從offset位置開始發送資料。
| DatagramPacket類中的常用方法 |
| 方法聲明 |
功能描述 |
| InetAddress getAddress() |
該方法用於返回傳送端或者接收端的IP地址,如果發送端的DatagramPacket對象,就返回接收端的IP地址,反之,就返回傳送端的IP地址 |
| int getPort() |
該方法用於返回傳送端或者接收端的連接埠號碼,如果發送端的DatagramPacket對象,就返回接收端的連接埠號碼,反之,就返回傳送端的連接埠號碼 |
| byte[] getData() |
該方法用於返回將要接收或者將要發送的資料,如果是發送端的DatagramPacket對象,就返回將要發送的資料,反之,就返回接收到的資料 |
| int getLength() |
該方法用於返回將要接收或者將要發送資料的長度,如果是發送端的DatagramPacket對象,就返回將要發送的資料長度,反之,就返回接收到資料的長度 |
2、DatagramSocket
DatagramSocket類似與碼頭,執行個體對象就可以發送和接收DatagramPacket資料包,在建立發送端和接收端的DatagramSocket對象時,使用的構造方法有所不同。
DatagramSocket構造方法:
①DatagramSocket()
用於建立發送端的DatagramSocket對象時,在建立對象時,並沒有指定連接埠號碼,此時,系統會分配一個沒有被其他網路程式使用的連接埠號碼。
②DatagramSocket(int port)
該方法即可用於建立接收端的DatagramSocket對象,又可以建立發送端的DatagramSocket對象,在建立接收端的DatagramSocket對象時,必須要指定一個連接埠號碼,這樣就可以監聽指定的連接埠。
③DatagramSocket(int port,InetAddress addr)
使用該構造方法在DatagramSocket時,不僅指定連接埠號碼,還指定了相關的IP地址,這種情況適用於計算上有多塊網卡的情況。
| DatagramSocket類中的常用方法 |
| 方法聲明 |
功能描述 |
| void receive(DatagramPacket p) |
該方法用於將接收到的資料填充到DatagramPacket資料包中,在接收到資料之前會一直處於阻塞狀態,只有當接收到資料包時,該方法才會返回。 |
| void send(DatagramPacket p) |
該方法用於發送DatagramPacket資料包,發送的資料包中包含將要發送的資料、資料的長度、遠程主機的IP地址和連接埠號碼 |
| void close() |
關閉當前的Socket,通知驅動程式釋放為這個Socket保留的資源。 |
3、UDP 網路程式
在通訊時只有接收端程式先運行,才能避免因發送端發送的資料無法接收,而造成資料丟失。樣本:
1 import java.net.DatagramPacket; 2 import java.net.DatagramSocket; 3 4 //接收端程式 5 public class Example2 { 6 public static void main(String[] args) throws Exception{ 7 //建立一個長度為1024的位元組數組,用於接收資料 8 byte [] buf = new byte[1024]; 9 //定義一個DatagramSocket對象,監聽的連接埠為895410 DatagramSocket ds = new DatagramSocket(8954);11 //定義一個DatagramPacket對象,用於接收資料12 DatagramPacket dp = new DatagramPacket(buf,1024);13 System.out.println("等待接收資料");14 ds.receive(dp); //等待接收資料,如果沒有資料則會阻塞15 //調用DatagramPacket的方法獲得接收的訊息,包括內容、長度、IP地址和連接埠號碼16 String str = new String(dp.getData(),0,dp.getLength())17 +"from"+dp.getAddress().getHostAddress()+":"+dp.getPort();18 System.out.println(str); //列印收到的資訊19 ds.close(); //釋放資源20 }21 }22 23 24 import java.net.DatagramPacket;25 import java.net.DatagramSocket;26 import java.net.InetAddress;27 28 //發送端程式29 public class Example3 {30 public static void main(String[] args) throws Exception {31 //建立一個DatagramSocket對象32 DatagramSocket ds = new DatagramSocket(3000);33 String str = "Hello World!"; //要發送的資料34 /*35 * 建立一個要發送的資料包,包括髮送資料,資料長度,接收端IP地址以及連接埠號碼36 */37 DatagramPacket dp = new DatagramPacket(str.getBytes(),str.length(),38 InetAddress.getByName("localhost"),8954);39 System.out.println("發送訊息");40 ds.send(dp); //發送資料41 ds.close(); //釋放資源42 }
運行結果
發送訊息等待接收資料Hello World!from127.0.0.1:3000
解析:發送貨物(資料)前,確定到貨碼頭是否能接收。
建立空間(資料容器)接收貨物(資料),建立碼頭【DatagramSocket(8954)】並即時監聽發貨碼頭髮貨通道(連接埠),建立集裝箱並將空間加入用於接收貨物,一直等待接收貨物,接收碼頭將貨物填充到集裝箱中,擷取到貨物資訊(資料等資訊)。
發送貨物需要建一個碼頭【DatagramSocket(3000)】,碼頭可指定發送通道即連接埠(也可以不指定發送通道),將要發送貨物(資料)裝進集裝箱(DatagramPacket
)中,並指定發送到的碼頭名字(IP地址或主機名稱)及接收通道(連接埠),通過碼頭把集裝箱發出去[send()],騰出空間(close)。
三、TCP通訊
1、ServerSocket
在開發TCP程式時,首先需要建立伺服器端程式,其構造方法如下:
①ServerSocket()
使用該構造方法在建立ServerSocket對象時並沒有綁定連接埠號碼,不能直接使用,還需要繼續調用bind(SocketAddress endpoint)方法將其綁定到指定的連接埠上,才能正常使用。
②ServerSocket(int port)【最常用】
使用用該構造方法在建立ServerSocket對象時,就可以將其綁定到一個指定的連接埠號碼上。
③ServerSocket(int port,int backlog)
backlog 參數用於指定在伺服器忙時,可以與之保持串連請求的等待客戶數量,如果沒有指定這個參數預設為50 。
④ServerSocket(int port,int backlog,InetAddress bindAddr)
指定了相關的IP地址,適用於電腦上有多塊網卡和多個IP的情況。
| ServerSocket類中的常用方法 |
| 方法聲明 |
功能描述 |
| Socket accept() |
該方法用於等待用戶端的串連,在用戶端串連之前一直處於阻塞狀態,如果有用戶端串連就會返回一個與之對應的Socket對象 |
| InetAddress getInetAddress() |
該方法用於返回一個InetAddress對象,該對象封裝了ServerSocket綁定的IP地址 |
| boolean isClosed() |
該方法用於判斷ServerSocket對象是否為關閉狀態,如果是關閉狀態則返回true,反之則返回false |
| void bind(SocketAddress endpoint) |
該方法用於判斷ServerSocket對象綁定到指定的IP地址和連接埠號碼,其中參數endpoint封裝了IP地址和連接埠號碼。 |
2、Socket
Socket類常用構造方法: ①Socket() 使用該構造方法在建立Socket對象時,並沒指定IP地址和連接埠號碼,建立對象後還需調用connect(SocketAddress endpoint)方法,才能完成與指定伺服器的串連,參數endpoint封裝了IP地址和連接埠號碼。 ②Socket(String host,int port) 使用該構造方法在建立Socket對象時,根據參數去串連在指定IP地址和連接埠上啟動並執行伺服器程式,其中參數host接收的一個字元類型的IP地址。 ③Socket(InetAddress addres,int port) 與第二個構造方法類似,參數address用於接收一個InetAddress類型的對象,該對象用於封裝一個IP地址。
| Socket類中的常用方法 |
| 方法聲明 |
功能描述 |
| int getPort() |
該方法返回一個int類型對象,該對象時Socket對象與伺服器端串連的連接埠號碼 |
| InetAddress getLocalAddress() |
該方法用於擷取Socket對象綁定的本地IP地址,並將IP地址封裝成InetAddress類型的對象返回 |
| void close() |
該方法用於關閉Socket串連,結束本次通訊。在關閉Socket之前,應將於Socket相關的所有的輸入與輸出資料流全部關閉,這是因為一個良好的程式應該在執行完畢時釋放所有的資源 |
| IputStream getInputStream() |
該方法返回一個InputStream類型的輸入資料流對象,如果該對象是由伺服器端的Socket返回,就用於讀取用戶端發送的資料,反之,用於讀取伺服器端發送的資料 |
| OutputStream getOutputStream() |
該方法返回一個OutputStream類型的輸出資料流對象,如果該對象是由伺服器端的Socket返回,就用於向用戶端發送資料,反之,用於向伺服器端發送資料 |
3、簡單的TCP網路程式
1 import java.io.OutputStream; 2 import java.net.ServerSocket; 3 import java.net.Socket; 4 5 public class Example4 { 6 public static void main(String[] args) throws Exception { 7 new TCPServer().listen(); //建立TCPServer對象,並調用listen()方法 8 } 9 }10 //TCP伺服器端11 class TCPServer{12 private static final int PORT= 7788;//定義一個連接埠號碼13 14 public void listen() throws Exception{ //定義一個listen()方法,拋出一個異常15 ServerSocket serverSocket = new ServerSocket(PORT);//建立ServerSocket對象16 Socket client=serverSocket.accept(); //調用ServerSocket的accept()方法接收資料17 OutputStream os = client.getOutputStream(); //擷取用戶端的輸出資料流18 System.out.println("開始與用戶端交換資料");19 os.write(("Java歡迎你!").getBytes());20 Thread.sleep(5000); //類比執行其他功能佔用的時間21 System.out.println("結束與用戶端互動資料");22 os.close();23 client.close();24 }25 }
1 import java.io.InputStream; 2 import java.net.InetAddress; 3 import java.net.Socket; 4 5 public class Example5 { 6 public static void main(String[] args) throws Exception{ 7 new TCPClient().connect();//建立TCPClient對象,並調用connect()方法 8 } 9 }10 //TCP用戶端11 class TCPClient{12 private static final int PORT=7788;//服務端的連接埠號碼13 public void connect() throws Exception{14 //建立一個Socket並串連到給出地址和連接埠號碼的電腦15 Socket client = new Socket(InetAddress.getLocalHost(),PORT);16 InputStream is = client.getInputStream(); //得到接收資料的流17 byte[] buf = new byte[1024]; //定義1024個位元組數組的緩衝區18 int len=is.read(buf); //將資料讀取到緩衝區中19 System.out.println(new String(buf,0,len)); //將緩衝區中的資料輸出20 client.close(); //關閉Socket對象,釋放資源21 }22 }
Example4 運行結果:開始與用戶端交換資料結束與用戶端互動資料Example5 運行結果:Java歡迎你!
4、TCP案例——檔案上傳
實現圖片上傳到伺服器的功能。
服務端程式:
1 import java.io.File; 2 import java.io.FileOutputStream; 3 import java.io.InputStream; 4 import java.io.OutputStream; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 8 public class Example7 { 9 public static void main(String[] args) throws Exception{10 ServerSocket serverSocket = new ServerSocket(10001);//建立ServerSocket對象11 while (true){12 //調用accept()方法接收用戶端請求,得到Socket對象13 Socket s = serverSocket.accept();14 //每當和用戶端建立Socket串連後,單獨開啟一個線程處理和用戶端的互動15 new Thread(new ServerThread(s)).start();16 }17 }18 }19 class ServerThread implements Runnable{20 private Socket socket ; //持有一個Socket類型的屬性21 public ServerThread(Socket socket){ //構造方法中吧Socket對象作為實參傳入22 this.socket=socket;23 }24 25 @Override26 public void run() {27 String ip = socket.getInetAddress().getHostAddress(); //擷取用戶端的IP地址28 int count =1; //上傳圖片個數29 try{30 InputStream in = socket.getInputStream();31 //建立上傳圖片目錄的File對象32 File parentFile =new File("/Users/adims/Downloads/upload/");33 if (!parentFile.exists()){ //如果不存在,就建立這個目錄34 parentFile.mkdir();35 }36 //把用戶端的IP地址作為上傳出檔案的檔案名稱37 File file = new File(parentFile,ip+"("+count+").jpeg");38 while (file.exists()){39 //如果檔案名稱存在,則把count++40 file=new File(parentFile,ip+"("+(count++)+").jpeg");41 }42 //建立FileOutputStream對象43 FileOutputStream fos = new FileOutputStream(file);44 byte[] buf=new byte[1024]; //定義一個位元組數組45 int len=0; //定義一個int類型的變數len,初始值為046 while ((len=in.read(buf))!=-1){ //迴圈讀取資料47 fos.write(buf,0,len);48 }49 OutputStream out = socket.getOutputStream(); //擷取服務端的輸出資料流50 out.write(("上傳成功").getBytes()); //上傳成功後向用戶端寫出"上傳成功"51 fos.close(); //關閉輸出資料流對象52 socket.close(); //關閉Socket對象53 }catch (Exception e){54 throw new RuntimeException(e);55 }56 }57 }
用戶端程式:
1 import java.io.FileInputStream; 2 import java.io.InputStream; 3 import java.io.OutputStream; 4 import java.net.InetAddress; 5 import java.net.Socket; 6 7 public class Example8 { 8 public static void main(String[] args) throws Exception{ 9 Socket socket= new Socket(InetAddress.getLocalHost(),10001); //建立用戶端Socket對象,指定IP地址和連接埠號碼10 OutputStream out= socket.getOutputStream(); //擷取Socket的輸出資料流對象11 //建立FileInputStream對象12 FileInputStream fis = new FileInputStream("/Users/adims/Downloads/WechatIMG1.jpeg");13 byte[] buf =new byte[1024]; //定義一個位元組數組14 int len; //定義一個int類型的變數len15 while ((len=fis.read(buf))!=-1){ //迴圈讀取資料16 out.write(buf,0,len);17 }18 socket.shutdownOutput(); //關閉用戶端輸出資料流19 InputStream in = socket.getInputStream(); //擷取Socket的輸入資料流對象20 byte[] bufMsg = new byte[1024]; //定義一個位元組數組21 int num =in.read(bufMsg); //接收服務端的資訊22 String Msg = new String(bufMsg,0,num);23 System.out.println(Msg);24 fis.close(); //關閉輸入資料流對象25 socket.close(); //關閉Socket對象26 }27 }
需注意:shutdownOutput()方法非常重要,因為伺服器端程式在while迴圈中讀取用戶端發送的資料,當讀取到-1時才會結束迴圈,如果用戶端不調用shutdownOutput()方法關閉輸出資料流,伺服器端就不會讀到-1,而會一直執行while迴圈,同時用戶端伺服器端的read(byte[])方法也是一個阻塞方法,這樣用戶端與伺服器端進入一個“死結”狀態。