我是靠谱客的博主 独特大门,这篇文章主要介绍NIO学习之ServerSocketChannel和SocketChannelSocket 通道SocketChannel,现在分享给大家,希望可以做个参考。

NIO学习

  • Socket 通道
    • ServerSocketChannel
      • 代码演示
      • ServerSocketChannel相关函数讲解
  • SocketChannel
    • SocketChannel 特征
    • SocketChannel 的使用
    • ServerSocket,Socket与ServerSocketChannel,SocketChannel区别


Socket 通道

(1)新的 socket 通道类可以运行非阻塞模式并且是可选择的,可以激活大程序(如网络服务器和中间件组件)巨大的可伸缩性和灵活性。本节中我们会看到,再也没有为每个 socket 连接使用一个线程的必要了,也避免了管理大量线程所需的上下文交换开销。

借助新的 NIO 类,一个或几个线程就可以管理成百上千的活动 socket 连接了并且只有很少甚至可能没有性能损失。

所有的 socket 通道类(DatagramChannel、SocketChannel 和 ServerSocketChannel)都继承了位于 java.nio.channels.spi 包中的 AbstractSelectableChannel。

这意味着我们可以用一个 Selector 对象来执行socket 通道的就绪选择(readiness selection)。

(2)请注意 DatagramChannel 和 SocketChannel 实现定义读和写功能的接口而ServerSocketChannel 不实现。ServerSocketChannel 负责监听传入的连接和创建新的 SocketChannel 对象,它本身从不传输数据。

(3)在我们具体讨论每一种 socket 通道前,您应该了解 socket 和 socket 通道之间的关系。通道是一个连接 I/O 服务导管并提供与该服务交互的方法。就某个 socket 而言,它不会再次实现与之对应的 socket 通道类中的 socket 协议 API,而 java.net 中已经存在的 socket 通道都可以被大多数协议操作重复使用。

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

(4)要把一个 socket 通道置于非阻塞模式,我们要依靠所有 socket 通道类的公有超级类:SelectableChannel。就绪选择(readiness selection)是一种可以用来查询通道的机制,该查询可以判断通道是否准备好执行一个目标操作,如读或写。非阻塞 I/O 和可选择性是紧密相连的,那也正是管理阻塞模式的 API 代码要在SelectableChannel 超级类中定义的原因。

设置或重新设置一个通道的阻塞模式是很简单的,只要调用 configureBlocking( )方法即可,传递参数值为 true 则设为阻塞模式,参数值为 false 值设为非阻塞模式。可以通过调用 isBlocking( )方法来判断某个 socket 通道当前处于哪种模式。

AbstractSelectableChannel.java 中实现的 configureBlocking()方法如下:

    /**
     * Adjusts this channel's blocking mode.
     *
     * <p> If the given blocking mode is different from the current blocking
     * mode then this method invokes the {@link #implConfigureBlocking
     * implConfigureBlocking} method, while holding the appropriate locks, in
     * order to change the mode.  </p>
     */
    public final SelectableChannel configureBlocking(boolean block)
        throws IOException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            boolean blocking = !nonBlocking;
            if (block != blocking) {
                if (block && haveValidKeys())
                    throw new IllegalBlockingModeException();
                implConfigureBlocking(block);
                nonBlocking = !block;
            }
        }
        return this;
    }

非阻塞 socket 通常被认为是服务端使用的,因为它们使同时管理很多 socket 通道变得更容易。但是,在客户端使用一个或几个非阻塞模式的 socket 通道也是有益处的,例如,借助非阻塞 socket 通道,GUI 程序可以专注于用户请求并且同时维护与一个或多个服务器的会话。在很多程序上,非阻塞模式都是有用的。

偶尔地,我们也会需要防止 socket 通道的阻塞模式被更改。API 中有一个blockingLock( )方法,该方法会返回一个非透明的对象引用。返回的对象是通道实现修改阻塞模式时内部使用的。只有拥有此对象的锁的线程才能更改通道的阻塞模式。

下面分别介绍这 3 个通道


ServerSocketChannel

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

