網絡編程
網絡編程
網絡基礎
計算機網絡
把分佈在不同地理區域的計算機與專門的外部設備用通信線路互連成一個規模大、功能強的網絡系統,從而使眾多的計算機可以方便地互相傳遞信息、共享硬件、軟件、數據信息等資源。
網絡編程的目的:
直接或間接地通過網絡協議與其它計算機實現數據交換,進行通訊。
網絡編程中有兩個主要的問題:
- 如何準確地定位網絡上一台或多台主機;定位主機上的特定的應用
- 找到主機後如何可靠高效地進行數據傳輸
網絡通信要素
想要實現網絡中的主機互相通信,需要知道通信雙方的地址(IP、端口號)和一定的規則(網絡通信協議)
IP和端口號
IP
IP的認識
-
唯一的標識Internet上的計算機(通信實體)
-
在Java中,使用InetAddress類代表IP
-
本地迴環地址(hostAddress)
主機名(hostName):localhost
-
IP地址分類
- 方式一:IPV4 和 IPV6
- IPV4:4個位元組組成,4個範圍是0-255。2011年初已經用盡。以點分十進制表示,如192.168.0.1
- IPV6:128位(16個位元組),寫成8個無符號整數,每個整數用4個十六進制位表示,數之間用冒號(:)分開,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- 方式二:公網地址(萬維網使用)和私有地址(局域網使用)。
- 192.168.開頭的就是私有址址,範圍即為192.168.0.0–192.168.255.255,專門為組織機構內部使用
- 方式一:IPV4 和 IPV6
-
特點:不易記憶
InetAddress類的使用
-
兩種方式表示地址:
-
InetAddress類沒有提供公共的構造器,而是提供了幾個靜態方法來獲取InetAddress 實例
- 獲取本地的IP地址:
public static InetAddress getLocalHost()
- 通用的方法(host可以是域名或是IP地址):
public static InetAddress getByName(String host)
- 獲取本地的IP地址:
-
幾個常用的方法(前兩個常用)
- public String getHostAddress() :返回 IP 地址字符串(以文本表現形式)。
- public String getHostName() :獲取此 IP 地址的主機名
- public boolean isReachable(int timeout):測試是否可以達到該地址
端口號
端口號的認識
端口號標識正在計算機上運行的進程(程序),可以用端口號區別不同的進程
- 不同的進程有不同的端口號
- 端口號被規定為一個 16 位的整數 0~65535。
- 端口分類:
- 公認端口:0~1023。被預先定義的服務通信佔用(如:HTTP佔用端口80,FTP佔用端口21,Telnet佔用端口23)
- 註冊端口:1024~49151。分配給用戶進程或應用程序。(如:Tomcat佔用端口8080,MySQL佔用端口3306,Oracle佔用端口1521等)
- 動態/ 私有端口:49152~65535。
端口號和IP地址組合在一起得出一個網絡套接字:Socket
網絡通信協議
計算機網絡中實現通信必須有一些約定,即通信協議,對速率、傳輸代碼、代碼結構、傳輸控制步驟、出錯控制等制定標準。
因為網絡協議過於複雜,在制定協議時,把複雜成份分解成一些簡單的成份,再將它們複合起來。
最常用的複合方式是層次方式,即同層間可以通信、上一層可以調用下一層,而與再下一層不發生關係。各層互不影響,利於系統的開發和擴展。
TCP/IP協議簇
傳輸層協議中兩個重要的協議:
- 傳輸控制協議TCP(Transmission Control Protocol)
- 用戶數據報協議UDP(User Datagram Protocol)
TCP協議
類似於生活中打電話
- 使用TCP協議前,須先建立TCP連接,形成傳輸數據通道
- 傳輸前,採用「三次握手」方式,點對點通信,是可靠的
- TCP協議進行通信的兩個應用進程:客戶端、服務端。
- 在連接中可進行大數據量的傳輸
- 傳輸完畢,需釋放已建立的連接,效率低
UDP協議
類似於發短訊
- 將數據、源、目的封裝成數據包,不需要建立連接
- 每個數據報的大小限制在64K內
- 發送不管對方是否準備好,接收方收到也不確認,故是不可靠的
- 可以廣播發送
- 發送數據結束時無需釋放資源,開銷小,速度快
Socket
概念
- 網絡上具有唯一標識的IP地址和端口號組合在一起構成唯一能識別的標識符套接字Socket
- 通信的兩端都要有Socket,是兩台機器間通信的端點。
- 網絡通信其實就是Socket間的通信。
- Socket允許程序把網絡連接當成一個流,數據在兩個Socket間通過IO傳輸。
- 一般主動發起通信的應用程序屬客戶端,等待通信請求的為服務端。
Socket常用的方法
Socket類的常用構造器
-
public Socket(InetAddress address,int port)
創建一個流套接字並將其連接到指定IP地址的指定端口號。
-
public Socket(String host,int port)
創建一個流套接字並將其連接到指定主機上的指定端口號。
Socket類的常用方法:
-
public InputStream getInputStream()
返回此套接字的輸入流。可以用於接收網絡消息
-
public OutputStream getOutputStream()
返回此套接字的輸出流。可以用於發送網絡消息
-
public InetAddress getInetAddress()
此套接字連接到的遠程IP地址;如果套接字是未連接的,則返回null。
-
public InetAddress getLocalAddress()
獲取套接字綁定的本地地址,即本端的IP地址
-
public int getPort()
此套接字連接到的遠程端口號;如果尚未連接套接字,則返回0。
-
public int getLocalPort()
返回此套接字綁定到的本地端口。如果尚未綁定套接字,則返回-1,即本端的端口號。
-
public void close()
關閉此套接字。套接字被關閉後,便不可在以後的網絡連接中使用(即無法重新連接或重新綁定)。需要創建新的套接字對象。關閉此套接字也將會關閉該套接字的 InputStream 和OutputStream。
-
public void shutdownInput()
如果在套接字上調用shutdownInput()後從套接字輸入流讀取內容,則流將返回 EOF(文件結束符)。即不能在從此套接字的輸入流中接收任何數據。
-
public void shutdownOutput()
禁用此套接字的輸出流。對於TCP套接字,任何以前寫入的數據都將被發送,並且後跟TCP的正常連接終止序列。如果在套接字上調用shutdownOutput()後寫入套接字輸出流,則該流將拋出IOException。即不能通過此套接字的輸出流發送任何數據。
基於Socket的TCP編程
Java語言的基於套接字編程分為服務端編程和客戶端編程
客戶端Socket的基本的步驟
- 創建Socket對象,指明服務器端的ip和端口號
- 創建的同時會自動向服務器方發起連接
- 獲取一個輸出流,用於輸出數據
- 寫出數據
- 資源關閉
public void client(){
//客戶端
Socket socket = null;
OutputStream os = null;
try {
//1. 創建Socket對象,指明服務器端的ip和端口號
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet,8899);
//2. 獲取一個輸出流,用於輸出數據
os = socket.getOutputStream();
//3. 寫出數據
os.write("您好,我是客戶端".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 資源的關閉
try {
if(os != null)
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服務端Socket的基本步驟
- 創建服務器端的ServerSocket,指明自己的端口號
- 服務器必須事先建立一個等待客戶請求建立套接字
的 連接的ServerSocket對象
- 服務器必須事先建立一個等待客戶請求建立套接字
- 調用accept()方法,表示接收來自客戶端的Socket
- 獲取一個輸入流
- 讀取輸入流中的數據
- 資源的關閉
public void server(){
//服務端
ServerSocket serverSocket = null;
Socket socket = null;
InputStream inputStream = null;
ByteArrayOutputStream baos = null;
try {
//1. 創建服務器端的ServerSocket,指明自己的端口號
serverSocket = new ServerSocket(8899);
//2. 調用accept()方法,表示接收來自客戶端的Socket
socket = serverSocket.accept();
//3. 獲取一個輸入流
inputStream = socket.getInputStream();
//4. 讀取輸入流中的數據
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = inputStream.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//5. 資源的關閉
try {
if(baos != null)
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(inputStream != null)
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(serverSocket != null)
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意
-
客戶端發送文件給服務端,服務端將文件保存在本地
- 客戶端發送文件時需要先利用節點流將文件讀取,在利用Socket寫出
FileInputStream fis = new FileInputStream(new File("殷志源.png")); byte[] buffer = new byte[1024]; int len; while((len = fis.read(buffer)) != -1){ ops.write(buffer,0,len); } - 服務端接受文件時,因為要保存在本地,要利用節點流
FileOutputStream fos = new FileOutputStream(new File("殷志源(1).png")); byte[] buffer = new byte[1024]; int len; while((len = ips.read(buffer)) != -1){ fos.write(buffer,0,len); }
- 客戶端發送文件時需要先利用節點流將文件讀取,在利用Socket寫出
-
從客戶端發送文件給服務端,服務端保存到本地。並返回「發送成功」給客戶端。
-
服務端
//服務器端給客戶端反饋 OutputStream os = socket.getOutputStream(); os.write("發送成功".getBytes()); -
客戶端
//關閉數據的輸出 socket.shutdownOutput(); //接收來自服務器端的數據並顯示在控制台上 InputStream is = socket.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer1 = new byte[1024]; int len1; while((len = is.read(buffer1)) != -1){ baos.write(buffer1,0, len); } System.out.println(baos.toString());read()方法是阻塞式方法,程序一直停在read()方法這裡,等待數據。沒有數據就不繼續往下執行,直到得到數據。所以需要調用socket的shutdownOutput()方法,關閉數據的輸出
阻塞式方法:在程序調用改方法時,必須等待輸入數據可用或者檢測到輸入結束或者拋出異常,否則程序會一直停留在該語句上,不會執行下面的語句。
-
UDP網絡編程
- 類DatagramSocket和DatagramPacket實現了基於 UDP協議網絡程序。其中可以理解為DatagramSocket為快遞員,DatagramPacket為包裹,即為需要傳輸的數據
- UDP數據報通過數據報套接字DatagramSocket發送和接收
- DatagramPacket對象封裝了UDP數據報,在數據報中包含了發送端的IP地址和端口號以及接收端的IP地址和端口號。
- UDP協議中每個數據報都給出了完整的地址信息,因此無須建立發送方和接收方的連接
代碼舉例
其中沒有用try-catch來處理異常,選擇拋出異常,但是實際使用時要用try-catch來處理異常,此處舉例為簡寫
@Test
public void send() throws IOException {
//發送端
DatagramSocket socket = new DatagramSocket();
String str = "UDP方式發送文件";
byte[] data = str.getBytes();
InetAddress inetAddress = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inetAddress,9090);
socket.send(packet);
socket.close();
}
@Test
public void receiver() throws IOException {
//接收端
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0 ,packet.getLength()));
socket.close();
}
URL編程
URL
- URL(Uniform Resource Locator):統一資源定位符,它表示Internet上某一資源的地址。
- URL的基本結構由5部分組成
< 傳輸協議>://< 主機名>:< 端口號>/< 文件名># 片段名? 參數列表
常用方法
- public String getProtocol( ) 獲取該URL的協議名
- public String getHost( ) 獲取該URL的主機名
- public String getPort( ) 獲取該URL的端口號
- public String getFile( ) 獲取該URL的文件名
- public String getQuery( ) 獲取該URL的查詢名