UDP 多執行緒服務端 和 簡單客戶端
阿新 • • 發佈:2019-02-16
首先來了解UDP協議的幾個特性
(1)UDP是一個無連線協議,傳輸資料之前源端和終端不建立連線,當UDP它想傳送時就簡單地去抓取來自應用程式的資料,並儘可能快地把它扔到網路上。在傳送端,UDP傳送資料的速度僅僅是受應用程式生成資料的速度、計算機的能力和傳輸頻寬的限制;在接收端,UDP把每個訊息段放在佇列中,應用程式每次從佇列中讀一個訊息段。
(2) 由於傳輸資料不建立連線,因此也就不需要維護連線狀態,包括收發狀態等,因此一臺服務機可同時向多個客戶機傳輸相同的訊息。
(3) UDP資訊包的標題很短,只有8個位元組,相對於TCP的20個位元組資訊包的額外開銷很小。
(4) 吞吐量不受擁擠控制演算法的調節,只受應用軟體生成資料的速率、傳輸頻寬、源端和終端主機效能的限制。 (5)UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持複雜的連結狀態表(這裡面有許多引數)。 (6)UDP是面向報文的。傳送方的UDP對應用程式交下來的報文,在新增首部後就向下交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界,因此,應用程式需要選擇合適的報文大小。
UDP是無狀態的,之前的做的TCP接到客戶端請求後馬上做一個執行緒,將連線物件傳遞進去進行處理!
但是UDP的話是沒有連線物件的,只要訊息包的概念!
這就好像兩個人在一條河邊幹活,TCP是架橋搬運貨物,而UDP是直接把貨物仍過去了,至於貨物是否到達只能通過對岸的人喊一聲收到了!
這裡我模擬的時候收到的訊息包就直接做一個執行緒進行處理了,理想的話應該是根據客戶端的地址來建立執行緒!
因為訊息包內是包含客戶端連線進來時的連線資訊的,所以這裡只需要設定要回復的資料即可!
可以看到,客戶端同時啟動了五個埠與服務端通訊!
服務端也收到了來自不同客戶端的訊息!
(1)UDP是一個無連線協議,傳輸資料之前源端和終端不建立連線,當UDP它想傳送時就簡單地去抓取來自應用程式的資料,並儘可能快地把它扔到網路上。在傳送端,UDP傳送資料的速度僅僅是受應用程式生成資料的速度、計算機的能力和傳輸頻寬的限制;在接收端,UDP把每個訊息段放在佇列中,應用程式每次從佇列中讀一個訊息段。
(2) 由於傳輸資料不建立連線,因此也就不需要維護連線狀態,包括收發狀態等,因此一臺服務機可同時向多個客戶機傳輸相同的訊息。
(3) UDP資訊包的標題很短,只有8個位元組,相對於TCP的20個位元組資訊包的額外開銷很小。
(4) 吞吐量不受擁擠控制演算法的調節,只受應用軟體生成資料的速率、傳輸頻寬、源端和終端主機效能的限制。 (5)UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持複雜的連結狀態表(這裡面有許多引數)。 (6)UDP是面向報文的。傳送方的UDP對應用程式交下來的報文,在新增首部後就向下交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界,因此,應用程式需要選擇合適的報文大小。
UDP是無狀態的,之前的做的TCP接到客戶端請求後馬上做一個執行緒,將連線物件傳遞進去進行處理!
但是UDP的話是沒有連線物件的,只要訊息包的概念!
這就好像兩個人在一條河邊幹活,TCP是架橋搬運貨物,而UDP是直接把貨物仍過去了,至於貨物是否到達只能通過對岸的人喊一聲收到了!
這裡我模擬的時候收到的訊息包就直接做一個執行緒進行處理了,理想的話應該是根據客戶端的地址來建立執行緒!
這樣的話就好像每個客戶端都有自己的鏈路了一樣,總服務端服務收包,然後根據包是給誰的就扔給某執行緒去處理!這和快遞公司的處理流程差不多,總站是總服務端,而快遞員是子服務端!
這裡子服務端中間停留了五秒鐘,這是模擬程式正在處理,處理完畢後再拿一些資料通過總服務端連線物件仍出去!package udpUpload; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; import java.util.Arrays; /** * @說明 UDP連線服務端,這裡一個包就做一個執行緒處理 * @author 崔素強(http://cuisuqiang.iteye.com/) * @version 1.0 * @since */ public class UdpService { public static void main(String[] args) { try { init(); while(true){ try { byte[] buffer = new byte[1024 * 64]; // 緩衝區 DatagramPacket packet = new DatagramPacket(buffer, buffer.length); receive(packet); new Thread(new ServiceImpl(packet)).start(); } catch (Exception e) { } Thread.sleep(1 * 1000); } } catch (Exception e) { e.printStackTrace(); } } /** * 接收資料包,該方法會造成執行緒阻塞 * @return * @throws Exception * @throws IOException */ public static DatagramPacket receive(DatagramPacket packet) throws Exception { try { datagramSocket.receive(packet); return packet; } catch (Exception e) { throw e; } } /** * 將響應包傳送給請求端 * @param bt * @throws IOException */ public static void response(DatagramPacket packet) { try { datagramSocket.send(packet); } catch (Exception e) { e.printStackTrace(); } } /** * 初始化連線 * @throws SocketException */ public static void init(){ try { socketAddress = new InetSocketAddress("localhost", 2233); datagramSocket = new DatagramSocket(socketAddress); datagramSocket.setSoTimeout(5 * 1000); System.out.println("服務端已經啟動"); } catch (Exception e) { datagramSocket = null; System.err.println("服務端啟動失敗"); e.printStackTrace(); } } private static InetSocketAddress socketAddress = null; // 服務監聽個地址 private static DatagramSocket datagramSocket = null; // 連線物件 } /** * @說明 列印收到的資料包,並且將資料原封返回,中間設定休眠表示執行耗時 * @author 崔素強(http://cuisuqiang.iteye.com/) * @version 1.0 * @since */ class ServiceImpl implements Runnable { private DatagramPacket packet; public ServiceImpl(DatagramPacket packet){ this.packet = packet; } public void run() { try { byte[] bt = new byte[packet.getLength()]; System.arraycopy(packet.getData(), 0, bt, 0, packet.getLength()); System.out.println(packet.getAddress().getHostAddress() + ":" + packet.getPort() + ":" + Arrays.toString(bt)); Thread.sleep(5 * 1000); // 5秒才返回,標識服務端在處理資料 // 設定回覆的資料,原資料返回,以便客戶端知道是那個客戶端傳送的資料 packet.setData(bt); UdpService.response(packet); } catch (Exception e) { e.printStackTrace(); } } }
因為訊息包內是包含客戶端連線進來時的連線資訊的,所以這裡只需要設定要回復的資料即可!
我們再來看一下客戶端,為了更形象模擬多客戶訪問的場景,這裡客戶端是一些子執行緒來完成,每個執行緒都有自己的連線物件,IP 一樣但是埠不一樣!
來看一下程式碼:
package udpUpload; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.util.Arrays; import java.util.Random; import java.util.UUID; /** * @說明 UDP連線客戶端 * @author 崔素強(http://cuisuqiang.iteye.com/) * @version 1.0 * @since */ public class UdpClient { public static void main(String[] args) { try { for(int i=0;i<5;i++){ new Thread(new ClientImpl()).start(); } } catch (Exception e) { e.printStackTrace(); } } } /** * @說明 執行緒建立自己的UDP連線,埠動態,傳送一組資料然後接收服務端返回 * @author 崔素強(http://cuisuqiang.iteye.com/) * @version 1.0 * @since */ class ClientImpl implements Runnable{ private Random random = new Random(); private String uuid = UUID.randomUUID().toString(); public void run() { try { init(); byte[] buffer = new byte[1024 * 64]; // 緩衝區 // 傳送隨機的資料 byte[] btSend = new byte[]{(byte)random.nextInt(127), (byte)random.nextInt(127), (byte)random.nextInt(127)}; DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("localhost"), 2233); packet.setData(btSend); System.out.println(uuid + ":傳送:" + Arrays.toString(btSend)); try { sendDate(packet); } catch(Exception e){ e.printStackTrace(); } receive(packet); byte[] bt = new byte[packet.getLength()]; System.arraycopy(packet.getData(), 0, bt, 0, packet.getLength()); if(null != bt && bt.length > 0){ System.out.println(uuid + ":收到:" + Arrays.toString(bt)); } Thread.sleep(1 * 1000); } catch (Exception e) { e.printStackTrace(); } } /** * 接收資料包,該方法會造成執行緒阻塞 * @return * @throws IOException */ public void receive(DatagramPacket packet) throws Exception { try { datagramSocket.receive(packet); } catch (Exception e) { throw e; } } /** * 傳送資料包到指定地點 * @param bt * @throws IOException */ public void sendDate(DatagramPacket packet) { try { datagramSocket.send(packet); } catch (Exception e) { e.printStackTrace(); } } /** * 初始化客戶端連線 * @throws SocketException */ public void init() throws SocketException{ try { datagramSocket = new DatagramSocket(random.nextInt(9999)); datagramSocket.setSoTimeout(10 * 1000); System.out.println("客戶端啟動成功"); } catch (Exception e) { datagramSocket = null; System.out.println("客戶端啟動失敗"); e.printStackTrace(); } } private DatagramSocket datagramSocket = null; // 連線物件 }
可以看到,客戶端同時啟動了五個埠與服務端通訊!
服務端也收到了來自不同客戶端的訊息!