UDP数据报
套接字是基于TCP协议的网络通信,即客户端程序和服务器端程序是有连接的,双方的信息是通过程序中的输入、输出流来交互的,使得接收方收到信息的顺序和发送方发送信息的顺序完全相同,就像生活中双方使用电话进行信息交互一样。
本节介绍Java中基于UDP(用户数据报协议)协议的网络信息传输方式。基于UDP的通信和基于TCP的通信不同,基于UDP的信息传递更快,但不提供可靠性保证。也就是说,数据在传输时,用户无法知道数据能否正确到达目的地主机,也不能确定数据到达目的地的顺序是否和发送的顺序相同。可以把UDP通信比作生活中的邮递信件,我们不能肯定所发的信件就一定能够到达目的地,也不能肯定到达的顺序是发出时的顺序,可能因为某种原因导致后发出的先到达。既然UDP是一种不可靠的协议,为什么还要使用它呢?如果要求数据必须绝对准确地到达目的地,显然不能选择UDP协议来通信。但有时候人们需要较快速地传输信息,并能容忍小的错误,就可以考虑使用UDP协议。
基于UDP通信的基本模式是:
将数据打包(好比将信件装入信封一样),称为数据包,然后将数据包发往目的地。
接收发来的数据包(好比接收信封一样),然后查看数据包中的内容。
发送数据包
用DatagramPacket类将数据打包,即用DatagramPacket类创建一个对象,称为数据包。用DatagramPacket的以下两个构造方法创建待发送的数据包。
DatagramPacket (byte data[], int length, InetAddtress address, int port)
使用该构造方法创建的数据包对象具有下列两个性质:
含有data数组指定的数据。
该数据包将发送到地址是address,端口号是port的主机上。
称address是这个数据包的目标地址,port是它的目标端口。
DatagramPack(byte data[], int offset, int length, InetAddtress address, int port)
使用该构造方法创建的数据包对象含有数组data中从offset开始后的length个字节,该数据包将发送到地址是address,端口号是port的主机上。例如:
byte data[] ="生日快乐".getBytes();InetAddtress address = InetAddtress.getName("www.china.com.cn");DatagramPacket data_pack = new DatagramPacket(data,data.length, address,2009);
注:对于用上述方法创建的用于发送的数据包,data_pack如果调用方法public int getPort()可以获取该数据包目标端口;调用方法public InetAddress getAddress()可获取这个数据包的目标地址;调用方法public byet[] getData()可以返回数据包中的字节数组。
用DatagramSocket类的不带参数的构造方法DatagramSocket()创建一个对象,该对象负责发送数据包。例如:
DatagramSocket mail_out= new DatagramSocket();mail_out.send(data_pack);
接收数据包
首先用DatagramSocket的另一个构造方法DatagramSocket(int port)创建一个对象,其中
的参数必须和待接收的数据包的端口号相同。例如,如果发送方发送的数据包的端口是5666,那么如下创建DatagramSocket对象:
DatagramSocket mail_in = new DatagramSocket(5666);
然后对象mail_in 使用方法receive (DatagramPacket pack)接收数据包。该方法有一个数据包参数pack,方法receive把收到的数据包传递给该参数。因此必须准备一个数据包以便收取数据包。这时需使用 DatagramPack类的另外一个构造方法DatagramPack (byte data[], int length)创建一个数据包,用于接收数据包,例如:
byte data[] = new byte[100];int length = 90;DatagramPacket pack = new DatagramPacket(data,length);mail_in.receive(pack);
该数据包pack将接收长度是length个字节的数据放入data。
注:
①receive方法可能会阻塞,直到收到数据包。
②如果pack调用方法getPort()可以获取所收数据包是从远程主机上的哪个端口发出的,即可以获取包的始发端口号;调用方法getLength()可以获取收到的数据的字节长度;调用方法InetAddress getAddress()可获取这个数据包来自哪个主机,即可以获取包的始发地址。我们称主机发出数据包使用的端口号为该包的始发端口号,发送数据包的主机地址称为数据包的始发地址。
③数据包数据的长度不要超过8192KB。
在下面的例子5中,张三和李四使用用户数据报(可用本地机器模拟)互相发送和接收数据包,程序运行时“张三”所在主机在命令行输入数据发送给“李四”所在主机,将接收到的数据显示在命令行的右侧;同样,“李四”所在主机在命令行输入数据发送给“张三”所在主机,将接收到的数据显示在命令行的右侧。程序代码如下所示:
①”张三”主机
import java.net.*;import java.util.Scanner;public class ZhangSan { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); Thread readData; ReceiveLetterForZhang receiver = new ReceiveLetterForZhang(); try { readData = new Thread(receiver); readData.start(); //负责接收信息的线程 byte[] buffer = new byte[1]; InetAddress address = InetAddress.getByName("127.0.0.1"); DatagramPacket dataPack = new DatagramPacket(buffer,buffer.length, address, 666); DatagramSocket postman=new DatagramSocket(); System.out.print("输入发送给李四的信息:"); while(scanner.hasNext()) { String mess = scanner.nextLine(); buffer = mess.getBytes(); if (mess.length()==0) System.exit(0); buffer = mess.getBytes(); dataPack.setData(buffer); postman.send(dataPack); System.out.print("继续输入发送给李四的信息:"); } } catch(Exception e) { System.out.println(e); } }}
import java.net.*;public class ReceiveLetterForZhang implements Runnable { public void run() { DatagramPacket pack = null; DatagramSocket postman = null; byte data[] = new byte[8192]; try { pack = new DatagramPacket(data,data.length); postman = new DatagramSocket(888); } catch (Exception e) {} while (true) { if (postman==null) break; else try { postman.receive(pack); String message = new String(pack.getData(),0,pack.getLength()); System.out.printf("%25s\n","收到:"+message); } catch (Exception e) {} } }}
②”李四”主机
import java.net.*;import java.util.Scanner;public class LiSi { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); Thread readData; ReceiveLetterForLi receiver = new ReceiveLetterForLi(); try { readData = new Thread(receiver); readData.start(); //负责接收信息的线程 byte[] buffer = new byte[1]; InetAddress address = InetAddress.getByName("127.0.0.1"); DatagramPacket dataPack = new DatagramPacket(buffer,buffer.length, address, 888); DatagramSocket postman=new DatagramSocket(); System.out.print("输入发送给张三的信息:"); while(scanner.hasNext()) { String mess = scanner.nextLine(); buffer = mess.getBytes(); if (mess.length()==0) System.exit(0); buffer = mess.getBytes(); dataPack.setData(buffer); postman.send(dataPack); System.out.print("继续输入发送给张三的信息:"); } } catch(Exception e) { System.out.println(e); } }}
import java.net.DatagramPacket;import java.net.DatagramSocket;public class ReceiveLetterForLi implements Runnable { public void run() { DatagramPacket pack = null; DatagramSocket postman = null; byte data[] = new byte[8192]; try { pack = new DatagramPacket(data,data.length); postman = new DatagramSocket(666); } catch (Exception e) {} while (true) { if (postman==null) break; else try { postman.receive(pack); String message = new String(pack.getData(),0,pack.getLength()); System.out.printf("%25s\n","收到:"+message); } catch (Exception e) {} } }}