UDP协议网络编程
UDP协议
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
UDP协议是面向非连接的协议,没有建立连接的过程, 因此通信效率很高。
它在通信实例两端各建立一个Socket, 这两个Socket之间没有虚拟链路, 这两个Socket知识发送,接收数据报的对象。
Java提供了 DatagramSocket对象作为UDP协议的Socket。
DatagramSocket
DatagramSocket本身只是码头,不维护状态, 不能产生IO流, 作用是接收和发送数据报, 使用 DatagramPacket代表数据报文。
DatagramSocket 的构造器:
DatagramSocket():创建一个实例, 绑定到本机默认IP, 本机可用端口中随机选择一个端口。
DatagramSocket(int port): 本机默认IP, 指定端口
DatagramSocket(int port, InetAddress addr): 绑定到指定ip,port。
receive(DatagramPacket p): 接收数据
send(DatagramPacket p): 发送数据
DatagramPacket 的构造器, 指定一个空数组, 长度,字节数, 还可以指定ip地址和端口
服务端接收到 DatagramPacket 时,可以通过下面方法获取ip和port
InetAddress getAddress() : 发送时,返回目标地址, 接收时, 返回发送主机ip
int getPort(): 返回 port
SocketAddress getSocketAddress(): 返回SocketAddress, 包括ip和port。
Server端:
public class UdpServerTest {
private int port = 9002;
//每个报文大小为4KB
private int dataLength = 4096;
//接收数据的数组
byte[] inBuff = new byte[dataLength];
//接收数据对象
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
//发送数据对象
private DatagramPacket outPacket;
public void init() throws IOException{
try(
DatagramSocket socket = new DatagramSocket(port);
){
for (int i = 0; i < 100; i++){
//接收
socket.receive(inPacket);
// inPackage.getData 和 inBuff 是否是同一个数组
System.out.println(inBuff == inPacket.getData());
// 接收数据打印
System.out.println(new String(inBuff, 0, inPacket.getLength()));
byte[] sendData = "udp server".getBytes();
//发送数据
outPacket = new DatagramPacket(sendData, sendData.length, inPacket.getSocketAddress());
socket.send(outPacket);
}
}
}
public static void main(String[] args) throws IOException{
new UdpServerTest().init();
}
}
client 端
public class UdpClientTest {
private int port = 9002;
private int dataLength = 4096;
byte[] inBuff = new byte[dataLength];
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
private DatagramPacket outPacket;
public void init() throws IOException {
try(
DatagramSocket socket = new DatagramSocket();
){
outPacket = new DatagramPacket(new byte[0], 0, InetAddress.getByName("127.0.0.1") ,port);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
byte[] buff = scanner.nextLine().getBytes();
outPacket.setData(buff);
socket.send(outPacket);
socket.receive(inPacket);
System.out.println(new String(inBuff, 0, inPacket.getLength()));
}
}
}
public static void main(String[] args) throws IOException{
new UdpClientTest().init();
}
}
MulticastSocket
DatagramSocket只允许数据报文发送给指定的目标地址, 而MulticastSocket可以将数据报以广播方式发送到多个客户端。
IP协议为多点广播提供了这批特殊的IP地址,这些IP地址的范围是:224.0.0.0至239.255.255.255.
public
class MulticastSocket extends DatagramSocket {
/**
* Create a multicast socket.
*
* <p>If there is a security manager,
* its {@code checkListen} method is first called
* with 0 as its argument to ensure the operation is allowed.
* This could result in a SecurityException.
* <p>
* When the socket is created the
* {@link DatagramSocket#setReuseAddress(boolean)} method is
* called to enable the SO_REUSEADDR socket option.
*
* @exception IOException if an I/O exception occurs
* while creating the MulticastSocket
* @exception SecurityException if a security manager exists and its
* {@code checkListen} method doesn't allow the operation.
* @see SecurityManager#checkListen
* @see java.net.DatagramSocket#setReuseAddress(boolean)
*/
public MulticastSocket() throws IOException {
this(new InetSocketAddress(0));
}
/**
* Create a multicast socket and bind it to a specific port.
*
* <p>If there is a security manager,
* its {@code checkListen} method is first called
* with the {@code port} argument
* as its argument to ensure the operation is allowed.
* This could result in a SecurityException.
* <p>
* When the socket is created the
* {@link DatagramSocket#setReuseAddress(boolean)} method is
* called to enable the SO_REUSEADDR socket option.
*
* @param port port to use
* @exception IOException if an I/O exception occurs
* while creating the MulticastSocket
* @exception SecurityException if a security manager exists and its
* {@code checkListen} method doesn't allow the operation.
* @see SecurityManager#checkListen
* @see java.net.DatagramSocket#setReuseAddress(boolean)
*/
public MulticastSocket(int port) throws IOException {
this(new InetSocketAddress(port));
}
/**
* Create a MulticastSocket bound to the specified socket address.
* <p>
* Or, if the address is {@code null}, create an unbound socket.
*
* <p>If there is a security manager,
* its {@code checkListen} method is first called
* with the SocketAddress port as its argument to ensure the operation is allowed.
* This could result in a SecurityException.
* <p>
* When the socket is created the
* {@link DatagramSocket#setReuseAddress(boolean)} method is
* called to enable the SO_REUSEADDR socket option.
*
* @param bindaddr Socket address to bind to, or {@code null} for
* an unbound socket.
* @exception IOException if an I/O exception occurs
* while creating the MulticastSocket
* @exception SecurityException if a security manager exists and its
* {@code checkListen} method doesn't allow the operation.
* @see SecurityManager#checkListen
* @see java.net.DatagramSocket#setReuseAddress(boolean)
*
* @since 1.4
*/
public MulticastSocket(SocketAddress bindaddr) throws IOException {
super((SocketAddress) null);
// Enable SO_REUSEADDR before binding
setReuseAddress(true);
if (bindaddr != null) {
try {
bind(bindaddr);
} finally {
if (!isBound())
close();
}
}
}
有上面三个构造函数。
创建对象后,要加入多点广播地址,有下面方法:
joinGroup(): 加入组
leaveGroup() : 离开组
有些系统中, 可能有多个网络接口, 使用 setInterface() 设置指定接口, getInterface() 获取接口。
MulticastSocket 多一个 setTimeToLive() 方法。 设置最多跨越多少个网络
ttl :
0: 本地主机
1: 本地局域网, 默认值
32: 本站点网络
64: 本地区
128: 本大洲
255:所有地方
public class MulticastSocketTest implements Runnable {
//使用常量作为本程序多点广播的IP地址
private static final String BROADCAST_IP = "230.0.0.1";
//使用常量作为本程序的多点广播的目的地端口
public static final int BROADCAST_PORT = 3000;
//定义每个数据报大小最大为4kb
private static final int DATA_LEN = 4096;
//定义本程序的MulticastSocket实例
private MulticastSocket socket = null;
private InetAddress broadcastAddress = null;
private Scanner scan = null;
//定义接收网络数据的字节数组
byte[] inBuff = new byte[DATA_LEN];
//以指定字节数组创建准备接收数据的MulticastSocket对象
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
//定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket = null;
public void init() throws IOException {
//创建键盘输入流
Scanner scan = new Scanner(System.in);
//创建用于发送、接收数据的MulticastSocket对象,由于该MulticastSocket需要接收数据,所以有指定端口
socket = new MulticastSocket(BROADCAST_PORT);
broadcastAddress = InetAddress.getByName(BROADCAST_IP);
//将该socket加入到指定的多点广播地址
socket.joinGroup(broadcastAddress);
//设置本MulticastSocket发送的数据报会被回送到自身
socket.setLoopbackMode(false);
//初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT);
//启动本实例的run()方法作为线程执行体的线程
new Thread(this).start();
//不断的读取键盘输入
while (scan.hasNextLine()) {
//将键盘输入的一行字符转换成字节数组
byte[] buff = scan.nextLine().getBytes();
//设置发送用的DatagramPacket里的字节数据
outPacket.setData(buff);
//发送数据报
socket.send(outPacket);
}
socket.close();
}
public void run() {
while (true) {
//读取Socket中的数据,读到的数据放入inPacket所封装的字节组里
try {
socket.receive(inPacket);
//打印从socket读取到的内容
System.out.println("聊天信息:" + new String(inBuff, 0, inPacket.getLength()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (socket != null) {
//让该socket离开多点IP广播地址
try {
socket.leaveGroup(broadcastAddress);
//关闭socket对象
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.exit(1);
}
}
public static void main(String[] args) {
try {
new MulticastSocketTest().init();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}