Netty入门到精通二(转)

在第一篇里面我们了解了传统IO,这篇我们就来介绍一下Nio吧,废话就不多说了,来看看代码吧

import java.io.IOException;

import java.net.InetSocketAddress;

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;

 

/**

 * NIO服务端

 *

 * @author -Jimmy_zjf888-

 */

public class NIOServer {

// 通道管理器

private Selector selector;

 

/**

 * 获得一个ServerSocket通道,并对该通道做一些初始化的工作

 * @param port绑定的端口号

 * @throws IOException

 */

public void initServer(int port) throws IOException {

// 获得一个ServerSocket通道

ServerSocketChannel serverChannel = ServerSocketChannel.open();

// 设置通道为非阻塞

serverChannel.configureBlocking(false);

// 将该通道对应的ServerSocket绑定到port端口

serverChannel.socket().bind(new InetSocketAddress(port));

// 获得一个通道管理器

this.selector = Selector.open();

// 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,

// 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

}

 

/**

 * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理

 *

 * @throws IOException

 */

public void listen() throws IOException {

System.out.println("服务端启动成功!");

// 轮询访问selector

while (true) {

// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞

selector.select();

// 获得selector中选中的项的迭代器,选中的项为注册的事件

Iterator<?> ite = this.selector.selectedKeys().iterator();

while (ite.hasNext()) {

SelectionKey key = (SelectionKey) ite.next();

// 删除已选的key,以防重复处理

ite.remove();

 

handler(key);

}

}

}

 

/**

 * 处理请求

 *

 * @param key

 * @throws IOException

 */

public void handler(SelectionKey key) throws IOException {

 

// 客户端请求连接事件

if (key.isAcceptable()) {

handlerAccept(key);

// 获得了可读的事件

else if (key.isReadable()) {

handelerRead(key);

}

}

 

/**

 * 处理连接请求

 *

 * @param key

 * @throws IOException

 */

public void handlerAccept(SelectionKey key) throws IOException {

ServerSocketChannel server = (ServerSocketChannel) key.channel();

// 获得和客户端连接的通道

SocketChannel channel = server.accept();

// 设置成非阻塞

channel.configureBlocking(false);

 

// 在这里可以给客户端发送信息哦

System.out.println("新的客户端连接");

// 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。

channel.register(this.selector, SelectionKey.OP_READ);

}

 

/**

 * 处理读的事件

 *

 * @param key

 * @throws IOException

 */

public void handelerRead(SelectionKey key) throws IOException {

// 服务器可读取消息:得到事件发生的Socket通道

SocketChannel channel = (SocketChannel) key.channel();

// 创建读取的缓冲区

ByteBuffer buffer = ByteBuffer.allocate(1024);

int read = channel.read(buffer);

if(read > 0){

byte[] data = buffer.array();

String msg = new String(data).trim();

System.out.println("服务端收到信息:" + msg);

 

//回写数据

ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());

channel.write(outBuffer);// 将消息回送给客户端

}else{

System.out.println("客户端关闭");

key.cancel();

}

}

 

/**

 * 启动服务端测试

 *

 * @throws IOException

 */

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

NIOServer server = new NIOServer();

server.initServer(8888);

server.listen();

}

 

}

 

 

 

运行上述代码,发现selector.select();这里是一个阻塞点

 

OK,我们看看能否正常运行吧,做程序就是这样,跑一下测试一下,这里我们测试的代码运行一下也不会怎么样,为了学习嘛


 

OK,我们看看能否正常运行吧,做程序就是这样,跑一下测试一下,这里我们测试的代码运行一下也不会怎么样,为了学习嘛

 

学到这里我们总结总结一下吧,传统IO单线程情况下只能有一个客户端,多线程的情况下可以有多个客户端,但非常消耗内存,但NIO就不一样了,他是全能的,一个线程就可以接入多个客户端,为了解释清楚笔者就用如下图片来描述传统IONIO的区别吧

 

如图所示,整个办公室就是我们的系统,ServerSocket就是我们的前台大厅,他监听着端口,

看看有没有客人来,客人我们就理解为socket吧,传统的多线程是不是就是需要多个线程给多个客户端服务啊,要是公司招那么多前台妹子是不是要花一笔不小的开支啊,虽然乐坏了我们这些程序猿了,但对公司来说就是一笔不小的开支,如图所示公司就那么大,人太多都装不下了,连前台妹子都被挤到外面去了( ><) ,那么介绍完传统IO以后,我们来介绍介绍我们的必杀技NIO吧,如图所示。

 

   如上图所示,整个办公室就是我们的系统,ServerSocketChannel就是我们的前台大厅,他监听着端口,看看有没有客人来,客人我们就理解为socket吧,与传统IO不同的是,咱们公司的妹子(线程池)不需要那么多了,这次我们的前台经过特殊培训,她已经不是一般能力的前台妹子了(神马妹子都升级了?能力惊人的逆天),她比较牛能有同时服务多个客人的能力,同时她还可以监听前台大厅,看看有没有新的客人进来,那么她是怎么欢迎新来的可能呢?

//首先我们先让她知道前台大厅在哪

ServerSocketChannel serverChannel = ServerSocketChannel.open();

// 设置通道为非阻塞的,当然也只能设为非阻塞的,设为true会抛异常的

serverChannel.configureBlocking(false);

// 获得一个通道管理器

this.selector = Selector.open();

// 讲大厅交给一个selector,这个selector我们就想象成一个前台妹子吧,SelectionKey我们就理解为一个标记吧

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

通过上述相信你们应该NIO有所了解了吧,是不是比对着电子书看有意思多了,有意思给个赞把,对了笔者讲讲代码中的几个关键点吧,也是在学习过程中发现的,当然如上代码已经进行bug修复

1.当关闭客户端窗口的时候会报错?为什么会报错

答:那是因为客户端关闭的时候我们还在给它写数据,解决方案:

int read = channel.read(buffer);

if(read > 0){

byte[] data = buffer.array();

String msg = new String(data).trim();

System.out.println("服务端收到信息:" + msg);

 

//回写数据

ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());

channel.write(outBuffer);// 将消息回送给客户端

}else{

System.out.println("客户端关闭");

key.cancel();

}

2.我们发现selector.select();这里是一个阻塞点,那么为什么说NIO是非阻塞的?

答:这里我们所说的阻塞并不是select阻塞,而是channel.read(buffer)的时候是否阻塞,而传统IO是会阻塞在那里的,当然我们这里的select可以说是阻塞的,也可以说是非阻塞的,为什么这么说呢?其实select也有其他方法

1).selector.select(timeOut);这里有个带毫秒值的方法,当然这里阻塞timeOut毫秒后会返回,只不过返回0而已

2).selector.wakeup();也可以唤醒selector(官方描述:如果当前的select是阻塞的,用.select()或者select(timeOut),这个方法调用后这些方法都会立马返回结果值),当然在后面讲解Netty源码的时候我会讲解该方法,让大家更好的去理解,这里就不多说了。

3.key.OP_WRITE事件神马时候注册呢?

答:其实真正写的时候很少用,其实WRITE主要描述底层缓存区是否有空间,当然正常的时候缓冲区都是存在足够的空间的,如果存在空间就返回true

猜你喜欢

转载自longzhun.iteye.com/blog/2396448