原文地址: http://cuisuqiang.iteye.com/blog/1726644
首先来了解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; // 连接对象
- }
先运行服务端,然后运行客户端,来看一下打印信息:
服务端:
- 服务端已经启动
- 127.0.0.1:381:[56, 5, 1]
- 127.0.0.1:1569:[45, 43, 124]
- 127.0.0.1:9969:[36, 97, 117]
- 127.0.0.1:9937:[2, 110, 80]
- 127.0.0.1:3420:[48, 19, 80]
客户端:
- 客户端启动成功
- 客户端启动成功
- 客户端启动成功
- 客户端启动成功
- 客户端启动成功
- 6d62c2ae-e693-4e35-a295-9a385244cbf0:发送:[56, 5, 1]
- 49fa0ae9-59c3-4db9-97e7-930d9ada50fb:发送:[45, 43, 124]
- 3338ddd0-dfa1-4001-80b1-8e663f7d502f:发送:[36, 97, 117]
- 7b7fc365-e865-4b7c-bd8e-22a5ea095516:发送:[2, 110, 80]
- 53f2c5fc-3194-4e90-9c47-dbcbdef0ccc4:发送:[48, 19, 80]
- 6d62c2ae-e693-4e35-a295-9a385244cbf0:收到:[56, 5, 1]
- 49fa0ae9-59c3-4db9-97e7-930d9ada50fb:收到:[45, 43, 124]
- 3338ddd0-dfa1-4001-80b1-8e663f7d502f:收到:[36, 97, 117]
- 7b7fc365-e865-4b7c-bd8e-22a5ea095516:收到:[2, 110, 80]
- 53f2c5fc-3194-4e90-9c47-dbcbdef0ccc4:收到:[48, 19, 80]
请您到ITEYE网站看原创,谢谢!
http://cuisuqiang.iteye.com/ !
可以看到,客户端同时启动了五个端口与服务端通信!
服务端也收到了来自不同客户端的消息!