1. 程式人生 > >UDP 多執行緒服務端 和 簡單客戶端

UDP 多執行緒服務端 和 簡單客戶端

首先來了解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; // 連線物件
}

可以看到,客戶端同時啟動了五個埠與服務端通訊!
服務端也收到了來自不同客戶端的訊息!