UDP协议基础:
UDP(User Datagram Protocol)协议,是用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。它是一种面向非连接的协议,面向非连接指的是双方在正式通信前不必于对方先建立连接关系,不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP协议无法控制。因此UDP协议是一种不可靠的协议,适用于一次只传送少量数据,对可靠性要求不高的应用环境。
UDP协议直接位于IP协议之上,和TCP协议一样输入传输层协议。
UDP协议与TCP协议的简单对比:
TCP协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大
UDP协议:不可靠,差错控制开销小,传输大小限制在64KB以下,不需要建立连接。
使用DatagramSocket 发送,接收数据:
JAVA使用DatagramSocket代表UDP协议的Socket,它的唯一作用是接收和发送数据报。
构造方法如下:
DatagramSocket(): 创建一个DatagramSocket实例,并将对象绑定到本地计算机默认的IP地址,本机所有可用端口中随机选择某个端口
DatagramSocket(int port) :创建一个指定端口,本机默认IP地址的DatagramSocket实例
DatagramSocket(int port,InetAddress laddr):创建一个指定ip地址,指定端口的DatagramSocket实例
DatagramSocket主要方法:
receive(DatagramPacket p):从该DatagramSocket对象接收数据报
send(DatagramPacket p):以该DatagramSocket对象向外发送数据报
DatagramPacket:
构造方法如下:
DatagramPakcet(byte[] buf,int length):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据放入buf中,最多放入length个字节。
DatagramPacket(byte[] buf,int length,InetAddress addr,int port): 以一个包含数据的数组来创建DatagramPacket发送对象,创建该DatagramPacket对象时还指定了IP指定和端口---这就决定了该数据包的目的地。
DatagramPacket(byte[] buf,int offset,int length): 以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中从offset开始,最多放length个字节。
DatagramPacket(byte[] buf,int offset,int length,InetAddress addr,int port):创建DatagramPacket发送对象,指定发送buf数组中从offset开始,总共length字节的数组。
当接收端接收到了一个DatagramPacket对象后,相向该数据报的发送者”反馈“一些信息,但由于UDP协议是面向非连接的,所以接收者并不知道每个数据报由谁发送过来的,但程序可以调用DatagramPacket的如下方法来获取发送者的IP地址和端口
1. InetAddress getAddress(): 当程序准备发送此数据报时,该方法返回此数据报对应目标机器的IP地址;当程序刚收到一份数据报时,该该方法返回该数据报发送者的IP地址。
2. int getPort(): 与getAddress类似,不过getAddress返回的是IP地址,而getPort返回的是端口.
3. SocketAddress getSocketAddress(): 与getAddress类型,不过getSocketAddress()返回的是SocketAddress对象。该对象实际上就是一个IP地址和一个端口号。
尽管UDP协议没有明确的区分服务端和客户端,事实上,我们通常将固定ip以及固定端口的DatagramSocket对象所在的程序被成为服务端,因为该DatagramSocket可以主动接收客户端数据。
以下代码使用DatagramSocket实现UDP协议通信
通信实体1 ,用于接收其他通信实体的数据报,以及对发送数据报的目的地址反馈
class UDPServer{ public static final int PORT = 30000; //定义每个数据报的大小最大为4KB public static final int DATA_LEN = 4096; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接收数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length); //定义一个用于发送的DatagramPacket private DatagramPacket outPacket; //定义一个字符串数组,服务端发送该数组的元素 String[] books = new String[]{"book_1","book_2","book_3","book_4"}; public void init(){ try { //创建DatagramSocket对象 DatagramSocket socket = new DatagramSocket(PORT); //采用循环接收数据 for(int i=0;i<1000;i++){ //读取Socket中的数据,读到的数据放入inPacket封装的数组里 socket.receive(inPacket); //判断inPacket.getData()和inBuff是否是同一数组 System.out.println(inBuff == inPacket.getData()); //将接收到的内容转换成字符串后输出 System.out.println(new String(inBuff,0,inPacket.getLength())); //从字符串数组中取出一个元素作为发送数据 byte[] sendData = books[i%4].getBytes(); /** * 以指定的字节数组作为发送数据, * 以刚接收到的DatagramPacket的源SocketAddress作为目标SocketAddress创建DatagramPacket * */ outPacket = new DatagramPacket(sendData,sendData.length,inPacket.getSocketAddress()); socket.send(outPacket); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public class Net{ public static void main(String[] args){ //UDP协议 System.out.println("启动通信实体(服务端)"); new UDPServer().init(); } }
通信实体2,用于对指定ip以及指定端口号的通信实体进行发送数据报,并接收来自指定IP地址和端口号的反馈数据报
class UDPClient{ //定义发送数组的目的地(ip地址与端口) public static final int DEST_ROPT = 30000; public static final String DEST_IP = "127.0.0.1"; //定义每一个发送数据的大小,为4KB private static final int DATA_LEN = 4096; byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接收数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length); //定义一个用于发送的DatagramPacket private DatagramPacket outPacket = null; public void init(){ try { //创建一个DatagramSocket,使用随机端口 DatagramSocket datagramSocket = new DatagramSocket(); //初始化发送用的DatagramPacket,它包含一个长度为0的字节数组,指定端口与IP地址 outPacket =new DatagramPacket(new byte[0],0,InetAddress.getByName(DEST_IP),DEST_ROPT); //创建键盘输入流 Scanner scan = new Scanner(System.in); //不断地读取键盘输入 while(scan.hasNextLine()){ //将键盘输入的一行字符串转成字节数组 byte[] sendByte =scan.nextLine().getBytes(); //设置发送用的DatagramPacket对象 outPacket.setData(sendByte); //发送数据报 datagramSocket.send(outPacket); //读取Socket中的数据,读的数据放入在inPacket所封装的字节数组中 datagramSocket.receive(inPacket); //打印数据 System.out.println(new String(inBuff,0,inPacket.getLength())); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public class MyClient { public static void main(String[] args){ System.out.println("启动通信实体(客户端)"); new UDPClient().init(); } }
运行通信实体1,以及运行多个通信实体2,并在多个通信实体2控制台中输入字符串,通信实体1控制台会打印接收来自各个不同通信实体2的数据,同时各个通信实体2也会收到来自通信实体的反馈。
使用MulticastSocket实现多点发送(多点广播)
在上述程序中,我们可以看到通信实体1对应着多个通信实体2,并且单个通信实体2发送数据给通信实体1,是不会让其他通信实体2所得知的。倘若我们想让所有的通信实体2知道某个通信实体发送给通信实体1的数据,我们可以通过JAVA的MulticastSocket类来实现。
MulticastSocket:该类是DatagramSocket的子类,作用是可以将数据报以广播方式发送到多个通信实体。若要使用多点发送(多点广播),则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报,不仅如此,MulticastSocket既可以接收广播也可以发送广播。
IP多点发送(多点广播)实现了将单一信息发送到多个通信实体(接收者),其思想是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看做一个组,通信实体需要在发送或者接收广播信息之前,加入到该组即可。同时IP协议为多点广播提供了这批特殊的IP地址,这些IP地址的范围是224.0.0.0 至 239.255.255.255,如下示意图:
MulticastSocket的构造方法:
1. MulticastSocket() :使用本机默认IP地址,随机端口来创建MulticastSocket对象
2. MulticastSocket(int port):使用本机默认IP地址以及指定端口来创建MulticastSocket对象
3. MulticastSocket(SocketAddress socketAddress):使用指定IP地址以及指定端接口来创建MulticastSocket对象
Ps:若创建仅用于发送数据报的MulticastSocket对象,则使用默认IP地址,随机端口即可;反之,若创建用于接收数据报的MulticastSocket对象,则必须指定端口,否则发送方无法确定发送数据报的目标端口
MulticastSocket的方法:
1.joinGroup(InetAddress multicastAddr):将该MulticastSocket加入指定的多点广播地址
2.leaveGroup(InetAddress multicastAddr):让MulticastSocket离开指定的多点广播地址
3.setInterface():强制MulticastSocket使用指定的网络端口
4.getInterface():查询MulticastSocket监听的网络端口
5. seTimeToLive(int ttl):设置数据报最多可以跨国多少个网络,以下是ttl值与对应网络
ttl = 0 , 数据报停留在本地
ttl = 1 , 数据报停留在本地局域网,默认
ttl = 32 , 数据报停留在本站点的网络上
ttl = 64 , 数据报停留在本地区的网络
ttl = 128 ,数据报停留在本大洲的网络
ttl = 255 ,数据报可以发送所有的地方
Ps:创建完MulticastSocket对象后,还需要将该MulticastSocket加入只当的多点广播地址中,方法一为加入指定组,方法二为离开指定组。同时MulticastSocket用于发送与接收的方法与DatagramSocket的发送与接受方法一致。
通过MulticastSocket实现多人聊天室。实现该聊天室只需用到一个MulticastSocket,以及两个线程。MulticastSocket实现接收与发送数据报,一个线程用于接收广播数据报,另外一个线程用于向MulticastSocket发送数据报,代码如下:
class MulticastSocketThread implements Runnable{ //使用常量作为本程序的多点广播IP地址 private static final String BROADCAST_IP = "230.0.0.1"; //使用常量作为本程序的多点广播目的地端口 private static final int BROADCAST_PORT = 30000; //定义每个数据报的大小最大为4KB public static final int DATA_LEN = 4096; //定义本程序的MulticastSocket实例 private MulticastSocket multicastSocket = null; private InetAddress broadcastAddress = null; private Scanner scan = null; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //定义以指定字节数组来创建接收数据的DatagramPacket对象 private DatagramPacket inPack = new DatagramPacket(inBuff, inBuff.length); //定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; public void init(){ try{ //创建键盘输入流 Scanner scan = new Scanner(System.in); //创建用于发送,接收数据的MulticastSocket对象 multicastSocket = new MulticastSocket(BROADCAST_PORT); broadcastAddress = InetAddress.getByName(BROADCAST_IP); //将multicastSocket加入多点广播地址 multicastSocket.joinGroup(broadcastAddress); //设置本MulticastSocket发送的数据报会被会送到本身,该方法设置true时,该程序接收不到任何信息,
//设置false时,与不调用该方法的运行结果是一致的,因此不太清楚该方法的用法 multicastSocket.setLoopbackMode(false); //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0],0,broadcastAddress,BROADCAST_PORT); //启动线程 new Thread(this).start(); //不断读取键盘输入 while(scan.hasNextLine()){ //将从键盘输入的字符串转化成字节数据 String line = scan.nextLine(); byte[] buff = line.getBytes(); //设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); //发送数据报 multicastSocket.send(outPacket); } }catch(Exception e){System.out.println("是否用异常");}finally { multicastSocket.close(); } } @Override public void run() { // TODO Auto-generated method stub try{ while(true){ //读取multicastSocket中的数据,读到的数据放在inPacket所封装的字节数组里 multicastSocket.receive(inPack); //打印输出从socket中读取的内容 System.out.println("聊天信息:"+new String(inPack.getData(),0,inPack.getLength())); } }catch(IOException e){ e.printStackTrace(); try{ if(multicastSocket != null){ //让该muticastSocket离开多点IP广播地址 System.out.println("离开"); multicastSocket.leaveGroup(broadcastAddress); multicastSocket.close(); //关闭该muticastSocket对象 } }catch(IOException e1){e1.printStackTrace();} } } } public class Net{ public static void main(String[] args){ System.out.println("MulticastSocket实现多点广播"); new MulticastSocketThread().init(); } }
多运行以上程序,运行结果如下:
1.打开3个控制台,3个控制台的初始状态如下:
2.对第一个控制台,输入任意字符后回车,运行状态如下:
控制台1: 控制台2
控制台3
3. 对第三个控制台,输入任意字符后回车,运行状态如下:
控制台1: 控制台2
控制台3
总结: 无论是DatagramSocket还是MuticastSocket都可以实现UDP协议的通信,其DatagramSocket适用于私信,而MuticastSocket适用于群体广告。
全部代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// Net类: /** * UDP协议 * */ class UDPServer{ public static final int PORT = 30000; //定义每个数据报的大小最大为4KB public static final int DATA_LEN = 4096; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接收数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length); //定义一个用于发送的DatagramPacket private DatagramPacket outPacket; //定义一个字符串数组,服务端发送该数组的元素 String[] books = new String[]{"book_1","book_2","book_3","book_4"}; public void init(){ try { //创建DatagramSocket对象 DatagramSocket socket = new DatagramSocket(PORT); //采用循环接收数据 for(int i=0;i<1000;i++){ //读取Socket中的数据,读到的数据放入inPacket封装的数组里 socket.receive(inPacket); //判断inPacket.getData()和inBuff是否是同一数组 System.out.println(inBuff == inPacket.getData()); //将接收到的内容转换成字符串后输出 System.out.println(new String(inBuff,0,inPacket.getLength())); //从字符串数组中取出一个元素作为发送数据 byte[] sendData = books[i%4].getBytes(); /** * 以指定的字节数组作为发送数据, * 以刚接收到的DatagramPacket的源SocketAddress作为目标SocketAddress创建DatagramPacket * */ outPacket = new DatagramPacket(sendData,sendData.length,inPacket.getSocketAddress()); socket.send(outPacket); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class MulticastSocketThread implements Runnable{ //使用常量作为本程序的多点广播IP地址 private static final String BROADCAST_IP = "230.0.0.1"; //使用常量作为本程序的多点广播目的地端口 private static final int BROADCAST_PORT = 30000; //定义每个数据报的大小最大为4KB public static final int DATA_LEN = 4096; //定义本程序的MulticastSocket实例 private MulticastSocket multicastSocket = null; private InetAddress broadcastAddress = null; private Scanner scan = null; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //定义以指定字节数组来创建接收数据的DatagramPacket对象 private DatagramPacket inPack = new DatagramPacket(inBuff, inBuff.length); //定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; public void init(){ try{ //创建键盘输入流 Scanner scan = new Scanner(System.in); //创建用于发送,接收数据的MulticastSocket对象 multicastSocket = new MulticastSocket(BROADCAST_PORT); broadcastAddress = InetAddress.getByName(BROADCAST_IP); //将multicastSocket加入多点广播地址 multicastSocket.joinGroup(broadcastAddress); //设置本MulticastSocket发送的数据报会被会送到本身 multicastSocket.setLoopbackMode(false); //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0],0,broadcastAddress,BROADCAST_PORT); //启动线程 new Thread(this).start(); //不断读取键盘输入 while(scan.hasNextLine()){ //将从键盘输入的字符串转化成字节数据 String line = scan.nextLine(); byte[] buff = line.getBytes(); //设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); //发送数据报 multicastSocket.send(outPacket); } }catch(Exception e){System.out.println("是否用异常");}finally { multicastSocket.close(); } } @Override public void run() { // TODO Auto-generated method stub try{ while(true){ //读取multicastSocket中的数据,读到的数据放在inPacket所封装的字节数组里 multicastSocket.receive(inPack); //打印输出从socket中读取的内容 System.out.println("聊天信息:"+new String(inPack.getData(),0,inPack.getLength())); } }catch(IOException e){ e.printStackTrace(); try{ if(multicastSocket != null){ //让该muticastSocket离开多点IP广播地址 System.out.println("离开"); multicastSocket.leaveGroup(broadcastAddress); multicastSocket.close(); //关闭该muticastSocket对象 } }catch(IOException e1){e1.printStackTrace();} } } } public class Net{ public static void main(String[] args){ //UDP协议 // System.out.println("启动通信实体(服务端)"); // new UDPServer().init(); System.out.println("MulticastSocket实现多点广播"); new MulticastSocketThread().init(); } } //MyClient类 /** * UDP协议网络 * */ class UDPClient{ //定义发送数组的目的地(ip地址与端口) public static final int DEST_ROPT = 30000; public static final String DEST_IP = "127.0.0.1"; //定义每一个发送数据的大小,为4KB private static final int DATA_LEN = 4096; byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接收数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length); //定义一个用于发送的DatagramPacket private DatagramPacket outPacket = null; public void init(){ try { //创建一个DatagramSocket,使用随机端口 DatagramSocket datagramSocket = new DatagramSocket(); //初始化发送用的DatagramPacket,它包含一个长度为0的字节数组,指定端口与IP地址 outPacket =new DatagramPacket(new byte[0],0,InetAddress.getByName(DEST_IP),DEST_ROPT); //创建键盘输入流 Scanner scan = new Scanner(System.in); //不断地读取键盘输入 while(scan.hasNextLine()){ //将键盘输入的一行字符串转成字节数组 byte[] sendByte =scan.nextLine().getBytes(); //设置发送用的DatagramPacket对象 outPacket.setData(sendByte); //发送数据报 datagramSocket.send(outPacket); //读取Socket中的数据,读的数据放入在inPacket所封装的字节数组中 datagramSocket.receive(inPacket); //打印数据 System.out.println(new String(inBuff,0,inPacket.getLength())); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public class MyClient{ public static void main(String[] args){ //UDP // System.out.println("启动通信实体(客户端)"); // new UDPClient().init(); } }