核心提示:UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocketUDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报。17.4.1UDP协议基础UDP协议是英文UserDatagramProtocol的缩写,即用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天,UDP仍然不失为一项非常实用和可行的网络传输层协议。尤其是在需要很强的实时交互性的场合,如网络游戏、视频会议等,UDP协议更是显示出极强的威力。UDP协议是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP协议无法控制,因此说UDP协议是一种不可靠的协议。与前面介绍的TCP协议一样,UDP协议直接位于IP协议之上。实际上,IP协议属于OSI参考模型的网络层协议,而UDP和TCP都属于传输层协议。UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。因为UDP协议是面向非连接的协议,没有建立连接的过程,因此它的通信效率高;但也正因为如此,它的可靠性不如TCP协议高。UDP协议的主要作用是完成网络数据流量和数据报之间的转换:在信息的发送端,UDP协议将网络数据流量封装数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据内容。实际上,我们可以认为基于UDP协议的Socket类似于一个码头,该码头的作用就是负责发送、接收集装箱,一个数据报类似于一个集装箱。因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器端的概念。Java中的DatagramSocket的作用类似于码头,而DatagramPacket的作用类似于集装箱。UDP和TCP简单的对比如下:ØTCP协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。ØUDP协议:不可靠,差错控制开销较小,传输大小限制在64K以下,不需要建立连接。17.4.2使用DatagramSocket发送、接收数据DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报,Java使用DatagramPacket来代表数据报,DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的。先看一下DatagramSocket的构造器:ØDatagramSocket():创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、本机所有可用端口中随机选择的某个端口。ØDatagramSocket(intprot):创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、指定端口。ØDatagramSocket(intport,InetAddressladdr):创建一个DatagramSocket实例,并将该对象绑定到指定IP地址、指定端口。通过上面三个构造器中任意一个构造器即可创建一个DatagramSocket实例,通常在创建服务器时,我们创建指定端口的DatagramSocket实例——这样保证其他客户端可以将数据发送到该服务器。一旦得到了DatagramSocket实例之后,就可以通过如下两个方法来接收和发送数据:Øreceive(DatagramPacketp):从该DatagramSocket中接收数据报。Øsend(DatagramPacketp):以该DatagramSocket对象向外发送数据报。从上面两个方法可以看出,使用DatagramSocket发送数据报时,DatagramSocket并不知道将该数据报发送到哪里,而是由DatagramPacket自身决定数据报的目的。就像码头并不知道每个集装箱的目的地,码头只是将这些集装箱发送出去,而集装箱本身包含了该集装箱的目的地。当Client/Server程序使用UDP协议时,实际上并没有明显的服务器和客户端,因为两方都需要先建立一个DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket对象作为传输数据的载体。通常固定IP、固定端口的DatagramSocket对象所在的程序被称为服务器,因为该DatagramSocket可以主动接收客户端数据。下面看一下DatagramPacket的构造器:ØDatagramPacket(bytebuf[],intlength):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。ØDatagramPacket(bytebuf[],intlength,InetAddressaddr,intport):以一个包含数据的数组来创建DatagramPacket对象,创建该DatagramPacket时还指定了IP地址和端口——这就决定了该数据报的目的。ØDatagramPacket(byte[]buf,intoffset,intlength):以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中时从offset开始,最多放length个字节。ØDatagramPacket(byte[]buf,intoffset,intlength,InetAddressaddress,intport):创建一个用于发送的DatagramPacket对象,也多指定了一个offset参数。在接收数据前,应该采用上面的第一个或第三个构造器生成一个DatagramPacket对象,给出接收数据的字节数组及其长度。然后调用DatagramSocket的方法receive()等待数据报的到来,receive()将一直等待(也就是说会阻塞调用该方法的线程),直到收到一个数据报为止。如下代码所示://创建接受数据的DatagramPacket对象DatagramPacketpacket=newDatagramPacket(buf,256);//接收数据socket.receive(packet);发送数据之前,调用第二个或第四个构造器创建DatagramPacket对象,此时的字节数组里存放了想发送的数据。除此之外,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的方法send()实现的,send()根据数据报的目的地址来寻径以传递数据报。如下代码所示://创建一个发送数据的DatagramPacket对象DatagramPacketpacket=newDatagramPacket(buf,length,address,port);//发送数据报socket.send(packet);当我们使用DatagramPacket来接收数据时,会感觉DatagramPacket设计得过于烦琐。对于开发者而言,只关心该DatagramPacket能放多少数据,而DatagramPacket是否采用字节数组来存储数据完全不想关心。但Java要求创建接收数据用的DatagramPacket时,必须传入一个空的字节数组,该数组的长度决定了该DatagramPacket能放多少数据,这实际上暴露了DatagramPacket的实现细节。接着DatagramPacket又提供了一个getData()方法,该方法又可以返回DatagramPacket对象里封装的字节数组,该方法更显得有些多余:如果程序需要获取DatagramPacket里封装的字节数组,直接访问传给DatagramPacket构造器的字节数组实参即可,无须调用该方法。当服务器(也可以客户端)接收到一个DatagramPacket对象后,如果想向该数据报的发送者“反馈”一些信息,但由于UDP是面向非连接的,所以接收者并不知道每个数据报由谁发送过来,但程序可以调用DatagramPacket的如下三个方法来获取发送者的IP和端口:ØInetAddressgetAddress():返回某台机器的IP地址,当程序准备发送次数据报时,该方法返回此数据报的目标机器的IP地址;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址。ØintgetPort():返回某台机器的端口,当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。ØSocketAddressgetSocketAddress():返回完整SocketAddress,通常由IP地址和端口组成。当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;当程序刚刚接收到一个数据报时,该方法返回该数据报是源SocketAddress。上面getSocketAddress方法的返回值是一个SocketAddress对象,该对象实际上就是一个IP地址和一个端口号,也就是说SocketAddress对象封装了一个InetAddress对象和一个代表端口的整数,所以使用SocketAddress对象可以同时代表IP地址和端口。下面程序使用DatagramSocket实现Server/Client结构的网络通信程序,本程序的服务器端使用循环1000次来读取DatagramSocket中的数据报,每当读到内容之后便向该数据报的发送者送回一条信息。服务器端代码如下:程序清单:codes/17/17-4/UdpServer.javapublicclassUdpServer{publicstaticfinalintPORT=30000;//定义每个数据报的最大大小为4KprivatestaticfinalintDATA_LEN=4096;//定义该服务器使用的DatagramSocketprivateDatagramSocketsocket=null;//定义接收网络数据的字节数组byte[]inBuff=newbyte[DATA_LEN];//以指定字节数组创建准备接受数据的DatagramPacket对象privateDatagramPacketinPacket=newDatagramPacket(inBuff,inBuff.length);//定义一个用于发送的DatagramPacket对象privateDatagramPacketoutPacket;//定义一个字符串数组,服务器发送该数组的的元素String[]books=newString[]{轻量级J2EE企业应用实战,基于J2EE的Ajax宝典,Struts2权威指南,ROR敏捷开发最佳实践};publicvoidinit()throwsIOException{try{//创建DatagramSocket对象socket=newDatagramSocket(PORT);//采用循环接受数据for(inti=0;i1000;i++){//读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。socket.receive(inPacket);//判断inPacket.getData()和inBuff是否是同一个数组System.out.println(inBuff==inPacket.getData());//将接收到的内容转成字符串后输出System.ou