第12章 UDP

来源:互联网 发布:可以在家干的工作 知乎 编辑:程序博客网 时间:2024/06/01 12:14

第12章 UDP

12.1 UDP协议

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

12.2 UDP客户端

示例12-1:一个daytime协议客户端
<span style="font-size:18px;">import java.io.*;import java.net.*;public class DaytimeUDPClient {  private final static int PORT = 13;  private static final String HOSTNAME = "time.nist.gov";  public static void main(String[] args) {    try (DatagramSocket socket = new DatagramSocket(0)) {      socket.setSoTimeout(10000);      InetAddress host = InetAddress.getByName(HOSTNAME);      DatagramPacket request = new DatagramPacket(new byte[1], 1, host , PORT);      DatagramPacket response = new DatagramPacket(new byte[1024], 1024);      socket.send(request);      socket.receive(response);      String result = new String(response.getData(), 0, response.getLength(),                                 "US-ASCII");      System.out.println(result);    } catch (IOException ex) {      ex.printStackTrace();    }  } }</span>

12.3 UDP服务器

<span style="font-size:18px;">import java.net.*;import java.util.Date;import java.util.logging.*;import java.io.*;public class DaytimeUDPServer {  private final static int PORT = 13;  private final static Logger audit = Logger.getLogger("requests");  private final static Logger errors = Logger.getLogger("errors");  public static void main(String[] args) {    try (DatagramSocket socket = new DatagramSocket(PORT)) {      while (true) {        try {          DatagramPacket request = new DatagramPacket(new byte[1024], 1024);          socket.receive(request);                    String daytime = new Date().toString();          byte[] data = daytime.getBytes("US-ASCII");          DatagramPacket response = new DatagramPacket(data, data.length,               request.getAddress(), request.getPort());          socket.send(response);          audit.info(daytime + " " + request.getAddress());        } catch (IOException | RuntimeException ex) {          errors.log(Level.SEVERE, ex.getMessage(), ex);        }      }    } catch (IOException ex) {      errors.log(Level.SEVERE, ex.getMessage(), ex);    }  } }</span>

12.4 DatagramPacket类

构造函数

接收数据报的构造函数
这两个构造函数可以创建新的DatagramPacket对象从网络接收数据:
public DatagramPacket(byte[] buffer, int length)
当Socket接收一个数据报时,它将数据报的数据部分存储在buffer,从buffer[0]开始,一直到包完全存储,或者直到向buffer写入了length字节。
public DatagramPacket(byte[] buffer, int offset, int length)
第二个构造函数,将从buffer[offet]开始存储。
不要创建超过8192(8k)字节数据的DatagramPacket对象。

发送数据报的构造函数
下面4个构造函数会创建新的DatagramPacket对象,用来通过网络发送数据:
public DatagramPacket(byte[] data, int length, InetAddress destination, int port)
public DatagramPacket(byte[] data, int offset, int length, InetAddress destination, int port)
public DatagramPacket(byte[] data, int length, SocketAddress destination)
public DatagramPacket(byte[] data, int offset, int length,SocketAddress destination)

get方法
有6个获取数据报不同部分地方法,主要用于接收网络的数据报。
public InetAddress getAddress()
public int getPort()
public SocketAddress getSocketAddress()
包含远程主机的IP地址和端口。
public byte[] getData()
返回数据报中的数据
将数据转化为字符串:
String s  = new String(dp.getData(),"UTF-8");
将数据转化为其他java数据
InputStream in = new ByteArrayInputStream(packet.getData(),packet.getOffset(),packet.getLength());
DataInputStream din = new DataInputStream(in);
public int getLength()
返回数据报中数据的字节数。
public int getOffset()
返回该数组中的一个位置,即开始填充数据报数据的那个位置。

set方法
public void setData(byte[] data)
可以重复地发送相同的DatagramPacket对象,每次只改变数据。
public void setData(byte[] data, int offset, int length)
public void setAddress(InetAddress remote)
会修改数据报发往的地址。这允许你将同一个数据报发送给多个不同的接收方。
public void setPort(int port)
public void setAddress(SocketAddress remote)
会改变数据报包要发往的地址和端口。
public void setLength(int length)
改变内部缓冲区中包含实际数据报数据的字节数,而不包括未填充数据的空间。

12.5 DatagramSocket类

客户端和服务器使用DatagramSocket是一样的,区别在于使用匿名端口还是已知端口。这与TCP中不同。

