传输
流经网络的数据总是具有相同的类型:字节。这些字节是如何流动的主要取决于我们所说的 网络传输—一个帮助我们抽象底层数据传输机制的概念。用户并不关心这些细节;他们只想确 保他们的字节被可靠地发送和接收。——————>字符流构建在字节流基础之上(通常还要传入一个字符集编码作为参数),为方便读取“文本文件”而设计的。字符流专门用于读取文本文件。字符更加的全面。
从阻塞传输切换到非阻塞传输,那么你可能会因为这两种网络 API 的截然不同而遇到问题————>Socket转化为NioSocket
不使用BIO以及NIO
阻塞版demo:
import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.Charset; public class PlainOioServer { public void serve(int port) throws IOException { //将服务器绑定到指定端口 final ServerSocket socket = new ServerSocket (port); try { for (;;) { //接受连接 final Socket clientSocket = socket.accept(); System.out.println( "Accepted connection from " + clientSocket); //创建一个新的线程来处理该连接 new Thread(new Runnable() { @Override public void run() { OutputStream out; try { out = clientSocket.getOutputStream(); out.write("Hi!\r\n".getBytes( Charset.forName("UTF-8"))); out.flush(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } finally { try { clientSocket.close(); } catch (IOException ex) { } } } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
非阻塞版demo:
import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class PlainNioServer { public void serve(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); ServerSocket ssocket = serverChannel.socket(); //绑定特定端口 InetSocketAddress address = new InetSocketAddress(port); ssocket.bind(address); //打开轮训器来处理Channel Selector selector = Selector.open(); //指定的Socket挂在到selector上 serverChannel.register(selector, SelectionKey.OP_ACCEPT); final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); for (;;) { try { //等待需要处理的新事件;阻塞将一直持续到下一个传入事件 selector.select(); } catch (IOException ex) { ex.printStackTrace(); break; } //获取所有接收事件的SelectionKey 实例 Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); try { //检查事件是否是一个新的已经就绪可以被接受的连接 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate()); System.out.println( "Accepted connection from " + client); } if (key.isWritable()) { SocketChannel client = (SocketChannel)key.channel(); ByteBuffer buffer = (ByteBuffer)key.attachment(); while (buffer.hasRemaining()) { if (client.write(buffer) == 0) { break; } } client.close(); } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { } } } } } }
使用Netty版demo:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.oio.OioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.oio.OioServerSocketChannel; import java.net.InetSocketAddress; /** * description: EchoServer * date: 2021/4/21 17:57 * * @author: SmartCat * version: 1.0.0 */ public class EchoServer { private void start() throws InterruptedException { EchoServerHandler echoServerHandler = new EchoServerHandler (); NioEventLoopGroup group = new NioEventLoopGroup (); try{ ServerBootstrap serverBootstrap = new ServerBootstrap (); serverBootstrap.group(group,group) .channel (OioServerSocketChannel.class) .localAddress (new InetSocketAddress (8088)) .childHandler (new ChannelInitializer<SocketChannel> () { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline ().addLast (echoServerHandler); // socketChannel.pipeline ().addLast (new EchoClientHandler ()); System.out.println ("已经添加完毕"); } }); ChannelFuture f = serverBootstrap.bind ().sync (); f.channel ().closeFuture ().sync (); } catch (InterruptedException e) { e.printStackTrace (); } finally { group.shutdownGracefully ().sync (); } }
传输 API 的核心是 interface Channel,它被用于所有的 I/O 操作。每个 Channel 都将会被分配一个 ChannelPipeline 和 ChannelConfig。 ChannelConfig 包含了该 Channel 的所有配置设置,并且支持热更新。由于特定的传输可能 具有独特的设置,所以它可能会实现一个 ChannelConfig 的子类型。
为什么会继承Cpmarable。由于 Channel 是独一无二的,所以为了保证顺序将 Channel 声明为 java.lang. Comparable 的一个子接口。因此,如果两个不同的 Channel 实例都返回了相同的散列码,那 么 AbstractChannel 中的 compareTo()方法的实现将会抛出一个 Error。ChannelPipeline 持有所有将应用于入站和出站数据以及事件的 ChannelHandler 实 例,这些 ChannelHandler 实现了应用程序用于处理状态变化以及数据处理的逻辑。
每个 Channel 都将会被分配一个 ChannelPipeline 和 ChannelConfig。 ChannelConfig包含了该 Channel的所有配置设置,并且支持热更新。由于特定的传输可能 具有独特的设置,所以它可能会实现一个 ChannelConfig的子类型。ChannelPipeline 持有所有将应用于入站和出站数据以及事件的 ChannelHandler 实 例,这些 ChannelHandler实现了应用程序用于处理状态变化以及数据处理的逻辑。
ChannelHandler的典型用途包括:将数据从一种格式转换为另一种格式;提供异常的通知;提供 Channel变为活动的或者非活动的通知;提供当 Channel注册到 EventLoop或者从 EventLoop注销时的通知;提供有关用户自定义事件的通知。
你也可以根据需要通过添加或者移除ChannelHandler实例来修改ChannelPipeline。
Netty 的 Channel实现是线程安全的,因此你可以存储一个到 Channel的引用,并且每当 你需要向远程节点写数据时,都可以使用它,即使当时许多线程都在使用它。
Netty的NIO的实现也是依赖JDK1.4时便可用的一个基于选择器的API,选择器背后的逻辑是充当一个注册表,在拿了你可以将请求在Chanel的状态发送变化时得到新通知。之前说过Channel等同于Socket的概念。
可能的状态变化有:新的channel已被接收且就绪;Channel连接已经完成;Channel有已经就绪的可供读取的数据;Channel可用于写数据
选择器运行在一个检查状态变化并对其做出相应响应的线程上,在应用程序对状态的改变做 出响应之后,选择器将会被重置,并将重复这个过程。
用于 JVM 内部通信的 Local 传输
Netty 提供了一个 Local 传输,用于在同一个 JVM 中运行的客户端和服务器程序之间的异步 通信。同样,这个传输也支持对于所有 Netty 传输实现都共同的 API。在这个传输中,和服务器 Channel相关联的 SocketAddress并没有绑定物理网络地址;只要服务器还在运行,它就会被存储在注册表里,并在 Channel关闭时注销。因为这个 传输并不接受真正的网络流量,所以它并不能够和其他传输实现进行互操作。因此,客户端希望 连接到(在同一个 JVM 中)使用了这个传输的服务器端时也必须使用它。除了这个限制,它的 使用方式和其他的传输一模一样。