寫在前面的廢話:馬上要找工作了,做了一年的.net ,到要找工作了發現沒幾個大公司招聘.net工程師,真是坑爹呀。哎,java就java吧,咱從頭開始學唄,啥也不說了,玩命擼吧,我真可憐啊。
摘要:
本片記載剛剛學習的網絡編程的內容,網絡編程也稱 Socket 編程 、套接字編程。
什么是Socket?
用於描述ip地址和端口,是一個通信鏈的Handle。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務,每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。Socket就是為了網絡編程提供的一種機制,通信的兩端都有socket,網絡通信其實就是socket間的通信,數據在兩個socket之間通過 IO 傳輸。(摘自黑馬視頻ppt).
網絡通信的三要素:
IP地址:
網絡中設備的標識,也可以用主機名識別,但ip地址唯一,主機名不唯一;
端口號:
用於標識進程的邏輯地址,是不同進程的標識;
傳輸協議:
也即通信的規則,常見的協議由 UDP 協議 和 TCP協議;
UDP協議和TCP協議
(1)UDP
UDP 為應用程序提供了一種無需建立連接就可以發送封裝的 IP 數據報的方法(百度百科截取)。
百度上講的有點復雜,不太容易懂,比較通俗的說法就是:UDP協議會把數據打包,然后扔給目標地址,但是這個包能不能扔的到目標機器上,就不管了,udp就只管扔。
所以這種通信協議的優缺點很明顯了,優點就是:速度快,效率高;缺點就是:安全性低,容易丟包;
(2)TCP
傳輸控制協議(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議(摘自百度百科)。
通俗說法:tcp協議只在已經確定通信雙方都能聯系上對方的時候才能進行通信:
使用tcp協議時要先建立連接;
建立連接的過程:三次握手,如下圖,
UDP協議的簡單使用
發送數據流程
- 創建發送端socket對象;
- 提供數據,並將數據封裝到數據包中;
- 通過socket服務的發送功能,將數據包發出去;
- 釋放資源;
接收數據流程
- 創建接收端socket對象;
- 接收數據;
- 解析數據;
- 輸出數據;
- 釋放資源;
一個案例:
創建兩個控制台程序模擬發送端和接收端,使用udp發送端發送數據,接收端接收數據。
發送端:
SendDemo.java
package com.cherish.Socket; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /* * 使用UDP協議發送數據 * 創建發送端Socket對象 * 創建數據並打包 * 發送數據 * 釋放資源 * * DatagramSocket:此類表示用來發送和接受數據,基於UDP協議 * * DatagramSocket(): * DatagramSocket(int port): * */ public class SendDemo { public static void main(String[] args) throws IOException { //創建發送端Socket對象 DatagramSocket ds = new DatagramSocket(); //創建數據並打包 DatagramPacket表示數據包 //數據 byte[] 設備地址ip 進程的地址 :端口號 String s = "hello udp,i m coming"; byte[] bys = s.getBytes(); int length = bys.length; InetAddress address = InetAddress.getByName("acer-pc"); int port = 8888; DatagramPacket dp = new DatagramPacket(bys, length, address,port); //發送數據 ds.send(dp); //釋放資源 ds.close(); } }
接收端:
ReceiveDemo.java
package com.cherish.Socket; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /* * 使用UDP協議接受對象: * 創建接收端Socket對象 * 接受數據 * 解析數據 * 輸出數據 * 釋放資源 * */ public class ReceiveDemo { public static void main(String[] args) throws IOException { //創建接收端Socket對象,此處的端口號要跟發送端一致 DatagramSocket ds = new DatagramSocket(8888); //接收數據 byte[] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length); System.out.println("接受前"); ds.receive(dp); System.out.println("接收后"); //解析數據 //InetAddress getAddress() InetAddress address = dp.getAddress(); //byte[] getData byte[] data = dp.getData(); int length = dp.getLength(); //輸出數據 System.out.println("sender ----"+address.getHostAddress()); System.out.println(new String(data)); } }
運行結果:
說明:
(1)DatagramSocket類
DatagramSocket() :創建實例,通常用於客戶端編程,他並沒有特定的監聽端口,僅僅使用一個臨時的。
DatagramSocket(int port) :創建實例,並固定監聽Port端口的報文。
DatagramSocket(int port, InetAddress laddr) :這是個非常有用的構建器,當一台機器擁有多於一個IP地址的時候,由它創建的實例僅僅接收來自LocalAddr的報文。
DatagramSocket(SocketAddress bindaddr) :bindaddr對象中指定了端口和地址。
常用方法:
receive(DatagramPacket p) :接收數據報文到p中。receive方法是阻塞的,如果沒有接收到數據報包的話就會阻塞在哪里。
send(DatagramPacket p) :發送報文p到目的地。
setSoTimeout(int timeout) :設置超時時間,單位為毫秒。
close() :關閉DatagramSocket。在應用程序退出的時候,通常會主動的釋放資源,關閉Socket,但是由於異常的退出可能造成資源無法回收。所以應該在程序完成的時候,主動使用此方法關閉Socket,或在捕獲到異常后關閉Socket。
(2)DatagramPacket類
DatagramPacket類用於處理報文,將字節數組、目標地址、目標端口等數據包裝成報文或者將報文拆卸成字節數組。
DatagramPacket(byte[] buf, int length, InetAddress addr, int port) :從buf字節數組中取出offset開始的、length長的數據創建數據對象,目標地址是addr,目標端口是port;
DatagramPacket(byte buf[], int offset, int length, SocketAddress address) :從buf字節數組中取出offset開始的、length長的數據創建數據對象,目標地址是address;
常用方法:
getData() byte[] :從實例中取得報文中的字節數組編碼。
setData(byte[] buf, int offset, int length): 設置數據報包中的數據內容
TCP協議的簡單使用
tcp客戶端發送數據流程:
- 創建發送端Socket對象(創建連接),Tcp的Socket對象與Udp的有所不同,需注意;
- 獲取輸出流對象;
- 發送數據;
- 釋放資源;
tcp服務端接收數據流程:
- 創建接收端Socket對象;
- 監聽(阻塞):如果建立連接失敗,程序會卡在這里,不往下執行;
- 獲取輸入流對象;
- 獲取數據;
- 輸出數據;
- 釋放資源;
案例一:客戶端發送數據,服務端接收數據;
ClietDemo.java:
package com.cherish.Socket; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; /* * 使用tcp協議發送數據 * 創建發送端Socket對象(創建連接) * 獲取輸出流對象 * 發送數據 * 釋放資源 * */ public class TcpClientDemo { public static void main(String[] args) throws IOException{ //創建發送端Socket對象(創建連接) Socket s = new Socket(InetAddress.getByName("acer-pc"),8886); //獲取輸出流對象 OutputStream os = s.getOutputStream(); //發送數據 String str = "hello tcp , i m coming!"; os.write(str.getBytes()); //釋放資源 s.close(); } }
ServerDemo.java:
package com.cherish.Socket; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /* * 使用tcp協議接受對象 * 創建接收端Socket對象 * 監聽(阻塞):如果建立連接失敗,程序會卡在這里,不往下執行 * 獲取輸入流對象 * 獲取數據 * 輸出數據 * 釋放資源 * */ public class TcpServerDemo { public static void main(String[] args) throws IOException { //創建接收端Socket對象 ServerSocket ss = new ServerSocket(8886); //監聽 Socket s = ss.accept(); //獲取輸入流對象 InputStream is = s.getInputStream(); //獲取數據 byte[] bys = new byte[1024]; int len; //用於存儲讀到的字節數 len = is.read(bys); //輸出數據 String client = s.getInetAddress().getHostName(); System.out.println(client+"發來的數據"); System.out.println(new String(bys,0,len)); //釋放資源 s.close(); //ss.close(); //socket對象一般不釋放,因為客戶端不止一個,可能有多個客戶端會發送數據 } }
運行結果:
案例二:對案例一進行改進,服務端收到數據后,將數據由小寫換成大寫,然后返回給客戶端:
Client.java:
package com.cherish.Socket.TcpDemo_2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws IOException { //創建發送端socket對象 Socket s = new Socket(InetAddress.getByName("acer-pc"),8866); //獲取輸出流對象 OutputStream os = s.getOutputStream(); //寫入數據 String str = "hello tcp ,ahahahah!!!"; os.write(str.getBytes()); System.out.println("數據已發送"); //獲取輸入流對象 InputStream is = s.getInputStream(); byte[] bys = new byte[1024]; int len = is.read(bys); String backStr = new String(bys,0,len); System.out.println(backStr); s.close(); } }
Server.java:
package com.cherish.Socket.TcpDemo_2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { //創建服務端接收對象 ServerSocket ss = new ServerSocket(8866); //監聽 來自客戶端的連接 Socket s = ss.accept(); //獲取輸入流對象 InputStream is = s.getInputStream(); //獲取數據 byte[] bys = new byte[1024]; int len = is.read(bys); String str = new String(bys,0,len); //輸出數據 System.out.println(str); //轉換數據 String upperStr = str.toUpperCase(); //獲取輸出流對象 OutputStream os = s.getOutputStream(); //寫入數據到流中 os.write(upperStr.getBytes()); //釋放資源 s.close(); } }
運行結果:
案例三:用tcp模擬一個登錄功能,tcp客戶端輸入用戶名和密碼,然后發送給服務端,服務端根據輸入結果返回登錄或失敗給客戶端;
LoginClient.java:
package com.cherish.Socket.LoginCase; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; /* * 模擬用戶登錄案例 * */ public class LoginClient { public static void main(String[] args) throws IOException { //創建客戶端socket對象 Socket s = new Socket("acer-pc",8885); //獲取用戶名和密碼 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("請輸入用戶名"); String userName = br.readLine(); System.out.println("請輸入密碼"); String password = br.readLine(); //獲取輸出流對象 PrintWriter out = new PrintWriter(s.getOutputStream(),true); //寫出數據 out.println(userName); out.println(password); //獲取輸入流對象 BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream())); //獲取服務端返回的數據 String backStr = serverBr.readLine(); System.out.println(backStr); //關閉資源 s.close(); } }
LoginServer.java:
package com.cherish.Socket.LoginCase; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class LoginServer { public static void main(String[] args) throws IOException { //創建服務端socket對象 ServerSocket ss = new ServerSocket(8885); //監聽 Socket s = ss.accept(); //獲取輸入流對象 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //獲取用戶名和密碼 String userName = br.readLine(); String password = br.readLine(); //判斷用戶名和密碼是否正確 boolean flag = false; if (userName.equals("cherish")&&password.equals("123")) { flag = true; } //獲取輸出流對象 PrintWriter out = new PrintWriter(s.getOutputStream(),true); //返回判斷信息 if (flag) { System.out.println("成功"); out.println("登錄成功"); }else { out.println("登陸失敗"); } //釋放資源 s.close(); //ss.close();//服務端一般不關閉 } }
運行結果:
結語:本篇博客根據傳智播客基礎視頻整理,記載比較簡單,但大致能演示清楚udp和tcp的區別及其用法。個人學習java才不到4天時間,對於java的一些知識點不能講解的很清楚,因此代碼注釋寫的不是很詳細,見諒見諒!哈哈!
本片博客的代碼我上傳到百度雲盤,需要的可自取:
鏈接:https://pan.baidu.com/s/1-dTfG9GY5MBIAlNJg1v0mw 提取碼:3dw6 復制這段內容后打開百度網盤手機App,操作更方便哦