1.基本概念介绍:
首先得简单介绍下UDP。
UDP( User Datagram Protocol )协议是用户数据报,在网络中它与TCP协议一样用于处理数据包。在OSI模型中,在第四层——传输层,处于IP协议的上一层。它是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的 但是这种协议却是方便快捷的,因此很多通信工具和游戏仍然采用这种通信方式,虽然有时会出现数据丢帧的现象。
(此处可以简单的理解为某些无良快递机构,因为缺乏责任心,只负责发送至于包裹是否能顺利送达目的地毫不关心,因此会出现丢包或者延迟接收的现象)。
在Java中操纵UDP 使用位于JDK中Java.net包下的DatagramSocket和DatagramPacket类,可以非常方便地控制用户数据报文进行UDP的程序开发。
在UDP开发中使用DatagramPacket类来包装一条需要发送的信息,之后使用DatagramSocket类用于完成信息的发送操作。
一个完整的UDP网络开发程序是包含服务器端和客户端的。
关于UDP开发中的服务器和客户端的区别:
客户端与服务器端的唯一区别在于:服务器端的IP地址、端口是固定的,所以客户端可以直接将该数据报发送给服务器端,而服务器端则需要根据接收到的数据报来决定"反馈"数据报的目的地。
下面简单介绍下 DatagramSocket和DatagramPacket类的常用方法。
DatagramSocket类:创建接收和发送UDP的Socket实例
DatagramSocket(): 创建实例。通常用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。
DatagramSocket(int port):创建实例,并固定监听Port端口的报文。
DatagramSocket(int port, InetAddress localAddr):这是个非常有用的构建器,当一台机器拥有多于一个IP地址的时候,由它创建的实例仅仅接收来自LocalAddr的报文
receive(DatagramPacket d):接收数据报文到d中。receive方法产生一个“阻塞”。
send(DatagramPacket d):发送报文d到目的地。
setSoTimeout(int timeout):设置超时时间,单位为毫秒。
close():关闭DatagramSocket。在应用程序退出的时候,通常会主动释放资源,关闭Socket,但是由于异常地退出可能造成资源无法回收。所以,应该在程序完成时,主动使用此方法关闭Socket,或在捕获到异常抛出后关闭Sock
注意:1.在创建DatagramSocket类实例时,如果端口已经被使用,会产生一个SocketException的异常抛出,并导致程序非法终止,这个异常应该注意捕获。
DatagramPacket类:用于处理报文,将byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成byte数组。
DatagramPacket(byte[] buf, int length, InetAddress addr, int port):从buf数组中,取出length长的数据创建数据包对象,目标是addr地址,port端口。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int
port):从buf数组中,取出offset开始的、length长的数据创建数据包对象,目标是addr地址,port端口。
DatagramPacket(byte[] buf, int offset, int length):将数据包中从offset开始、length长的数据装进buf数组。
DatagramPacket(byte[] buf, int length):将数据包中length长的数据装进buf数组。
getData():它从实例中取得报文的byte数组编码
2.实现方法:
想要实现UDP程序,建议首先从客户端编写,在客户端指定需要接收的端口和取得数据。
客户端(接收端)实现步骤
1. 建立udp的socket服务。要监听一个端口。 DatagramSocket ds = new
DatagramSocket(9001);
2. 定义一个缓冲区,将该缓冲区封装到packet包中。 byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
3. 通过socket的receive方法将数据存入数据包中。 ds.receive(dp);
4. 通过数据包dp的方法getData()、getAddress()、getPort()等方法获取包中的指定信息。
5. 关闭socket。 ds.close();
请看以下的代码以下是UDP的客户端程序:
import java.net.DatagramPacket ; import java.net.DatagramSocket ; public class UDPClient{ public static void main(String args[]) throws Exception{ // 所有异常抛出 DatagramSocket ds = null ; // 定义接收数据报的对象 byte[] buf = new byte[1024] ; // 开辟空间,以接收数据 DatagramPacket dp = null ; // 声明DatagramPacket对象 ds = new DatagramSocket(9000) ; // 客户端在9000端口上等待服务器发送信息 dp = new DatagramPacket(buf,1024) ; // 所有的信息使用buf保存 ds.receive(dp) ; // 接收数据 String str = new String(dp.getData(),0,dp.getLength()) + "from " + dp.getAddress().getHostAddress() + ":" + dp.getPort() ; System.out.println(str) ; // 输出内容 } };
以上程序运行后,客户端程序已经打开了监听的端口,等待服务器端向客户端发送信息。
下面开始介绍服务器端(发送端)实现步骤
1. 建立udpsocket服务端点。该端点建立,系统会随机分配一个端口。如果不想随机配置,可以手动指定。 DatagramSocket ds = new
DatagramSocket(3000);
2. 将数据进行packet包的封装,必须要指定目的地地址和端口。 byte[] buf = "hi 红军".getBytes(); DatagramPacket
dp =new
DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.254"),9000);
3. 通过socket服务的send方法将该包发出。 ds.send(dp);
4. 将socket服务关闭。主要是关闭资源。 ds.close();
下面开始编写UDP的发送 服务器程序—Udpserve
import java.net.DatagramPacket ; import java.net.DatagramSocket ; import java.net.InetAddress ; public class UDPServer{ public static void main(String args[]) throws Exception{ // 所有异常抛出 DatagramSocket ds = null ; // 定义发送数据报的对象 DatagramPacket dp = null ; // 声明DatagramPacket对象 ds = new DatagramSocket(3000) ; // 服务端在3000端口上等待服务器发送信息\ String str = "hello World!!!" ; dp = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("localhost"),9000) ; // 所有的信息使用buf保存 System.out.println("发送信息。") ; ds.send(dp); // 发送信息出去 ds.close() ; } };
服务器端程序运行后,客户端就可以接收服务器端发送来的数据了。
以上是一个简单的收发过程。当然为了保证每个设备都可以收发,可以同时运行服务器和客户端程序。
从以上程序我们可以看出使用DatagramSocket进行网络通信时,服务器端无须也无法保存每个客户端的状态,客户端把数据报发送到服务器端后,完全有可能立即退出。但不管客户端是否退出,服务器端都无法知道客户端的状态。
当使用UDP协议时,如果想让一个客户端发送的聊天信息被转发到其他所有的客户端则比较困难,可以考虑在服务器端使用Set集合来保存所有的客户端 信息,每当接收到一个客户端的数据报之后,程序检查该数据报的源SocketAddress是否在Set集合中,如果不在就将该 SocketAddress添加到该Set集合中。这样又涉及一个问题:可能有些客户端发送一个数据报之后永久性地退出了程序,但服务器端还将该客户端的 SocketAddress保存在Set集合中……总之,这种方式需要处理的问题比较多,编程比较烦琐。
基于UDP数据传输特性,它的不可靠性也给我们在开发过程的带来了麻烦,针对此类问题,提出以下解决方案:
服务器和客户端 可以建立一套自己的校验方案(方案形式很多例如:XML,校验和等检验方式),如果数据包丢失造成数据不完整,采用补发的形式来完成,当然这个方案类似于TCP的握手连接。
在开发过程中还有很多细节,文章摘取网络上一些信息。文笔不好,还望见谅,此为普及类文章,希望能带给大家帮助。如若有发现什么问题,请及时指出方便我修改。谢谢。
张敬宇
2015.3.15于南京编辑