UDP:
UDP协议没有socket之间的虚拟链路,也就是说没有“握手”阶段,只是发送接收。
可以想象成直播,直播时如果中间网络不好,不会事后重新播放之前的,而是直接跳过。
也就是说一方只负责发送,一方只负责接收,发送方不关心对方会不会接到数据。
TCP协议:可靠,传输大小无限制,但是需要建立连接后传输数据。
UDP协议:不可靠,传输大小限制在64K以下,但是不需要建立连接。
相关类:
DatagramSocket:接收和发送的数据报。
receive(DatagramPacket P)://从DatagramSocket接收数据。 send(DatagramPacket P)://从DatagramSocket发送数据。
DatagramSocket并不知道数据发送到哪里去,而是由DatagramPacket来指定。
DatagramPacket:用来承载数据的,代表数据报。
DatagramPacket(byte buf[],int length, InetAnddress addr,int port);//一个装有传送数据的数组,加一个接收方的地址以及端口。 DatagramPacket(byte buf[],int length);//只有装有数据的数组,意味着接收数据报。
/*那么反馈数据应该怎么办,DatagramSocket也不知道从哪发过来的数据。 DatagramPacket提供了3个方法:*/
InetAddress getAddress();//当接收到一个数据的时候返回发送主机IP地址。发送时反之。 Int getPort();//当接收到一个数据的时候返回发送主机端口。发送时反之。 SocketAddress getSocketAddress();//当接收到一个数据的时候返回打包成发送主机IP&PORT的SocketAddress。发送时反之。
实例:
服务端代码:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class UDPServer { public static final int PORT = 30000; // 定义每个数据报的最大大小为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; // 定义一个字符串数组,服务器端发送该数组的元素 String[] books = new String[] { "疯狂Java讲义", "轻量级Java EE企业应用实战", "疯狂Android讲义", "疯狂Ajax讲义" }; public void init() throws IOException { 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); } } } public static void main(String[] args) throws IOException { new UDPServer().init(); } }
客户端代码:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner; public class UDPClient { // 定义发送数据报的目的地 public static final int DEST_PORT = 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()throws IOException { try( // 创建一个客户端DatagramSocket,使用随机端口 DatagramSocket socket = new DatagramSocket()) { // 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0] , 0 , InetAddress.getByName(DEST_IP) , DEST_PORT); // 创建键盘输入流 Scanner scan = new Scanner(System.in); // 不断地读取键盘输入 while(scan.hasNextLine()) { // 将键盘输入的一行字符串转换成字节数组 byte[] buff = scan.nextLine().getBytes(); // 设置发送用的DatagramPacket中的字节数据 outPacket.setData(buff); // 发送数据报 socket.send(outPacket); // 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组中 socket.receive(inPacket); System.out.println(new String(inBuff , 0 , inPacket.getLength())); } } } public static void main(String[] args) throws IOException { new UDPClient().init(); } }
运行:
- 打开服务端
- 打开客户端
- 在客户端窗口键盘输入后enter
MulticastSocket:
当使用UDP协议时,如果想让一个客户端发送的聊天信息被转发到其他所有的客户端则比较困难,可以考虑在服务器端使用Set集合来保存所有的客户端信息,
每当接收到一个客户端的数据报之后,程序检查该数据报的源SocketAddress是否在Set集合中,如果不在就将该SocketAddress添加到该Set集合中。
这样又涉及一个问题:可能有些客户端发送一个数据报之后永久性地退出了程序,但服务器端还将该客户端的SocketAddress保存在Set集合中……
总之,这种方式需要处理的问题比较多,编程比较烦琐。幸好Java为UDP协议提供了MulticastSocket类,通过该类可以轻松地实现多点广播。
MulticastSocket没有客户端服务端之说,都是发往广播地址中,只要join到广播地址中,那么所有MulticastSocket都会接到此数据。
MulticastSocket类是DatagramSocket的子类 JoinGroup(InetAddress mulicastAddr):将该MulticastSocket加入到广播地址中。发送广播那么我们需要用到广播地址(230.0.0.1)。 LeaveGroup(InetAddress mulicastAddr): 与JoinGroup(InetAddress mulicastAddr)相反。 SetTimeToLive(int ttl):设置数据可跨多少个网络。
*设置ttl为1我们就在局域网内使用。
ttl我们可以在CMD命令窗口中通过ping命令可以确认到TTL对应值。
当ttl=0时,指定数据报应停留在本地主机。
当ttl=1时,指定数据报发送到局域网。
当ttl=32时,指定数据报发送到本站点的网络上。
当ttl=64时,指定数据报发送到本地区。
当ttl=128时,指定数据报发送到本大洲内。
当ttl=255时,指定数据报发送所有地方。
实例:
在原来的DatagramSocket的客户端代码中进行了修改。
import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; import java.util.Scanner; public class MulticastSocketDemo implements Runnable { // 定义发送数据报的目的地 public static final int DEST_PORT = 30000; public static final String MULTICAST_IP = "230.0.0.1"; public InetAddress multicastAddr = null; // 定义每个数据报的最大大小为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; // 定义一个MulticastSocket对象 private MulticastSocket socket = null; public void init() throws IOException { try { // 创建一个MulticastSocket,必須指定端口,指定端口为DEST_PORT, socket = new MulticastSocket(DEST_PORT); //创建一个广播地址 multicastAddr = InetAddress.getByName(MULTICAST_IP); // 将MulticastSocket加入到广播地址中 socket.joinGroup(multicastAddr); // 使用在局域网中 socket.setTimeToLive(1); // 发送的数据报回送到自身 socket.setLoopbackMode(false); // 初始化发送用的MulticastSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0], 0,multicastAddr, DEST_PORT); // 创建接收数据的线程 new Thread(this).start(); // 创建键盘输入流 Scanner scan = new Scanner(System.in); // 不断地读取键盘输入,没有数据时会堵塞,等待输入 while (scan.hasNextLine()) { // 将键盘输入的一行字符串转换成字节数组 byte[] buff = scan.nextLine().getBytes(); // 设置发送用的DatagramPacket中的字节数据 outPacket.setData(buff); // 发送数据报 socket.send(outPacket); } } finally { socket.close(); } } @Override public void run() { // TODO Auto-generated method stub try { // 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组中 while (true) { socket.receive(inPacket); System.out.println(new String(inBuff, 0, inPacket.getLength())); } } catch (IOException e) { // TODO Auto-generated catch block if(socket !=null){ try { socket.leaveGroup(multicastAddr); socket.close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } e.printStackTrace(); } } public static void main(String[] args) throws IOException { new MulticastSocketDemo().init(); } }
运行:
- 打开多个此窗口
- 在窗口键盘输入后enter