构造函数
所有构造函数都只处理本地地址和端口。远程地址和端口存储在DatagramPacket中,而不是DatagramScoket。
一个DatagramSocket可以从多个远程主机和端口收发数据报。
public DatagramSocket() throws SocketException
创建一个绑定到匿名端口的Socket.
public DatagramSocket(int port) throws SocketException
创建一个在指定端口监听入站数据报的Socket。
public DatagramSocket(int port, InetAddress interface) throws SocketException
public DatagramSocket(SocketAddress interface) throws SocketException
protected DatagramSocket(DatagramSocketImpl impl ) throws SocketException

发送和接收数据报
DatagramSocket类的首要任务是发送和接收UDP数据报。一个Socket可以既发送又接收数据报。可以同时对多台主机收发数据。
public void send(DatagramPacket dp) throws IOException
public void receive(DatagramPacket d) throws IOException
public void close()
调用DatagramSocket对象的close()方法将释放该Socket占用的端口。
public int getLocalPort()
返回Socket正在监听的本地端口。
public InetAddress getLocalAddress()
返回Socket绑定到的本地地址。
public SocketAddress getLocalSocketAddress()
返回包装了Socket绑定到的本地接口和端口

管理连接
public void connect(InetAddress host, int port)
指定DatagramSocket只对指定远程主机和指定远程端口收发数据包。
public void disconnect()
disconnect()方法中断已连接DatagramSocket的“连接”,从而可以再次收发任何主机和端口的包。
public int getPort()
返回连接的远程端口。
public InetAddress getInetAddress()
返回连接的远程主机的地址
public InetAddress getRemoteSocketAddress()
返回连接的远程主机的地址

Socket选项
Java支持6个UDP Socket选项:
*SO_TiMEOUT
SO_TIMEOUT是receive()在抛出异常前等待入站数据报的时间,以毫秒计。
public void getSoTimeout(int timeout) throws SocketException
public int getSoTimeout() throws IOException
*SO_RCVBUF
DatagramSocket的SO_RCVBUF选项与Socket的SO_RCVBUF选项紧密相关。
public void setReceiveBufferSize(int size) throws SocketException
public int getReceiveBufferSize() throws SocketException
*SO_SNDBUF
获取和设置建议用于网络传输的发送缓冲区大小:
public void setSendBufferSize(int size) throws SocketException
public int getSendBufferSize() throws SocketException
*SO_REUSEADDR
SO_REUSEADDR可以控制是否允许多个数据报Socket同时绑定到相同的端口和地址。
public void setReuseAddress(boolean on) throws SocketException
public boolean getReuseAddress() throws SocketException
*SO_BROADCAST
SO_BROADCAST选项控制是否允许一个Socket向广播地址收发包。
public void setBroadcast(boolean on) throws SocketException
public boolean getBroadcast() throws SocketException
*IP_TOS
业务流类型。

12.6 一些有用的应用

12.7 DatagramChannel

DatagramChannel类用于非阻塞UDP应用程序。

使用DatagramChannel

打开一个Socket
DatagramChannel channel  = DatagramChannel.open();
SocketAddress address = new InetSocketAddress(3141);
DatagramSocket socket = channel.socket();
socket.bind(address);
Java7中简化:
SocketAddress address  = new InetSocketAddress(3141);
channel.bind(address);

接收
public SocketAddress receive(ByteBuffer dst) throws IOException
如果通道是阻塞的,这个方法砸读取到包之前不会返回。
如果通道是非阻塞的,没有包可以读取的情况下这个方法会立即返回null。
<span style="font-size:18px;">import java.io.*;import java.net.*;import java.nio.*;import java.nio.channels.*;public class UDPDiscardServerWithChannels {  public final static int PORT = 9;  public final static int MAX_PACKET_SIZE = 65507;  public static void main(String[] args) {        try {      DatagramChannel channel = DatagramChannel.open();      DatagramSocket socket = channel.socket();      SocketAddress address = new InetSocketAddress(PORT);      socket.bind(address);      ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);      while (true) {        SocketAddress client = channel.receive(buffer);        buffer.flip();        System.out.print(client + " says ");        while (buffer.hasRemaining()) System.out.write(buffer.get());        System.out.println();        buffer.clear();      }    } catch (IOException ex) {      System.err.println(ex);    }  }}</span>

