1,NIO与BIO的区别
BIO:传统的同步阻塞模型BIO是通过Socket和ServerSocket实现的,ServerSocket监听端口,Socket进行连接。
这种情况不适合处理多个请求:
1,生成较多的Socket会消耗过多的本地资源,
2,Socket连接的速度比较慢,
3,BIO一般都是采取accpet获取Socket后,给一个请求分配一个线程,不管连接是否有真正的数据请求,都需要开辟一个 新的线程,开辟过多线程会导致效率低下,栈溢出 OutOfMemory异常等。
NIO:JDK1.4引入的一种新型IO。对BIO的一种改进,基于Reactor模型。一个Socket连接其实只有在一小部分情况下才会发生数据 传输IO操作,大部分时间都是空闲的,但是还是占着线程。NIO作出的改进是在连接到服务端的众多Socket中,只有需要进行IO操作的才能获取服务端的处理线程进行IO。客户端的Socket在连接到服务端时就会在事件分离器注册一个IO请求事件和IO 事件处理器。在该系统的连接发生IO请求时,IO事件处理器就会启动一个线程来处理这个IO请求,不断尝试获取系统的IO使用权限,一旦成功呢,则通知这个Socket进行IO数据传输
2,Java程序实现BIO与NIO
BIOServer
package 网络编程;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* 类说明
* 描述:TODO
*@author wj
*@date 2018年10月19日
*/
public class ServerSocketTest2 {
public static void main(String[] args) throws Exception {
int port = 6667;
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务器开启,等待连接...");
while(true){
Socket socket =serverSocket.accept();
new Thread(new TaskSocket(socket)).start();
}
}
}
class TaskSocket implements Runnable{
Socket client;
public TaskSocket(Socket client) {
// TODO Auto-generated constructor stub
this.client = client;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
Scanner scanner = new Scanner(client.getInputStream());
OutputStream outputStream = client.getOutputStream();
while(scanner.hasNext()){
String s = scanner.nextLine();
System.out.println("客户端"+client.getPort()+"发来了"+s);
outputStream.write("你好啊\n".getBytes());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
BIOClient
package 网络编程;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class ClientSocketTest2 {
public static void main(String[] args) throws Exception {
final int port = 6667;
final String host = "127.0.0.1";
for(int i = 0;i < 3;i++) {
new Thread( new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Socket socket = new Socket(host, port);
System.out.println("Cliect[port:" + socket.getLocalPort() + "] 哈哈哈哈..");
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(is);
BufferedReader reader1 = new BufferedReader(reader);
os.write(("你好啊"+socket+Thread.currentThread().getName()+"\n").getBytes());
os.flush();
System.out.println("Cliect[port:" + socket.getLocalPort() + "] 哈哈哈");
System.out.println("server recv:"+ reader1.readLine());
System.out.println("client send info....");
os.close();
is.close();
reader1.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
}
NIOServer
package 网络编程;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 类说明
* 描述:TODO
*@author wj
*@date 2018年10月19日
*/
public class NIOServer {
/**
* 主线程选择器
*/
public static Selector selector = null;
/**
* 一个ServerSocketChannel实例
*/
public static ServerSocketChannel channel;
/**
* 线程池
*/
static ExecutorService executorService = Executors.newFixedThreadPool(4);
public static void main(String[] args) throws IOException{
/**
* 端口号
*/
int port = 8989;
/**
* 初始化
*/
channel = ServerSocketChannel.open();
channel.bind(new InetSocketAddress(port));
selector = Selector.open();
channel.register(selector,SelectionKey.OP_ACCEPT);
}
public void start() throws IOException{
while(selector.select() > 0){
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isValid() && key.isAcceptable()) {
SocketChannel socketchannel = (SocketChannel)key.channel();
socketchannel = channel.accept();
SocketAddress address = socketchannel.getRemoteAddress();
System.out.println("客户端:"+address +" 已上线");
Task task = new Task(socketchannel);
executorService.execute(task);
}
}
}
}
}
class Task implements Runnable{
/**
* 子线程选择器
*/
private Selector selector;
/**
* SocketChannel实例
*/
private SocketChannel channel;
private ByteBuffer buffer;
public Selector getSelector() {
return selector;
}
public void setSelector(Selector selector) {
this.selector = selector;
}
public Task(SocketChannel channel) {
// TODO Auto-generated constructor stub
this.channel = channel;
try {
channel.configureBlocking(false);
selector = Selector.open();
buffer = ByteBuffer.allocate(1024);
channel.register(selector,SelectionKey.OP_READ);
SocketAddress address = channel.getLocalAddress();
System.out.println("当前线程"+Thread.currentThread().getName()+"处理"+address+"的请求");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while(selector.select() > 0){
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
NIOClient
package 网络编程;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClient {
public static void main(String[] args) throws Exception{
for(int i = 0;i < 2;i++) {
new Thread() {
public void run(){
/**
* 1创建SocketChannel 实例
*/
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
/**
* 连接服务器
*/
socketChannel.connect(new InetSocketAddress("127.0.0.1",8989));
/**
* 写数据
*/
String msg = "我是客户端!!!";
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.shutdownOutput();
/**
* 读数据
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
while(true) {
byteBuffer.clear();
len = socketChannel.read(byteBuffer);
if(len == -1) {
break;
}
byteBuffer.flip();
while(byteBuffer.hasRemaining()){
bos.write(byteBuffer.get());
}
System.out.println("服务端说:"+new String(bos.toByteArray()));
socketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
3,NIO核心部分——Buffer
Buffer是一个缓冲区。它与通道紧密联系。通道是I/O传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。数据从Channel读到Buffer中,也可以从Buffer写到Channel中。所有数据的读写都是通过Buffer进行的。
这是BUffer类的家谱
接下来我们来了解一下这个类
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
容量(Capacity):缓冲区能够容纳的数据元素最大数量。这一容量在缓冲区创建时被设定,并且不能被改变。
上界(Limit):缓冲区的第一个不能别读或写的元素。
位置(position):下一个要被读或写的元素的索引。位置会自动由get()、put()方法更新。
标记(Mark):一个备忘位置。调用mark()来设定 mark=postion 调用reset()来设定postion=mark
我们,来创建一个Buffer对象
使用调试
我们可以看到创建后的Buffer对象,capality(容量)为最大值100.limit也为100,pos为0.
接下来我们向里面存放数据
再次调试
可以看出pos指向了5,也就是说向里添加数据,pos会增加。
我们现在
我们知道buffer即可以读也可以写,那么它是怎样做到的呢?关键方法——filp()
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
将position置为0,limit界限置为position,于是开始从头读取数据。
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
clear方法将这些属性还原至刚开始创建的样子,它并没有删除里面的数据,只是在写的时候会覆盖原来的数据。
mark()方法标记位置,reset()方法恢复位置。
在NIO网络编程时,通常使用channel和buffer。
channel.read(buffer):从channel写入数据到buffer。
channel.write(buffer):从buffer读入数据到channel。
4,NIO核心部分——Channel
channel就是通道,类似于BIO中的stream(流)。
channel和流的区别
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的;
- 通道可以异步地读写;
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入.
Channel的主要实现类:
1,FileChannel:从文件中读写数据。FileChannel无法设置为非阻塞模式
2,DatagramChannel:能通过UDP读写网络中的数据
3,SocketChannel:能通过TCP读写网络中的数据
4,ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器一样对于每一个新进来的连接都会创建一个SocketChannel
5,NIO核心部分——Selector
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。管理着一个被注册的通道集合的信息和他们的就绪状态。通过是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
每一个Selector对象维护三个键的集合:
1,已注册的键的集合(Registered key set)
2,已选择的键的集合(Selected key set)
3,已取消的键的集合(Cancelled key set)
打开selector,将channel注册到选择器上。
SelectionKey:选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回一个表示这种病注册关系的标记。选择键包含了两个 比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。
一个selectionKey对象包含两个以整数形式进行编码的比特掩码:一个用于指示哪些通道/选择器组合体所关心的操作(interest集合),另一个表示通道准备好要执行的操作(ready集合)。当前的interest集合可以通过调用interestOps()方法来获取。最初,这应该是通道被注册时传进来的值。
选择过程:(来源:《Java NIO》)