Java网络编程学习笔记(四):UDP和组播

1,528 阅读7分钟
原文链接: www.geekmuseo.com

UDP

Java的UDP实现分为两个类:DatagramPacket类和DatagramSocket类。DatagramPacket类将数据字节填充到UDP包中,这称为数据报,由你来解析接收到的数据报。DatagramSocket类则是可以收发数据报。当发送数据的时候,要将数据放到DatagramPacket中,使用DatagramSocket来发送这个数据报。当接收数据的时候,可以从DatagramSocket中接收一个DatagramPacket对象,然后检查该对象的内容

1、DatagramPacket类

1.1、构造函数

public DatagramPacket(byte buf[], int length)
public DatagramPacket(byte buf[], int offset, int length)
public DatagramPacket(byte buf[], int length,InetAddress address, int port)
public DatagramPacket(byte buf[], int length, SocketAddress address)
public DatagramPacket(byte buf[], int offset, int length,InetAddress address, int port)
public DatagramPacket(byte buf[], int offset, int length, SocketAddress address)

DatagramPacket是UDP使用的数据包,它会包含要发送或者接受的数据,以及相应的地址和端口信息。上面6个构造函数,前两个只传入byte数组,表示要发送的数据或者要接受数据的数组。后四个构造函数除了填入数据外,还填入数据报发往的地址和端口。接受数据一般用前两个构造函数,发送数据一般来说用后四个构造函数:

byte[] data = "hello world".getBytes("UTF-8");
InetAddress ia = InetAddress.getByName("www.example.com");
int port = 22;
DatagramPacket dp = new DatagramPacket(data,data.length,ia,port);
//....发送数据

1.2、获取数据报信息

a)、public synchronized InetAddress getAddress()

这个方法返回一个InetAddress对象。如果数据报是远程服务器发过来的,则这个InetAddress对象包含远程服务器地址,如果这个数据报是本地创建的,则InetAddress表示要发往的远程服务器的地址。


b)、public synchronized int getPort()

这个方法返回一个整数,表示远程端口。如果数据报是远程服务器发过来的,这这个端口是远程服务器的端口,如果这个数据报是本地创建的,则这个端口表示要发往的远程服务器的端口。


c)、public synchronized SocketAddress getSocketAddress()

这个方法返回一个SocketAddress对象,其中包含了远程服务器的IP和端口。如果数据报是远程服务器发过来的,则这个SocketAddress对象包含远程服务器地址和端口,如果这个数据报是本地创建的,则SocketAddress表示要发往的远程服务器的地址和端口。


d)、public synchronized byte[] getData()

这个方法返回一个byte数组其中包含数据报中的数据。如果数据报是远程服务器发过来的,则这些数据是远程服务器的响应数据,如果这个数据报是本地创建的,则这些数据是本地对远程服务器的请求数据。


e)、public synchronized int getLength()

这个方法返回数据报中数据的字节数。它和getData().length不一定相等,因为getData()返回的字节数组,可能会在末尾被填充一些初始化数据,真正的有效数据的长度以本方法返回的为准。


f)、public synchronized int getOffset()

这个方法返回数据报中有效数据的开始位置。所以,获取数据报中的有效数据应该如下:

DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
socket.receive(dp);
String result = new String(dp.getData(),dp.getOffset(),dp.getLength(),"UTF-8");

1.3、设置数据报信息

a)、public synchronized void setAddress(InetAddress iaddr)

这个方法用来修改数据报发往的地址。当你要发给多个主机的时候,可以创建一个DatagramPacket,然后修改地址后发送,而不是创建多个DatagramPacket。


b)、public synchronized void setData(byte[] buf)

该方法用来改变数据内容,比如对同一个远程主机发送两个不同的数据,则可以创建一个DatagramPacket,然后修改内容后发送。


c)、public synchronized void setData(byte[] buf, int offset, int length)

这个方法指定一个数据中的部分范围的数据,作为数据报的发送内容。


d)、public synchronized void setLength(int length)

这个方法会改变数据报中实际需要发送或接受的数据长度,超过数据长度的数据不会被处理。


e)、public synchronized void setPort(int iport)

这个方法用于修改数据报要发往的端口。


f)、public synchronized void setSocketAddress(SocketAddress address)

这个方法用于同时修改数据报要发往的地址和端口。

2、DatagramSocket类