由于 ServerSocketChannel 没有 bind()方法,因此有必要取出对等的 socket 并使用它来绑定到一个端口以开始监听连接。我们也是使用对等 ServerSocket 的 API 来根据需要设置其他的 socket 选项。

同 java.net.ServerSocket 一样,ServerSocketChannel 也有 accept( )方法。一旦创建了一个 ServerSocketChannel 并用对等 socket 绑定了它,然后您就可以在其中一个上调用 accept()。

如果您选择在 ServerSocket 上调用 accept( )方法,那么它会同任何其他的 ServerSocket 表现一样的行为:总是阻塞并返回一个 java.net.Socket 对象。

如果您选择在 ServerSocketChannel 上调用 accept( )方法则会返回SocketChannel 类型的对象,返回的对象能够在非阻塞模式下运行。

换句话说:

ServerSocketChannel 的 accept()方法会返回 SocketChannel 类型对象,SocketChannel 可以在非阻塞模式下运行。

其它 Socket 的 accept()方法会阻塞返回一个 Socket 对象。

如果ServerSocketChannel 以非阻塞模式被调用,当没有传入连接在等待时,ServerSocketChannel.accept( )会立即返回 null。正是这种检查连接而不阻塞的能力,实现了可伸缩性并降低了复杂性。

可选择性也因此得到实现。我们可以使用一个选择器实例来注册 ServerSocketChannel 对象以实现新连接到达时自动通知的功能。


