UDP协议与TCP的协议
UDP优点:速度快
--这里不作过多的累赘,估计都了略了解一二
先说说IO中的UDP:
1、java.util.DatagramSocket:负责接收和发送UDP数据报。
2、java.util.DatagramPacket:表示UDP数据报。
作为服务端:DatagramSocket必须与本地主机的ip和端口进行绑定,同时都可以接收任意远程的UDP数据,在DatagramPacket中它一定包含了远的主机ip和端口信息。
作为客户端:只是少了服务端的bind(int port),使用无参构造函数时,则就像TCP中使用任意一个空闲的UDP端口进行发送DatagramPacket。
DatagramPacket类包括以下属性:
data:表示数据报的数据缓冲区。
offset:表示数据报的数据缓冲区的起始位置。
length:表示数据报的长度。
address:对于用于发送的数据报,address属性表示数据报的目标地址。对于用于接收的数据报,address属性表示发送者的地址。
port:对于用于发送的数据报,address属性表示数据报的目标UDP端口。对于用于接收的数据报,port属性表示接收者的UDP端口。
它们的关系如下图:
3、UDP协议是无连接的协议
客户端的DatagramSocket与服务器端的DatagramSocket不存在一一对应关系,两者无需建立连接,就能交换数据报。
DatagramSocket提供了接收和发送数据报的方法:
public void receive(DatagramPacket dst)throws IOException //接收数据报
public void send(DatagramPacket src)throws IOException //发送数据报
4、Java IO的UDP服务端核心代码:
/**
* 接收
*/
@Test
public void testRec() {
try {
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), port);
DatagramSocket ds = new DatagramSocket(address);
/**
* 大多数的udp传输都是8k
*/
byte[] buffer = new byte[8 * 1024];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
//如果在接收端指定datagrampacket的address则意思是只接收指定ip和端口的udp数据报文
//DatagramPacket dp = new DatagramPacket(buffer, buffer.length, new InetSocketAddress("127.0.0.1", 4444));
while (true) {
ds.receive(dp);
System.out.println("dp.getLength:" + dp.getLength());
System.out.println(new String(dp.getData(),0, dp.getLength()));
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
5、Java IO的UDP客户端核心代码:
/**
* 指定发送ip
*/
@Test
public void testSendSpecifyIp() {
try {
// 指定目标地址
InetAddress address = InetAddress.getLocalHost();
// 无参构造函数,使用随机端口发送
DatagramSocket ds = new DatagramSocket();
String content = "Hi udp sever, testSendSpecifyIp!";
// 数据报文
DatagramPacket datagramPacket = new DatagramPacket(content.getBytes(), content.length(), address, port);
System.out.println(ds.getLocalAddress());
// 发送
ds.send(datagramPacket);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
6、如果需要广播的发送则在指定目标ip中改成广播地址即可:
/**
* 本地广播发送
*/
@Test
public void testSendLocalBrocast(){
// 本网段的广播地址
InetSocketAddress address = new InetSocketAddress("172.16.27.255",port);
try {
// 匿名udp socket
DatagramSocket ds = new DatagramSocket();
String content = "Test sending local broacast... msg=testSendLocalBrocast !";
// 广播报文
DatagramPacket dp = new DatagramPacket(content.getBytes(), content.length(), address);
ds.send(dp);
} catch (Exception e) {
e.printStackTrace();
}
}
附:广播地址的计算(广播地址B与IP地址I,子网掩码M的关系为:B = (I & M)|~M)
第一步:获得网段(I&M)
第二步:将子网俺码按位取反
第三步:将1、2步进行相或
最简单的例子:比如宿舍中本机ip为192.168.1.100,子网掩码:255.255.255.0,求广播地址。如下图(为了直观图中使用十进制,实际需要转为二进制进行计算)
7、组播MulticastSocket
它继承DatagramSocket,所以它们是比较相像的,用法基本相同,不过需要在发送前加入组,不需要后需要离开组。
组播的概念,通过对比简单解释一下,避免过多的书面语:
(1)单播:即是基于一对一的思想进行传播,发送端需要指定ip与端口,接收端同样需要指定需要接收的ip,port(一对一,在DatagramPackage中指定);
(2)单播:即是基于多对一的思想进行传播,(不错依然是单播)多个发送端口需要指定ip与port,接收端不需要指定ip,port(多对一)。
(3)广播:即是基于一对多r的思想进行传播,先说说大大家都清楚的广播,顾名思义广播即是对网内所有的ip进行发送数据,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都可以接收到所有信息(不管你是否需要),缺点浪费资源。
(4)多播:只能说在需求状况下,它基于单播与广播之间,属于按需分配,只有都指定了组播地址和端口才能发送与接收。如果偏要说它属于“一对多还,多对多,抑者是一对一”,只能说它的思想还是(多对一对多)在电子商务领域有一个叫c2b2c的概念,中间那个“一”就是组播ip,发送端,根据组播ip发送,接收端根据组播ip接收。
附:http://www.360doc.com/content/07/0801/22/38435_648534.shtml
(5)看代码说话:
package com.jasic;
import org.junit.Test;
import java.net.*;
/**
* User: Jasic
* Date: 12-12-5
*/
public class MultiUdpIo {
/**
* 组播的地址是保留的D类地址从224.0.0.0—239.255.255.255
* 224.0.0.0—244.0.0.255 只能用于局域网中路由器是不会转发的,
* 并且224.0.0.1是所有主机的地址,224.0.0.2所有路由器的地址,
* 224.0.0.5所有ospf路由器的地址,224.0.13是PIMv2路由器的地址;
* 239.0.0.0—239.255.255.255 私有地址(和192.168.x..x这类地址类似);
* 224.0.1.0—238.255.255.255 用与Internet上的。
*/
public static String mul_ip1 = "239.0.0.1";
public static int mul_port1 = 4441;
public static String mul_ip2 = "239.0.0.2";
//这里可以指定不同的端口,改的同时如果还需要接收到消息2,则需要改testMultiRec中multicastSocket的端口咯
public static int mul_port2 = mul_port1;
/**
* 组播发送测试
*/
@Test
public void testMultiSend(){
try {
// 组播ip1
InetAddress a1 = InetAddress.getByName(mul_ip1);
// 组播ip2
InetAddress a2 =InetAddress.getByName(mul_ip2);
// socket组(随意绑定一个空闲udp端,这里也可以使用匿名构造函数)
MulticastSocket multicastSocket = new MulticastSocket();
multicastSocket.joinGroup(a1);
multicastSocket.joinGroup(a2);
String content1 = "a1:你在哪里,亲。。。?";
String content2 = "a2:你死哪里去了,亲。。。?";
DatagramPacket packet1 = new DatagramPacket(content1.getBytes(),content1.getBytes().length,a1,mul_port1);
DatagramPacket packet2 = new DatagramPacket(content2.getBytes(),content2.getBytes().length,a2,mul_port2);
multicastSocket.send(packet1);
System.out.println("数据[" + content1 + "]发送给组播地址:" + a1 + ":" + mul_port1 + ", 接收端需指定这个ip与端口");
multicastSocket.send(packet2);
System.out.println("数据[" + content2 + "]发送给组播地址:" + a2 + ":" + mul_port2 + ", 接收端需指定这个ip与端口");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 组播接收测试
*/
@Test
public void testMultiRec(){
try {
// 组播ip1
InetAddress a1 = InetAddress.getByName(mul_ip1);
// 组播ip2
InetAddress a2 =InetAddress.getByName(mul_ip2);
// 一个MulticastSocket只能监听一个udp端口喔
// socket组(随意绑定一个空闲udp端,这里也可以使用匿名构造函数)
MulticastSocket multicastSocket = new MulticastSocket(mul_port1);
// 同一个端口监听两个组网地址
multicastSocket.joinGroup(a1);
multicastSocket.joinGroup(a2);
byte[] buffer = new byte[8*1024];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
while (true){
multicastSocket.receive(packet);
System.out.println("接收到长度:" + packet.getLength());
System.out.println("接收内容:" + new String(packet.getData(),0,packet.getLength()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
8、Java NIO的UDP接收
public class UdpNio {
private static int port = 4444;
@Test
public void rec() {
Selector selector = null;
DatagramChannel dc = null;
try {
selector = Selector.open();
dc = DatagramChannel.open();
InetSocketAddress address = new InetSocketAddress(port);
dc.bind(address);
dc.configureBlocking(false);
dc.register(selector, SelectionKey.OP_READ);
while (true) {
int count = selector.select();
if (count <= 0) {
continue;
}
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (key.isReadable()) {
DatagramChannel datagramChannel = (DatagramChannel) key.channel();
ByteBuffer temp = ByteBuffer.allocate(10);
int l = 0;
System.out.println("通道是否打开:" + datagramChannel.isOpen());
System.out.println("通道是否连接:" +datagramChannel.isConnected());
ByteBuffer bu = ByteBuffer.allocate(8 * 1024);
System.out.println("数据包源地址" + datagramChannel.receive(bu));
int pos = bu.position();
bu.flip();
System.out.println(new String(bu.array(),0, pos));
//记录NotYetConnectedException
System.out.println("这里不能使用read(temp),否则会抛NotYetConnectedException");
// 在这里我直接copy,TCP的测试代码,后来出错才发觉,UDP不能使用read,因为它根本不存连接概念。
//while ((l = datagramChannel.read(temp)) != -1 && l!=0) {
// }
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
9、 其实在UDP的NIO中与TCP的基本相同,但记住一点,它是没连接的概念的。
DatagramChannel的write()与send()方法的区别在于:
(1)write()方法要求DatagramChannel已经建立连接,也就是说,程序在调用DatagramChannel的write()方法之前,要求先调用connect()方法使通道与特定的远程接收方连接。而send()方法则没有这一限制。
(2)在非阻塞模式下,write()方法不保证把ByteBuffer内的所有剩余数据作为一个数据报发送。假如ByteBuffer的剩余数据为r,实际发送的字节数为n,那么0<=n<=r。而send()方法总是把ByteBuffer内的所有剩余数据作为一个数据报发送。
DatagramChannel的read()与receive()方法的区别在于:
read()方法要求DatagramChannel已经建立连接,也就是说,程序在调用DatagramChannel的read()方法之前,要求先调用connect()方法使通道与特定的远程发送方连接。
而receive()方法则没有这一限制。