Java Nio 十五

2018/01/20 Java

Java NIO 十五

DatagramChannel

每一个 DatagramChannel 对象也有一个关联的 DatagramSocket 对象。DatagramChannel 是模拟包导向的无连接协议(如 UDP)。


public abstract class DatagramChannel
    extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel
{
    // This is a partial API listing
    public static DatagramChannel open( ) throws IOException
    public abstract DatagramSocket socket( );
    public abstract DatagramChannel connect (SocketAddress remote)
        throws IOException;
    public abstract boolean isConnected( );
    public abstract DatagramChannel disconnect( ) throws IOException;
    public abstract SocketAddress receive (ByteBuffer dst)
        throws IOException;
    public abstract int send (ByteBuffer src, SocketAddress target)
    public abstract int read (ByteBuffer dst) throws IOException;
    public abstract long read (ByteBuffer [] dsts) throws IOException;
    public abstract long read (ByteBuffer [] dsts, int offset,
int length)
        throws IOException;
    public abstract int write (ByteBuffer src) throws IOException;
    public abstract long write(ByteBuffer[] srcs) throws IOException;
    public abstract long write(ByteBuffer[] srcs, int offset,
int length)
        throws IOException;
}

创建 DatagramChannel 的模式和创建其他 socket 通道是一样的:调用静态的 open( )方法来创建 一个新实例。新 DatagramChannel 会有一个可以通过调用 socket( )方法获取的对等 DatagramSocket 对象。DatagramChannel 对象既可以充当服务器(监听者)也可以充当客户端(发送者)。如果希望新创建的通道负责监听,那么通道必须首先被绑定到一个端口或地址/端口组合上。绑定 DatagramChannel 同绑定一个常规的 DatagramSocket 没什么区别,都是委托对等 socket 对象上的 API 实现的:

DatagramChannel channel = DatagramChannel.open( );
DatagramSocket socket = channel.socket( );
socket.bind (new InetSocketAddress (portNumber));

DatagramChannel 是无连接的。每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据。与面向流的的 socket 不同,DatagramChannel 可以发送单独的数据报给不同的目的地址。同样,DatagramChannel 对象也可以接收来自任意地址的数据包。每个到达的数据报都含有关于它来自何处的信息(源地址)。

一个未绑定的 DatagramChannel 仍能接收数据包。当一个底层 socket 被创建时,一个动态生成 的端口号就会分配给它。绑定行为要求通道关联的端口被设置为一个特定的值(此过程可能涉及安 全检查或其他验证)。不论通道是否绑定,所有发送的包都含有 DatagramChannel 的源地址 (带端口号)。未绑定的 DatagramChannel 可以接收发送给它的端口的包,通常是来回应该通道之前发出的一个包。已绑定的通道接收发送给它们所绑定的熟知端口(wellknown port)的包。数据 的实际发送或接收是通过send( )receive( )方法来实现的:

public abstract class DatagramChannel
    extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel
{
    // This is a partial API listing
    public abstract SocketAddress receive (ByteBuffer dst)
        throws IOException;
    public abstract int send (ByteBuffer src, SocketAddress target)
}

receive( )方法将下次将传入的数据报的数据复制到预备好的 ByteBuffer 中并返回一个 SocketAddress 对象以指出数据来源。如果通道处于阻塞模式,receive( )可能无限期地休眠直到有包到达。如果是非阻塞模式,当没有可接收的包时则会返回 null。如果包内的数据超出缓冲区能承 受的范围,多出的数据都会被悄悄地丢弃。

假如提供的 ByteBuffer 没有足够的剩余空间来存放正在接收的数据包, 没有被填充的字节都会被悄悄地丢弃。

调用 send( )会发送给定 ByteBuffer 对象的内容到给定 SocketAddress 对象所描述的目的地址和端 口,内容范围为从当前 position 开始到末尾处结束。如果 DatagramChannel 对象处于阻塞模式,调用线程可能会休眠直到数据报被加入传输队列。如果通道是非阻塞的,返回值要么是字节缓冲区的字节数,要么是“0”。发送数据报是一个全有或全无(all-or-nothing)的行为。如果传输队列没有 足够空间来承载整个数据报,那么什么内容都不会被发送。

如果安装了安全管理器,那么每次调用 send( )receive( )时安全管理器的 checkConnect( )方法 都会被调用以验证目的地址,除非通道处于已连接的状态。

数据报协议的不可靠性是固有的,它们不对数据传输做保证。send( )方法返回的非零 值并不表示数据报到达了目的地,仅代表数据报被成功加到本地网络层的传输队列。传输过程中的协议可能将数据报分解成碎片。被 分解的数据报在目的地会被重新组合起来,接收者将看不到碎片。但是,如果有一个碎片不能按时到达,那么整个数据报将被丢弃。

DatagramChannel 对数据报 socket 的连接语义不同于对流 socket 的连接语义。有时候,将数据报对话限制为两方是很可取的。将 DatagramChannel 置于已连接的状态可以使除了它所“连接”到的地址之外的任何其他源地址的数据报被忽略。这是很有帮助的,因为不想要的包都已经被网络层丢弃了,从而避免了使用代码来接收、检查然后丢弃包的麻烦。

当 DatagramChannel 已连接时,使用同样的令牌,不可以发送包到除了指定给 connect( )方 法的目的地址以外的任何其他地址。试图一定要这样做的话会导致一个 SecurityException 异常。

可以通过调用带 SocketAddress 对象的 connect( )方法来连接一个 DatagramChannel,该 SocketAddress 对象描述了 DatagramChannel 远程对等体的地址。如果已经安装了一个安全管理器, 那么它会进行权限检查。之后,每次 send/receive 时就不会再有安全检查了,因为来自或去到任何其他地址的包都是不允许的。

不同于流 socket,数据报 socket 的无状态性质不需要同远程系统进行对话来建立连接状态。没有实际的连接,只有用来指定允许的远程地址的本地状态信息。可以使用 isConnected( )方法来测试一个数据报通道的连接状态。

不同于 SocketChannel(必须连接了才有用并且只能连接一次),DatagramChannel 对象可以任 意次数地进行连接或断开连接。每次连接都可以到一个不同的远程地址。调用 disconnect( )方法可 以配置通道,以便它能再次接收来自安全管理器(如果已安装)所允许的任意远程地址的数据或发送数据到这些地址上。

当一个 DatagramChannel 处于已连接状态时,发送数据将不用提供目的地址而且接收时的源地址也是已知的。这意味着 DatagramChannel 已连接时可以使用常规的 read( )write( )方法,包括scatter/gather 形式的读写来组合或分拆包的数据。

使用数据报socket不使用流socket的理由:

  • 程序可以承受数据丢失或无序的数据。
  • “发射后不管”(fire and forget)而不需要知道您发送的包是否已接收。
  • 数据吞吐量比可靠性更重要。
  • 需要同时发送数据给多个接受者(多播或者广播)。
Show Disqus Comments

Search

    Table of Contents