代码演示

    //测试ServerSocketChannel的使用
    public static void serverSocketChannel()
    {
        SocketChannel socketChannel=null;
        try {
            //创建缓冲区,初始化缓冲区里面的数据
            ByteBuffer byteBuffer=ByteBuffer.wrap("大忽悠".getBytes(StandardCharsets.UTF_8));
            //创建serverSocketChannel对象
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //绑定对应的端口号---先将一个ServerSocket关联到当前通道,然后给这个serverSocket对象绑定一个端口号
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            //设置为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //等待接收客户端的连接
            while(true)
            {
                //因为设置了非阻塞模式,没有客户端连接的时候,会立即返回null
                 socketChannel = serverSocketChannel.accept();
                if(socketChannel==null)
                {
                    System.out.println("等待客户端连接中....");
                    //睡2秒,等待客户端连接
                    Thread.sleep(2000);
                }
                else
                {
                    //获取和当前客户端建立连接的socket对象,并且这个socket对象和当前的通道关联
                    Socket socket = socketChannel.socket();
                    System.out.println("当前客户端["+socket.getRemoteSocketAddress()+"]");
                    //position指针置0,limit指针不变
                    byteBuffer.rewind();
                    //从缓冲区中读取数据,写入通道中
                    socketChannel.write(byteBuffer);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //关闭连接
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

ServerSocketChannel相关函数讲解

(1)打开 ServerSocketChannel

通过调用 ServerSocketChannel.open() 方法来打开 ServerSocketChannel.

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

(2)关闭 ServerSocketChannel

通过调用 ServerSocketChannel.close() 方法来关闭 ServerSocketChannel.

serverSocketChannel.close();

(3)监听新的连接

通过 ServerSocketChannel.accept() 方法监听新进的连接。当accept()方法返回时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。通常不会仅仅只监听一个连接,在 while 循环中调用 accept()方法. 如下面的例子:
在这里插入图片描述

非阻塞模式

ServerSocketChannel 可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是 null。 因此,需要检查返回的SocketChannel 是否是 null.如:

在这里插入图片描述


SocketChannel

Java NIO 中的 SocketChannel 是一个连接到 TCP 网络套接字的通道。

SocketChannel 是一种面向流连接sockets 套接字的可选择通道。从这里可以看出:

  • SocketChannel 是用来连接 Socket 套接字,即通过一个通道与之前的BIO中的Socket对象相关联
  • SocketChannel 主要用途用来处理网络 I/O 的通道
  • SocketChannel 是基于 TCP 连接传输
  • SocketChannel 实现了可选择通道,可以被多路复用的

SocketChannel 特征

  • (1)对于已经存在的 socket 不能创建 SocketChannel
  • (2)SocketChannel 中提供的 open 接口创建的 Channel 并没有进行网络级联,需要使 用 connect接口连接到指定地址
  • (3)未进行连接的 SocketChannle 执行 I/O 操作时,会抛出 NotYetConnectedException
  • (4)SocketChannel 支持两种 I/O 模式:阻塞式和非阻塞式
  • (5)SocketChannel 支持异步关闭。如果 SocketChannel 在一个线程上 read 阻塞,另 一个线程对该SocketChannel 调用 shutdownInput,则读阻塞的线程将返回-1 表示没有 读取任何数据;如果SocketChannel 在一个线程上 write 阻塞,另一个线程对该 SocketChannel 调用shutdownWrite,则写阻塞的线程将抛出 AsynchronousCloseException
  • (6)SocketChannel 支持设定参数
SO_SNDBUF 套接字发送缓冲区大小
SO_RCVBUF 套接字接收缓冲区大小
SO_KEEPALIVE 保活连接
O_REUSEADDR 复用地址
SO_LINGER 有数据传输时延缓关闭 Channel (只有在非阻塞模式下有用)
TCP_NODELAY 禁用 Nagle 算法

SocketChannel 的使用

(1)创建 SocketChannel

方式一:

SocketChannel socketChannel = SocketChannel.open(new 
InetSocketAddress("www.baidu.com", 80));

方式二:

SocketChannel socketChanne2 = SocketChannel.open();
socketChanne2.connect(new InetSocketAddress("www.baidu.com", 80));

直接使用有参 open api 或者使用无参 open api,但是在无参 open 只是创建了一个SocketChannel 对象,并没有进行实质的 tcp 连接。

(2)连接校验

socketChannel.isOpen(); // 测试 SocketChannel 是否为 open 状态
socketChannel.isConnected(); //测试 SocketChannel 是否已经被连接
socketChannel.isConnectionPending(); //测试 SocketChannel 是否正在进行
连接
socketChannel.finishConnect(); //校验正在进行套接字连接的 SocketChannel
是否已经完成连接

(3)读写模式

前面提到 SocketChannel 支持阻塞和非阻塞两种模式:

socketChannel.configureBlocking(false);

通过以上方法设置 SocketChannel 的读写模式。false 表示非阻塞,true 表示阻塞。

(4)读写

SocketChannel socketChannel = SocketChannel.open(
 new InetSocketAddress("www.baidu.com", 80));
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
socketChannel.close();
System.out.println("read over");

以上为阻塞式读,当执行到 read 出,线程将阻塞,控制台将无法打印 read over

SocketChannel socketChannel = SocketChannel.open(
 new InetSocketAddress("www.baidu.com", 80));
socketChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
socketChannel.close();
System.out.println("read over");

以上为非阻塞读,控制台将打印 read over
读写都是面向缓冲区,这个读写方式与前文中的 FileChannel 相同

(5)设置和获取参数

socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, 
Boolean.TRUE)
 .setOption(StandardSocketOptions.TCP_NODELAY, Boolean.TRUE);

通过 setOptions 方法可以设置 socket 套接字的相关参数

socketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);
socketChannel.getOption(StandardSocketOptions.SO_RCVBUF);

可以通过 getOption 获取相关参数的值。如默认的接收缓冲区大小是 8192byte。

SocketChannel 还支持多路复用,但是多路复用在后续内容中会介绍到。


ServerSocket,Socket与ServerSocketChannel,SocketChannel区别

  • (1)Socket 和ServerSocket 是一对 他们是java.net下面实现socket通信的类
  • (2) SocketChannel 和ServerSocketChannel是一对 他们是java.nio下面实现通信的类支持异步通信
  • (3)服务器必须先建立ServerSocket或者ServerSocketChannel 来等待客户端的连接
  • (4)客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接
  • (5)服务器接受到客户端的连接受,再生成一个Socket或者SocketChannel与此客户端通信

最后

以上就是独特大门最近收集整理的关于NIO学习之ServerSocketChannel和SocketChannelSocket 通道SocketChannel的全部内容,更多相关NIO学习之ServerSocketChannel和SocketChannelSocket内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(276)

评论列表共有 0 条评论

立即
投稿
返回
顶部