java nio 实例_Java NIO(二)NIO入门实例

一 Java 源生api的核心概念

1.1 Channel

Channel:通道,BIO模型中使用流来传输数据,在NIO中使用Channel来传输数据,它是双向的,一个Channel即可以读也可以写(BIO中流是单向的,所以分了InputStream和OutputStream)。

网络编程中用到的Channel只有ServerSocketChannel和SocketChannel,可以类比于ServerSocket和Socket,一个在服务端使用,一个在客户端使用。

1.2 Buffer

在BIO中,可以将数据直接写入到流当中,但是在NIO中,数据只能写入到缓冲区中,Buffer就是缓冲区。

一个Buffer其实就是一个字节数组,最常用的Buffer是ByteBuffer。

1.3 Selector

在文章Java NIO(一)select 和 epoll底层实现原理中,我们提到select/epoll函数可以同时监听多个socket,在socket准备就绪后会返回。

Java中使用Selector对象完成select/epoll函数的功能,Selector可以监控多个Channel,当某一个Channel有数据可以读取时,Selector会把它选取出来,然后交给线程处理。

二 使用NIO构建一个简单的服务器

基本的步骤如下:

创建一个ServerSocketChannel对象,绑定端口并配置成非阻塞模式。

创建一个Selector,并把第一步创建的ServerSocketChannel交给Selector监听。

不停的从Selector获取准备就绪的Channel,当有客户端连接时,ServerSocketChannel就会被选取出来。

从ServerSocketChannel获取SocketChannel对象,这个对象代表了客户端,需要使用它来与客户端读写数据。

把SocketChannel也交给Selector监听,当SocketChannel可以读取数据时,也会被选取出来

SocketChannel被选取出来后,从中读取数据解析出请求,并写入返回数据。

代码如下:

public class NioServerTest {

public static void main(String[] args) throws Exception{

//创建一个ServerSocketChannel对象,绑定端口并配置成非阻塞模式。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.bind(new InetSocketAddress(8888), 1024);

//下面这句必需要,否则ServerSocketChannel会使用阻塞的模式,那就不是NIO了

serverSocketChannel.configureBlocking(false);

//把ServerSocketChannel交给Selector监听

Selector selector = Selector.open();

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

//循环,不断的从Selector中获取准备就绪的Channel,最开始的时候Selector只监听了一个ServerSocketChannel

//但是后续有客户端连接时,会把客户端对应的Channel也交给Selector对象

while (true) {

//这一步会阻塞,当有Channel准备就绪时或者超过1000秒后会返回。

selector.select(1000);

//获取所有的准备就绪的Channel,SelectionKey中包含中Channel信息

Set selectionKeySet = selector.selectedKeys();

//遍历,每个Channel都可处理

for (SelectionKey selectionKey : selectionKeySet) {

//如果Channel已经无效了,则跳过(如Channel已经关闭了)

if(!selectionKey.isValid()) {

continue;

}

//判断Channel具体的就绪事件,如果是有客户端连接,则建立连接

if (selectionKey.isAcceptable()) {

acceptConnection(selectionKey, selector);

}

//如果有客户端可以读取请求了,则读取请求然后返回数据

if (selectionKey.isReadable()) {

System.out.println(readFromSelectionKey(selectionKey));

}

}

//处理完成后把返回的Set清空,如果不清空下次还会再返回这些Key,导致重复处理

selectionKeySet.clear();

}

}

//客户端建立连接的方法

private static void acceptConnection(SelectionKey selectionKey, Selector selector) throws Exception{

System.err.println("accept connection...");

//SelectionKey中包含选取出来的Channel的信息,我们可以从中获取,对于建立连接来说,只会有ServerSocketChannel可能触发,

//因此这里可以把它转成ServerSocketChannel对象

ServerSocketChannel ssc = ((ServerSocketChannel) selectionKey.channel());

//获取客户端对应的SocketChannel,也需要配置成非阻塞模式

SocketChannel socketChannel = ssc.accept();

socketChannel.configureBlocking(false);

//把客户端的Channel交给Selector监控,之后如果有数据可以读取时,会被select出来

socketChannel.register(selector, SelectionKey.OP_READ);

}

//从客户端读取数据的庐江

private static String readFromSelectionKey(SelectionKey selectionKey) throws Exception{

//从SelectionKey中包含选取出来的Channel的信息把Channel获取出来

SocketChannel socketChannel = ((SocketChannel) selectionKey.channel());

//读取数据到ByteBuffer中

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

int len = socketChannel.read(byteBuffer);

//如果读到-1,说明数据已经传输完成了,可以并闭

if (len < 0) {

socketChannel.close();

selectionKey.cancel();

return "";

} else if(len == 0) { //什么都没读到

return "";

}

byteBuffer.flip();

doWrite(selectionKey, "Hello Nio");

return new String(byteBuffer.array(), 0, len);

}

private static void doWrite(SelectionKey selectionKey, String responseMessage) throws Exception{

System.err.println("Output message...");

SocketChannel socketChannel = ((SocketChannel) selectionKey.channel());

ByteBuffer byteBuffer = ByteBuffer.allocate(responseMessage.getBytes().length);

byteBuffer.put(responseMessage.getBytes());

byteBuffer.flip();

socketChannel.write(byteBuffer);

}

}

