在上篇文章中对BIO网络编程的相关内容进行了讲解,经过咱们一步一步的优化,虽然咱们经过多线程解决了并发访问的问题,可是BIO自己的一些特性形成的问题却没有获得解决。java
BIO是阻塞IO,咱们使用线程来进行IO的调度,咱们没法肯定io是否就绪,可是每一个IO操做都会建立线程,这个时候若是IO未就绪,那么建立的线程也会处于阻塞状态。git
在以前讲解NIO基本知识的时候咱们提到过NIO经过通道选择器能够实现同时对多个通道的管理,实际上就是经过管理多个IO操做,换句话说是单线程处理多线程并发,有效的防止线程由于IO没有就绪而被挂起。github
在使用NIO进行网络编程的时候须要用到的就是通道选择器,因此咱们先看一下通道选择器的相关内容。编程
Java NIO中的 ServerSocketChannel 是一个能够监听新进来的TCP链接的通道, 就像标准IO中的ServerSocket同样。服务器
//代开ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
复制代码
经过 ServerSocketChannel.accept() 方法监听新进来的链接。当 accept()方法返回的时候,它返回一个包含新进来的链接的 SocketChannel。 能够设置成非阻塞模式。在非阻塞模式下,accept() 方法会马上返回,若是尚未新进来的链接,返回的将是null。网络
Java NIO中的SocketChannel是一个链接到TCP网络套接字的通道。能够经过如下2种方式建立SocketChannel:多线程
//打开一个SocketChannel并链接到互联网上的某台服务器。
socketChannel.connect(new InetSocketAddress("localhost",8888));
//一个新链接到达ServerSocketChannel时,会建立一个SocketChannel。
SocketChannel socketChannel = SocketChannel.open();
复制代码
在前言中提到过,NIO经过通道选择器能够实现同时对多个通道的管理,其实就是同时对多个IO操做的管理,也就是实现了单线程处理多线程并发问题。对于操做系统来讲,线程之间上下文切换的开销很大,并且每一个线程都要占用系统的一些资源(如内存)。所以太多的线程会耗费大量的资源,因此使用通道选择器来对多个通道进行管理。并发
1. 建立Selectorsocket
//经过调用Selector.open()方法建立一个Selector,以下:
Selector selector = Selector.open();
复制代码
2.将通道注册到通道选择器中优化
//为了将Channel和Selector配合使用,必须将channel注册到selector上。经过SelectableChannel.register()方法来实现
// 建立ServerSocketChanner
ServerSocketChannel ssc = ServerSocketChannel.open();
// 绑定端口号
ssc.bind(new InetSocketAddress(8888));
// 设置通道非阻塞
ssc.configureBlocking(false);
// 建立通道选择器
Selector selector = Selector.open();
// 将通道注册到通道选择器中 要求:通道都必须是非阻塞的 意味着不能将FileChannel与Selector一块儿使用,由于FileChannel不能切换到非阻塞模式
// 第二个参数是咱们须要通道选择器帮咱们管理什么事件类型 注册为接受就绪
ssc.register(selector, SelectionKey.OP_ACCEPT);
复制代码
register()方法的第二个参数表明注册的通道事件类型,一个通道触发一个事件就意味着该事件准备就绪了,总共有四种事件:Connect(链接就绪 ) Accept(接受就绪) Read(有数据可读的通道 读就绪) Write(写就绪)。对于选择器而言,能够针对性的找到(监听)的事件就绪的通道,进行相关的操做。
这四种事件用SelectionKey的四个常量来表示:
能够用“位或”操做符将常量链接起来
SelectionKey对象
对象中包含不少的属性,譬如:
3.选择器的select ()方法
select()方法返回的int值表示有多少通道已经就绪。自上次调用select()方法后有多少通道变成就绪状态。若是调用select()方法,由于有一个通道变成就绪状态,返回了1,若再次调用select()方法,若是另外一个通道就绪了,它会再次返回1。若是对第一个就绪的channel没有作任何操做,如今就有两个就绪的通道,但在每次select()方法调用之间,只会有一个通道就绪。
4.选择器的selectedKeys()方法
经过调用selector的selectedKeys()方法,能够获得就绪通道的集合。遍历集合能够找到本身须要的通道进行相关的操做。
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//处理nio事件
if(key.isAcceptable()){
// 获取通道
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = channel.accept();
// 注册读事件类型
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
// 读取数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = -1;
while (true){
byteBuffer.clear();
len = socketChannel.read(byteBuffer);
if(len == -1){
break;
}
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
bos.write(byteBuffer.get());
}
}
// 打印读取到的数据
System.out.println(bos.toString());
//写数据给客户端 注册事件类型 写事件
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
if(key.isWritable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
String msg = "你好,我是服务器";
ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.close();
}
iterator.remove();
}
复制代码
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws Exception{
// 建立ServerSocketChanner
ServerSocketChannel ssc = ServerSocketChannel.open();
// 绑定端口号
ssc.bind(new InetSocketAddress(8888));
// 设置通道非阻塞
ssc.configureBlocking(false);
// 建立通道选择器
Selector selector = Selector.open();
// 将通道注册到通道选择器中 要求:通道都必须是非阻塞的
// 第二个参数是咱们须要通道选择器帮咱们管理什么事件类型 注册为接受就绪
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 遍历通道选择器
while (true){
System.out.println("我在8888等你......");
// 返回准备就绪的通道数量
int nums = selector.select();
// 若是数量小于1说明没有通道准备就绪 跳过本次循环
if(nums<1) {continue;}
// 获取全部的keys(通道 事件类型)
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//处理nio事件
if(key.isAcceptable()){
// 获取通道
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = channel.accept();
// 注册读事件类型
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
// 读取数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = -1;
while (true){
byteBuffer.clear();
len = socketChannel.read(byteBuffer);
if(len == -1){
break;
}
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
bos.write(byteBuffer.get());
}
}
// 打印读取到的数据
System.out.println(bos.toString());
//写数据给客户端 注册事件类型 写事件
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
if(key.isWritable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
String msg = "你好,我是服务器";
ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.close();
}
iterator.remove();
}
}
}
}
复制代码
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOCleint {
public static void main(String[] args) throws IOException {
// 建立sc
SocketChannel socketChannel = SocketChannel.open();
// 链接服务器
socketChannel.connect(new InetSocketAddress("localhost",8888));
// 写数据给服务器
String msg = "你好,我是客户端";
ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
// 关闭输出流
socketChannel.shutdownOutput();
// 读取数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream bosread = new ByteArrayOutputStream();
int len = -1;
while (true){
readBuffer.clear();
len = socketChannel.read(readBuffer);
if(len == -1){
break;
}
readBuffer.flip();
while (readBuffer.hasRemaining()){
bosread.write(readBuffer.get());
}
}
System.out.println("我收到:"+bosread.toString());
socketChannel.close();
}
}
复制代码
我不能保证每个地方都是对的,可是能够保证每一句话,每一行代码都是通过推敲和斟酌的。但愿每一篇文章背后都是本身追求纯粹技术人生的态度。
永远相信美好的事情即将发生。