`
zachary.guo
  • 浏览: 487262 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

NIO - Socket 通道

    博客分类:
  • NIO
阅读更多
        socket 通道有三个类:SocketChannel、ServerSocketChannel 和 DatagramChannel。请注意:DatagramChannel 和 SocketChannel 实现定义读和写功能的接口,而 ServerSocketChannel 不实现。ServerSocketChannel 负责监听传入的连接和创建新的 SocketChannel对象,它本身从不传输数据。

        全部 socket 通道类在被实例化时都会创建一个对等 socket 对象。这些是我们所熟悉的来自 java.net 的类(Socket、ServerSocket和DatagramSocket),它们已经被更新以识别通道。对等的 socket 对象可以通过调用通道类的 socket() 方法从通道上获取。此外,这三个 java.net 类现在都有 getChannel()方法。

        虽然每个 socket 通道都有一个关联的 java.net socket 对象,却并非所有的 socket 都有一个关联的通道。如果你用传统方式(直接实例化)创建了一个 Socket 对象,它就不会有关联的 SocketChannel 并且它的 getChannel() 方法将总是返回 null。

        Socket 通道可以在非阻塞模式下运行。要把一个 socket 通道置于非阻塞模式,我们要依靠所有 socket 通道类的公有超级类:SelectableChannel。下面的方法就是关于通道的阻塞模式的:
public abstract class SelectableChannel extends AbstractChannel implements Channel {
    // This is a partial API listing
    public abstract void configureBlocking (boolean block) throws IOException;
    public abstract boolean isBlocking();
    public abstract Object blockingLock();
}

        设置或重新设置一个通道的阻塞模式是很简单的,只要调用 configureBlocking() 方法即可,传递参数值为 true 则设为阻塞模式,参数值为 false 值设为非阻塞模式。真的,就这么简单!调用isBlocking() 方法来判断某个 socket 通道当前处于哪种模式:
SocketChannel sc = SocketChannel.open();
sc.configureBlocking (false); // nonblocking
...
if ( ! sc.isBlocking( )) {
    doSomething (cs);
}

        偶尔地,我们也会需要防止 socket 通道的阻塞模式被更改。API 中有一个 blockingLock()方法,该方法会返回一个非透明的对象引用。返回的对象是通道实现修改阻塞模式时内部使用的。只有拥有此对象的锁的线程才能更改通道的阻塞模式。使用 blockingLock() 的示例:
Socket socket = null;
Object lockObj = serverChannel.blockingLock();
// have a handle to the lock object, but haven't locked it yet may block here until lock is acquired
synchronize (lockObj) {
    // This thread now owns the lock; mode can't be changed
    boolean prevState = serverChannel.isBlocking();
    serverChannel.configureBlocking(false);
    socket = serverChannel.accept();
    serverChannel.configureBlocking(prevState);
}
// lock is now released, mode is allowed to change
if (socket != null) {
    doSomethingWithTheSocket(socket);
}


    ◇ ServerSocketChannel
        让我们从最简单的 ServerSocketChannel 来开始对 socket 通道类的讨论。以下是 ServerSocketChannel 的完整API:
public abstract class ServerSocketChannel extends AbstractSelectableChannel {
    public static ServerSocketChannel open() throws IOException
    public abstract ServerSocket socket();
    public abstract ServerSocket accept() throws IOException;
    public final int validOps();
}

        ServerSocketChannel 是一个基于通道的 socket 监听器。它同我们所熟悉的 java.net.ServerSocket 执行相同的基本任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。

        用静态的 open() 工厂方法创建一个新的 ServerSocketChannel 对象,将会返回同一个未绑定的 java.net.ServerSocket 关联的通道。该对等 ServerSocket 可以通过在返回的 ServerSocketChannel 上调用 socket() 方法来获取。作为 ServerSocketChannel 的对等体被创建的 ServerSocket 对象依赖通道实现。这些 socket 关联的 SocketImpl 能识别通道。通道不能被封装在随意的 socket 对象外面。由于 ServerSocketChannel 没有 bind() 方法,因此有必要取出对等的 socket 并使用它来绑定到一个端口以开始监听连接。使用对等 ServerSocket 的 API 来根据需要设置其他的 socket 选项:
ServerSocketChannel ssc = ServerSocketChannel.open();
// 取到 ServerSocketChannel 对等的 serverSocket 对象
ServerSocket serverSocket = ssc.socket();
// Listen on port 1234
serverSocket.bind (new InetSocketAddress (1234));

        ServerSocketChannel 和 serverSocket 均有 accept() 方法,你可以在其中一个上调用 accept()。如果你选择在 ServerSocket 上调用 accept() 方法,那么它会同任何其他的 ServerSocket 表现一样的行为:总是阻塞并返回一个 java.net.Socket 对象。如果你选择在 ServerSocketChannel 上调用 accept() 方法则会返回 SocketChannel 类型的对象,返回的对象能够在非阻塞模式下运行。

    ◇ SocketChannel
        下面开始学习 SocketChannel,它是使用最多的 socket 通道类:
public abstract class SocketChannel extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel {

    // This is a partial API listing
    public static SocketChannel open() throws IOException
    public static SocketChannel open (InetSocketAddress remote) throws IOException
    public abstract Socket socket();
    public abstract boolean connect (SocketAddress remote) throws IOException;
    public abstract boolean isConnectionPending();
    public abstract boolean finishConnect() throws IOException;
    public abstract boolean isConnected();
    public final int validOps();
}

        每个 SocketChannel 对象创建时都是同一个对等的 java.net.Socket 对象串联的。静态的 open() 方法可以创建一个新的 SocketChannel 对象,而在新创建的 SocketChannel 上调用 socket() 方法能返回它对等的 Socket 对象;在该 Socket 上调用 getChannel() 方法则能返回最初的那个 SocketChannel。虽然每个 SocketChannel 对象都会创建一个对等的 Socket 对象,反过来却不成立。直接创建的 Socket 对象不会关联 SocketChannel 对象,它们的 getChannel() 方法只返回 null。

        新创建的 SocketChannel 虽已打开却是未连接的。在一个未连接的 SocketChannel 对象上尝试一个 I/O 操作会导致 NotYetConnectedException 异常。我们可以通过在通道上直接调用 connect() 方法或在通道关联的 Socket 对象上调用 connect() 来将该 socket 通道连接。一旦一个 socket 通道被连接,它将保持连接状态直到被关闭。你可以通过调用布尔型的 isConnected() 方法来测试某个 SocketChannel 当前是否已连接。创建 SocketChannel 时创建连接:
SocketChannel socketChannel = 
    SocketChannel.open(new InetSocketAddress ("somehost", somePort));

等价于:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress ("somehost", somePort));

        如果你选择使用传统方式进行连接 -- 通过在对等 Socket 对象上调用 connect()方法,那么传统的连接语义将适用于此:线程在连接建立好或超时过期之前都将保持阻塞。如果你选择通过在通道上直接调用 connect() 方法来建立连接并且通道处于阻塞模式(默认模式),那么连接过程实际上是一样的。

        在 SocketChannel 上并没有一种 connect() 方法可以让你指定超时(timeout)值,当 connect() 方法在非阻塞模式下被调用时 SocketChannel 提供并发连接:它发起对请求地址的连接并且立即返回值。如果返回值是 true,说明连接立即建立了(这可能是本地环回连接);如果连接不能立即建立,connect() 方法会返回 false 且并发地继续连接建立过程。

        面向流的的 socket 建立连接状态需要一定的时间,因为两个待连接系统之间必须进行包对话以建立维护流 socket 所需的状态信息。跨越开放互联网连接到远程系统会特别耗时。假如某个 SocketChannel 上当前正有一个并发连接,isConnectPending() 方法就会返回 true 值。

        调用 finishConnect() 方法来完成连接过程,该方法任何时候都可以安全地进行调用。假如在一个非阻塞模式的 SocketChannel 对象上调用 finishConnect() 方法,将可能出现下列情形之一:
  • connect( )方法尚未被调用。那么将产生 NoConnectionPendingException 异常。
  • 连接建立过程正在进行,尚未完成。那么什么都不会发生,finishConnect() 方法会立即返回 false 值。
  • 在非阻塞模式下调用 connect() 方法之后,SocketChannel 又被切换回了阻塞模式。那么如果有必要的话,调用线程会阻塞直到连接建立完成,finishConnect() 方法接着就会返回 true 值。
  • 在初次调用 connect() 或最后一次调用 finishConnect() 之后,连接建立过程已经完成。那么 SocketChannel 对象的内部状态将被更新到已连接状态,finishConnect() 方法会返回 true 值,然后 SocketChannel 对象就可以被用来传输数据了。
  • 连接已经建立。那么什么都不会发生,finishConnect() 方法会返回 true 值。
        当通道处于中间的连接等待(connection-pending)状态时,你只可以调用 finishConnect()、isConnectPending() 或 isConnected()方法。一旦连接建立过程成功完成,isConnected()将返回 true 值。finishConnect() 示例:
InetSocketAddress addr = new InetSocketAddress (host, port);
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
sc.connect(addr);
while ( ! sc.finishConnect( )) {
    doSomethingElse();
}
doSomethingWithChannel(sc);
sc.close();

        上面的代码中,我们通过 while 轮询并在连接进行过程中判断通道所处的状态,后续我们将了解到如何使用选择器来避免进行轮询并在异步连接建立之后收到通知。如果尝试异步连接失败,那么下次调用 finishConnect() 方法会产生一个适当的经检查的异常以指出问题的性质。通道然后就会被关闭并将不能被连接或再次使用。

        Socket 通道是线程安全的。并发访问时无需特别措施来保护发起访问的多个线程,不过任何时候都只有一个读操作和一个写操作在进行中。请记住,sockets 是面向流的而非包导向的。它们可以保证发送的字节会按照顺序到达但无法承诺维持字节分组。某个发送器可能给一个 socket 写入了 20 个字节而接收器调用 read() 方法时却只收到了其中的 3 个字节,剩下的 17 个字节仍然在传输中。由于这个原因,让多个不配合的线程共享某个流 socket 的同一侧绝非一个好的设计选择。

        connect() 和 finishConnect() 方法是互相同步的,并且只要其中一个操作正在进行,任何读或写的方法调用都会阻塞,即使是在非阻塞模式下。如果此情形下你有疑问或不能承受一个读或写操作在某个通道上阻塞,请用 isConnected() 方法测试一下连接状态。

    ◇ DatagramChannel
        最后一个 socket 通道是 DatagramChannel。正如 SocketChannel 对应 Socket,ServerSocketChannel 对应 ServerSocket,每一个 DatagramChannel 对象也有一个关联的 DatagramSocket 对象。不过原命名模式在此并未适用:“DatagramSocketChannel”显得有点笨拙,因此采用了简洁的“DatagramChannel”名称。

        正如 SocketChannel 模拟连接导向的流协议(如 TCP/IP),DatagramChannel 则模拟包导向的无连接协议(如 UDP/IP):
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。如果包内的数据超出缓冲区能承受的范围,多出的数据都会被悄悄地丢弃。

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

        请注意,数据报协议的不可靠性是固有的,它们不对数据传输做保证。send() 方法返回的非零值并不表示数据报到达了目的地,仅代表数据报被成功加到本地网络层的传输队列。此外,传输过程中的协议可能将数据报分解成碎片。例如,以太网不能传输超过 1,500 个字节左右的包。如果你的数据报比较大,那么就会存在被分解成碎片的风险,成倍地增加了传输过程中包丢失的几率。被分解的数据报在目的地会被重新组合起来,接收者将看不到碎片。但是,如果有一个碎片不能按时到达,那么整个数据报将被丢弃。

        我们可以通过调用带 SocketAddress 对象的 connect() 方法来连接一个 DatagramChannel,该 SocketAddress 对象描述了 DatagramChannel 远程对等体的地址。

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

        不同于 SocketChannel(必须连接了才有用并且只能连接一次),DatagramChannel 对象可以任意次数地进行连接或断开连接。每次连接都可以到一个不同的远程地址。当一个 DatagramChannel 处于已连接状态时,发送数据将不用提供目的地址而且接收时的源地址也是已知的。这意味着 DatagramChannel 已连接时可以使用常规的 read() 和 write() 方法,包括 scatter/gather 形式的读写来组合或分拆包的数据:
public abstract class DatagramChannel extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel {

    // This is a partial API listing
    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;
}

        read() 方法返回读取字节的数量,如果通道处于非阻塞模式的话这个返回值可能是“0”。write() 方法的返回值同 send() 方法一致:要么返回你的缓冲区中的字节数量,要么返回“0”(如果由于通道处于非阻塞模式而导致数据报不能被发送)。当通道不是已连接状态时调用 read() 或 write() 方法,都将产生 NotYetConnectedException 异常。
分享到:
评论
1 楼 xianneng.lin 2012-12-19  
引用

public abstract ServerSocket accept() throws IOException;  


应该是返回一个SocketChannel
阁下的博客写的太赞了,受益匪浅

相关推荐

    nio.rar_NIO_NIO-socket_java nio_java 实例_java.nio

    标题“nio.rar_NIO_NIO-socket_java nio_java 实例_java.nio”表明这个压缩包包含了一个关于Java NIO的实例,特别是关于NIO套接字(Socket)的编程示例。NIO套接字是Java NIO库中用于网络通信的关键组件,它们允许...

    android-socket-nio-master.zip

    "android-socket-nio-master.zip" 是一个关于Android中使用Socket结合NIO实现高效通信的项目,其目标是提高Socket通信的性能和处理大量并发连接的能力。 NIO(非阻塞I/O)是Java提供的一个替代传统I/O的API,主要...

    《NIO与Socket编程技术指南》_高洪岩

    《NIO与Socket编程技术指南》是一本深入探讨Java NIO(New Input/Output)和Socket编程的专业书籍,由高洪岩撰写。本书主要针对Java开发者,旨在帮助他们理解和掌握这两种在开发网络应用中至关重要的技术。 Java ...

    socket-nio-single-reactor.zip

    Socket NIO 单 Reactor 模式是一种在 Java 中实现高性能网络编程的技术,它结合了非阻塞I/O(New I/O,即NIO)和Reactor设计模式。本示例代码旨在帮助开发者理解如何使用Java NIO和Reactor模式构建网络服务。尽管...

    《NIO与Socket编程技术指南》高洪岩.zip

    非常详细地讲解了NIO中的缓冲区、通道、选择器、编码,以及使用Socket技术实现TCP/IP和UDP编程,细化到了演示全部SocketOption的特性,这对理解基于NIO和Socket技术为基础所开发的NIO框架是非常有好处的,本书以案例...

    java nio 实现socket

    ### Java NIO 实现Socket通信详解 #### 一、NIO与传统IO的区别及优势 在探讨如何使用Java NIO实现Socket通信之前,我们需要先理解NIO(Non-blocking I/O,非阻塞I/O)与传统阻塞I/O之间的区别。 **传统阻塞I/O...

    SocketIO-BIO-NIO-AIO.zip

    NIO基于通道(Channels)和缓冲区(Buffers)进行数据传输,可以实现单一线程处理多个客户端连接。当没有数据可读或无空间可写时,I/O操作会立即返回,而不是阻塞。此外,NIO还提供了选择器(Selectors),可以监控...

    java网络编程NIO视频教程

    Java NIO-Channel-Socket通道-概述 - **主要内容**:介绍Socket通道的基础知识,包括SocketChannel和ServerSocketChannel。 - **学习目标**:理解Socket通道的基本概念和使用场景。 #### 8. Java NIO-Channel-...

    NIO-实践-多线程实例

    NIO用于高性能Socket编程由来已久,网络也有较为丰富的原理和源代码。我这里主要介绍几点经验总结: 1.Selector.select()在筛选就绪的SelectionKey的时候,采用的是阻塞模式。同时只要在就绪的SelectionKey列表中有...

    基于Java的AIO框架smart-socket.zip

    而NIO引入了选择器(Selector)和通道(Channel),实现了多路复用,可以在单个线程中处理多个连接,但仍然需要轮询检查是否有I/O事件发生。 AIO(又称NIO.2)进一步改进了NIO,提供了异步读写操作。在AIO模型中,...

    Java NIO Socket基本

    **Socket编程在NIO中的应用**: 1. **ServerSocketChannel**:用于监听客户端连接,通过调用`ServerSocketChannel.open()`创建,然后绑定到指定的IP和端口,并调用`accept()`方法接收客户端连接。 2. **...

    nio的socket

    1. **SocketChannel**:这是NIO中的客户端连接通道,用于代替传统的`Socket`。`SocketChannel`可以从服务器接收数据,也可以向服务器发送数据。与`Socket`不同的是,`SocketChannel`是非阻塞的,这意味着当没有数据...

    Android-netty和socket通信的demo

    Netty的核心组件包括Bootstrap(启动器)、ServerBootstrap(服务器启动器)、Channel(通道)、EventLoop(事件循环)和ChannelHandler(处理程序)。通过这些组件,开发者可以方便地创建出可伸缩、低延迟的网络...

    【IT十八掌徐培成】Java基础第27天-03.NIO-Selector.zip

    本教程由知名IT教育专家徐培成讲解,主要聚焦于NIO中的Selector组件,它是NIO系统中的核心部分,用于管理多个通道(Channels)并监听它们的事件。 Selector允许单线程处理多个通道,极大地提高了程序的并发性能。在...

    NioSocket,包括server端和client端

    NioSocket是一个基于Java NIO(非阻塞I/O)技术实现的网络通信框架,它包含服务器端(Server)和客户端(Client)两部分。在Java编程中,NIO(New Input/Output)提供了一种不同于传统IO模型的I/O操作方式,其核心...

    网络编程(socket、NIO、mina)---demo

    在服务端,无论是Socket、NIO还是Mina,都需要创建监听套接字来接收客户端连接,并为每个连接创建一个新的套接字或通道。而在客户端,需要建立到服务器的连接,发送和接收数据。 总结来说,这个"网络编程(socket、...

    基于java NIO的socket通信demo

    在这个“基于java NIO的socket通信demo”中,我们将探讨如何使用NIO进行服务器和客户端的Socket通信,并解决通信过程中的字符集乱码问题。 首先,我们来看`NioServer.java`。这个文件中包含了一个基于NIO的服务器端...

    netty-socket实时在线人数统计

    相比传统的Java NIO,Netty提供了更高级别的API,使得开发者可以更容易地处理网络通信。在实时在线人数统计的场景下,Netty可以作为底层通信层,处理客户端连接、数据传输和断开连接等事件。 **WebSocket** ...

    Java视频教程 Java游戏服务器端开发 Netty NIO AIO Mina视频教程

    [第6节] Java NIO流-socket通道操作.flv [第7节] Java NIO流-文件通道操作.flv [第8节] Java NIO流-选择器 .flv [第9节] Java NIO流-选择器操作.flv 四、Mina视频教程 00、Mina视频课程介绍.flv 01、Mina...

    java socket nio 研究

    Java Socket和NIO(Non-blocking Input/Output,非阻塞I/O)是Java网络编程中的重要组成部分,它们在处理高并发、低延迟的网络服务时展现出强大的能力。本篇文章将深入探讨这两个概念,并通过示例代码`...

Global site tag (gtag.js) - Google Analytics