咋一看,有点多,其实很简单,两分钟就能看完了。另外,这只是个简单的示例代码,没有处理异常,关闭Channel和连接等操作,也没有考虑并发问题。

使用浏览器访问http://localhost:8888/,可以在控制台上看到请求头信息就说明服务器搭建成功了(浏览器中是不会看到信息的,因为我们返回的数据就不是一个符合HTTP协议的数据)

ca2d92f5b4ce

image.png

把Channel交给Selector对象的代码中,有一个参数:

socketChannel.register(selector, SelectionKey.OP_READ);

这个参数表明了关注的内容,比如ServerSocketChannel,我们关注它什么时候需要建立连接,而SocketChannel我们关注它什么时候可以读写数据。

三 构建NIO的简单客户端

创建客户端的方式与服务端类似,这里不再重复,把步骤放在代码的注释中了。

public class NioClient {

public static void main(String[] args) throws Exception{

//创建一个SocketChannel对象,配置成非阻塞模式

SocketChannel socketChannel = SocketChannel.open();

socketChannel.configureBlocking(false);

//创建一个选择器,并把SocketChannel交给selector对象

Selector selector = Selector.open();

socketChannel.register(selector, SelectionKey.OP_CONNECT);

//发起建立连接的请求,这里会立即返回,当连接建立完成后,SocketChannel就会被选取出来

socketChannel.connect(new InetSocketAddress("localhost", 8888));

//遍历,不段的从Selector中选取出已经就绪的Channel,在这个例子中,Selector只监控了一个SocketChannel

while (true) {

selector.select(1000);

Set selectionKeySet = selector.selectedKeys();

for (SelectionKey selectionKey : selectionKeySet) {

if(!selectionKey.isValid()) {

continue;

}

//连接建立完成后的操作:直接发送请求数据

if (selectionKey.isConnectable()) {

if(socketChannel.finishConnect()) {

socketChannel.register(selector, SelectionKey.OP_READ);

doWriteRequest(((SocketChannel) selectionKey.channel()));

}

}

//如果当前已经可以读数据了,说明服务端已经响应完了,读取数据

if (selectionKey.isReadable()) {

doRead(selectionKey);

}

}

//最后同样要清除所有的Key

selectionKeySet.removeAll(selectionKeySet);

}

}

//发送请求

private static void doWriteRequest(SocketChannel socketChannel) throws Exception{

System.err.println("start connect...");

//创建ByteBuffer对象,会放入数据

ByteBuffer byteBuffer = ByteBuffer.allocate("Hello Server!".getBytes().length);

byteBuffer.put("Hello Server!".getBytes());

byteBuffer.flip();

//写数据

socketChannel.write(byteBuffer);

if(!byteBuffer.hasRemaining()) {

System.err.println("Send request success...");

}

}

//读取服务端的响应

private static void doRead(SelectionKey selectionKey) throws Exception{

SocketChannel socketChannel = ((SocketChannel) selectionKey.channel());

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

int len = socketChannel.read(byteBuffer);

System.out.println("Recv:" + new String(byteBuffer.array(), 0 ,len));

}

}

先启动服务端,再启动客户端,可以在客户端看到服务端返回的数据:

ca2d92f5b4ce

客户端打印信息

也可以在服务端看到客户端的请求数据:

ca2d92f5b4ce

服务端打印信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值