要发送DatagramPacket,必须打开一个DatagramSocket,这个socket会绑定到一个本地端口,在这个端口上监听入站数据。这个端口也会放置在出站数据报的首部中。也就是说,DatagramSocket不区分客户端和服务端,它既监听数据,也发送数据。

2.1、构造函数

public DatagramSocket() throws SocketException
public DatagramSocket(int port) throws SocketException
public DatagramSocket(int port, InetAddress laddr) throws SocketException
public DatagramSocket(SocketAddress bindaddr) throws SocketException

第一个构造函数在匿名本地端口打开一个socket。第二个构造函数在指定端口打开一个socket。后两个构造函数在指定的本地地址和端口打开socket,只有当本地有多个ip的时候才会用这种构造函数。注意,UDP的端口和TCP的端口是不冲突的,一个被TCP使用的端口,同时也可以被UDP使用。

2.2、发送数据

public void send(DatagramPacket p) throws IOException

一旦创建了DatagramSocket,就可以创建一个DatagramPacket,然后用send方法发送,根本不需要建立连接。

byte[] data = "hello world".getBytes("UTF-8");
InetAddress ia = InetAddress.getByName("www.example.com");
int port = 22;//远程服务器的端口
DatagramPacket dp = new DatagramPacket(data,data.length,ia,port);
DatagramSocket socket = new DatagramSocket();
socket.send(dp);

2.3、接受数据

public synchronized void receive(DatagramPacket p) throws IOException

一旦创建了DatagramSocket,就可以接受数据,这个方法是个阻塞方法,直到数据到达才会响应。

DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
//监听本地22端口的UDP数据报
DatagramSocket server  = new DatagramSocket(22);
//接收数据报
server.receive(dp);
System.out.println(new String(dp.getData(),dp.getOffset(),dp.getLength(),"UTF-8"));

2.4、socket管理

public void connect(InetAddress address, int port)

public void connect(SocketAddress addr) throws SocketException

public void disconnect()

UDP是不存在连接的,但是DatagramSocket任然有connect方法,这个方法并不是用来建立连接,而是告诉底层,本客户端只向指定的远程服务器发送数据,其他服务器一概不处理。而disconnect方法则是取消这种限制。

2.5、关闭

public void close()

public boolean isClosed()DatagramSocket

不管是客户端还是服务端,都通过close方法关闭,要检查是否已经关闭,可以使用isClosed方法。

组播

TCP和UDP都是点对点的收发数据,组播是指将一个数据一次发给一个组,所有在这个组里的机器都可以获取这个请求。如果一台机器需要获取数据,首先应该加入这个组,如果不再感兴趣,则可以退出这个组。组播在架构上有点类似于监听者模式,组播地址类似于主题(topic),加入组播的机器类似于监听者,客户端首先发送数据给组播地址的路由器,然后路由器将数据发送给加入这个组的所有机器。组播在Java中以MulticastSocket类的形式存在,它继承了DatagramSocket,因此也是一种基于数据报的无连接的协议。

MulticastSocket

1、构造函数

public MulticastSocket() throws IOException 
public MulticastSocket(int port) throws IOException
public MulticastSocket(SocketAddress bindaddr) throws IOException

这三个构造函数用于绑定本地地址和端口,从而创建一个socket。它和DatagramSocket并没有什么区别。

2、加入组

public void joinGroup(InetAddress mcastaddr) throws IOException
public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf)throws IOException

要加入一个组,必须传入一个InetAddress或者SocketAddress,而且这个地址必须是一个合法的组播地址,从224.0.0.0到239.255.255.255,如果不合法则会抛出一个IOException。一台机器可以加入多个组,所以这两个方法可以多次调用。下面举个例子:

MulticastSocket ms = new MulticastSocket(4322);
InetAddress ma1 = InetAddress.getByName("224.2.2.3");
InetAddress ma2 = InetAddress.getByName("224.2.2.4");
ms.joinGroup(ma1);
ms.joinGroup(ma2);

3、离开组

public void leaveGroup(InetAddress mcastaddr) throws IOException
public void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf)throws IOException

有加入组,就有离开组,上面两个方法需要传入特定的组播地址,从而离开一个特定的组,MulticastSocket没有提供离开所有组的方法,因此需要保存加入的组。

4、收发数据

由于MulticastSocket继承了DatagramSocket,因此它的收发数据本质上是依靠DatagramSocket的收发方法,在此不做讨论。


5、关闭

由于MulticastSocket继承了DatagramSocket,因此它的关闭和DatagramSocket一样。