发送
public int send(ByteBuffer src,SocketAddress target) throws IOExcetion
<span style="font-size:18px;">import java.io.*;import java.net.*;import java.nio.*;import java.nio.channels.*;public class UDPEchoServerWithChannels {  public final static int PORT = 7;  public final static int MAX_PACKET_SIZE = 65507;  public static void main(String[] args) {        try {      DatagramChannel channel = DatagramChannel.open();      DatagramSocket socket = channel.socket();      SocketAddress address = new InetSocketAddress(PORT);      socket.bind(address);      ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);      while (true) {        SocketAddress client = channel.receive(buffer);        buffer.flip();        channel.send(buffer, client);        buffer.clear();      }    } catch (IOException ex) {      System.err.println(ex);    }  }}</span>

连接
打开一个数据报通道,可以使用Connect()方法将它连接到一个特定的远程地址:
SocketAddress remote = new InetSocketAddress("time.nist.gov",37);
channel.connect(remote);

读取
有3个read()方法,只用于已连接的通道。
public int read(ByteBuffer dst) throws IOException
public long read(ByteBuffer[] dsts) throws IOException
public long read(ByteBuffer[] dsts,int offset,int length) throws IOException

写入
有3个write()方法,只用于已连接的通道。
public int write(ByteBuffer src) throws IOException
public long write(ByteBuffer[] dsts) throws IOException
public long write(ByteBuffer[] dsts,int offset,int length) throws IOException

选择器和非阻塞I/O也可以用于UDP:
基于通道的UDP echo客户端:
import java.io.*;import java.net.*;import java.nio.*;import java.nio.channels.*;import java.util.*;public class UDPEchoClientWithChannels {  public  final static int PORT = 7;  private final static int LIMIT = 100;   public static void main(String[] args) {        SocketAddress remote;    try {      remote = new InetSocketAddress(args[0], PORT);    } catch (RuntimeException ex) {      System.err.println("Usage: java UDPEchoClientWithChannels host");      return;       }        try (DatagramChannel channel = DatagramChannel.open()) {      channel.configureBlocking(false);      channel.connect(remote);            Selector selector = Selector.open();      channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);            ByteBuffer buffer = ByteBuffer.allocate(4);      int n = 0;      int numbersRead = 0;      while (true) {        if (numbersRead == LIMIT) break;        // wait one minute for a connection        selector.select(60000);        Set<SelectionKey> readyKeys = selector.selectedKeys();        if (readyKeys.isEmpty() && n == LIMIT) {           // All packets have been written and it doesn't look like any           // more are will arrive from the network          break;        }        else {          Iterator<SelectionKey> iterator = readyKeys.iterator();          while (iterator.hasNext()) {            SelectionKey key = (SelectionKey) iterator.next();            iterator.remove();            if (key.isReadable()) {              buffer.clear();              channel.read(buffer);              buffer.flip();              int echo = buffer.getInt();              System.out.println("Read: " + echo);              numbersRead++;            }             if (key.isWritable()) {              buffer.clear();              buffer.putInt(n);              buffer.flip();              channel.write(buffer);              System.out.println("Wrote: " + n);              n++;              if (n == LIMIT) {                // All packets have been written; switch to read-only mode                key.interestOps(SelectionKey.OP_READ);              }            }          }        }      }      System.out.println("Echoed " + numbersRead + " out of " + LIMIT +                                                                " sent");      System.out.println("Success rate: " + 100.0 * numbersRead / LIMIT +                                                                "%");    } catch (IOException ex) {      System.err.println(ex);    }  }}

关闭
public void close() throws IOException

Socket选项//Java 7
SO_SNDBUF             StandardSocketOptions.SO_SNDBUF      Integer                 用于发送数据报包的缓冲区大小
SO_RCVBUF             StandardSocketOptions.SO_RCVBUF      Integer                  用于接收数据报包的缓冲区大小
SO_REUSEADDR     ...                                                                Boolean                启用/禁用地址重用
SO_BROADCAST     ...                                                                 Boolean                启用/禁用广播消息
IP_TOS     ...                                                                                  Integer                  业务流类型
IP_MULTICAST_IF     ...                                                                NetworkInterface   用于组播的本地网络接口
IP_MULTICAST_TTL                                                                     Integer                   用于组播数据报的生存时间值
IP_MULTICAST_LOOP                                                                  Boolean                 启用/禁用组播数据报的回送
0 0
原创粉丝点击