前言
繼續今天我們的Java網路程式設計——TCP和UDP通訊
一、TCP和UDP概述
傳輸層通常以TCP和UDP協議來控制端點與端點的通訊
TCP | UDP | |
---|---|---|
協議名稱 | 傳輸控制協議 | 使用者資料包協議 |
是否連線 | 面向連線的協議。資料必須要建立連線 | 無連線的協議,每個資料包中都給出完整的地址資訊,因此不需要事先建立傳送方和接受方的連線 |
是否可靠 | 可靠協議。確保收方完全地獲取傳送方所傳送的全部資料 | 不可靠協議。傳送方所傳送的資料包並不一定以相同的次序到達接收方。 |
可以傳輸的資料大小 | 傳輸資料大小不受限制。一旦連線建立,雙方可以按統一的格式傳輸大的資料 | 傳輸資料時是有大小限制的。每個被傳輸的資料包必須限定在64KB之類 |
資料傳輸方式 | IO流 | DatagramPacket |
二、UDP
1.UDP通訊概述
UDP協議是一種對等通訊的實現,傳送方只需要接受方的IP(地址)和Port(埠),就可以直接向它傳送資料,不需要線連線。每個程式都可以作為伺服器,也可以作為客戶端。UDP是一種無連線的傳輸協議,每個資料包的大小限定在64KB以內。資料包是一個在網路上傳送的獨立資訊,它的到達。到達時間以及內容本身等都不能得到保證。這種傳輸方式是無序的,也不能確保絕對的安全可靠,但它很簡單也具有較高的效率。
使用UDP協議進行資料傳輸是,需要將需要傳輸資料定義為資料包(DatagramPaket),在資料包中指明資料所要到達Socket(主機地址和埠號),然後再將資料包傳送出去。例項化DatagramPacket時使用引數port和沒有使用引數port的區別在與,提供port的一方可以讓別人主動傳送訊息過來,而沒有引數port的則會在傳送訊息時自動繫結一個本地沒有使用的埠。在接收到傳送的資料包(DatagramPaket)時,不僅可以獲取資料,還可以獲得傳送方的IP和Port,這樣就可以向傳送方傳送資料,因此,本質上二者是對等的。
2.UDP通訊特點
1、UDP是一種無連線的協議,每個資料包都是一個獨立的資訊,包括完整的原地址或目的地址,它在網路上任何可能的路徑傳往目的地,因此能否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的。
2、UDP不屬於連線型協議,因而具有資源消耗小,處理速度快的優點,所以通常音訊視訊和普通資料在傳輸時使用UDP較多,因為它們即使偶爾丟一兩個資料包,也不會對接收結果產生太大的影響。
程式碼如下(示例):
3.UDP通訊傳輸實現的基石
UDP通訊的Socket使用DatagramSocket類實現,資料包使用DatagramPaket實現
3.1、DatagramPake常用方法
InetAddress getAddress() | 得到傳送方IP地址 |
---|---|
int getPort() | 得到傳送方的埠號 |
byte[] getData() | 返回接收緩衝區,這是一個byte[] |
int getLength() | 接收位元組的真實大小,通常用於從byte[]中提取出有效資料 |
int getOffset() | 返回將要傳送或則接收的資料偏移量 |
3.2、DatagramSocket常用方法
DatagramSocket() | 空建構函式 |
---|---|
DatagramSocket(int port) | 指定通訊埠 |
void receive(DatagramPaket p) | 接收資料包 |
void send(DatagramPaket p) | 傳送資料包 |
void close() | 關閉Socket |
4.UDP通訊實現原理
無論一個UDP通訊程式的功能多麼功能齊全,程式多麼複雜,七基本結構都是一樣的,都包括以下四個基本步驟
1、在接收端指定一個埠號來建立DatagramSocket,然後建立一個接收資料包(DatagramPaket),使用recevie方法等待傳送方請求報文,這將阻塞伺服器執行緒
2、在傳送方建立一個DatagramSocket,使用接收方的IP和埠來建立傳送資料包(DatagramPaket),使用send方法傳送。現在接收方的recevie方法被喚醒,同時會將傳送方的資料包內容填充到接收方的DatagramPaket中。
3、接收方從傳送方的資料包中獲得傳送方的IP和埠,使用它們構造一個傳送資料包,然後傳送給傳送方,這樣就實現了傳送方和接收方的通訊
4、在通訊完成後,在客服端和服務端中分別關閉Socket
5.UDP通訊原理(程式碼實現)
程式碼如下(傳送端):
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class SendMessage {
public static void main(String[] args) throws Exception {
// 建立傳送端Socket物件
DatagramSocket sendSocket = new DatagramSocket();
// 準備需要傳送的資料
String message = "hello";
// 建立一個緩衝區
byte[] messageByte = message.getBytes();
// 獲取緩衝區中資料的真實長度
int messageLen = message.length();
// InetAddress例項化獲取本機通訊地址
InetAddress address = InetAddress.getLocalHost();
// 設定通訊埠號
int port = 12345;
// 打包資料
DatagramPacket sendPacket = new DatagramPacket(messageByte, messageLen, address, port);
// 傳送資料
sendSocket.send(sendPacket);
// 傳送端等待接收端成功接收資訊後返回的回應
// 建立一個緩衝區,容量儘量設定大一點因為不知道傳送過來的資訊有多大
byte[] recevieByte = new byte[1024*10];
int len = recevieByte.length;
// 接收資料包
DatagramPacket receivePacket = new DatagramPacket(recevieByte, len);
// 接收資料
sendSocket.receive(receivePacket);
// 獲取接收端傳送過來的真實長度以及資料
byte[] data = receivePacket.getData();
int length = receivePacket.getLength();
String receiveData = new String(data,0,length);
// 獲取傳送者的IP
address = receivePacket.getAddress();
String ip = address.getHostAddress();
System.out.println("接收來自:"+ip+"的資料,內容是:"+receiveData);
// 關閉資源
sendSocket.close();
}
}
程式碼如下(接收端):
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class RecevieMessage {
public static void main(String[] args) throws Exception {
// 接收訊息的埠(與傳送端保持一致)
int port = 12345;
DatagramSocket recevieSocket = new DatagramSocket(port);
// 設定緩衝區接收發過來的資訊
byte[] receiveByte = new byte[1024*10];
int len = receiveByte.length;
// 接收資料包
DatagramPacket receviePacket = new DatagramPacket(receiveByte, len);
// 接收資料
recevieSocket.receive(receviePacket);
// 獲取實際接收到的資料及其大小
byte[] data = receviePacket.getData();
int length = receviePacket.getLength();
String receiveData = new String(data,0,length);
// 獲取傳送者的IP
InetAddress address = receviePacket.getAddress();
String ip = address.getHostAddress();
System.out.println("接收來自:"+ip+"的資料,內容是:"+receiveData);
// 接收端接收到資訊後傳送一條確認接收的訊息到傳送端
String message = "OK";
byte[] messageByte = message.getBytes();
int messageLength = message.length();
// 從已收到的資料包中獲取IP和port
address = receviePacket.getAddress();
int port1 = receviePacket.getPort();
//構造新資料包
DatagramPacket sendPacket = new DatagramPacket(messageByte, messageLength, address, port1);
// 傳送資料
recevieSocket.send(sendPacket);
// 關閉資源
recevieSocket.close();
}
}
傳送端輸出結果:
接收端輸出結果:
總結
**注意:**在傳送端與接收端啟動測試時最好最好先啟動接收端,因為這樣才能確保資訊能傳送出去,接收端鞥接收到資訊。