在看这篇文章之前,可以先去看看我博客中另一篇关于同步与异步、阻塞与非阻塞的理解
Java标准IO(BIO)
BIO全称Blocking IO又叫做同步阻塞IO,它存在如下特点:
- 面向流
- 同步
- 阻塞
package com.xdong.bio.client;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class BioClient {
public static void main(String[] args) {
Socket client = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
client = new Socket("127.0.0.1", 8888);
inputStream = client.getInputStream();
outputStream = client.getOutputStream();
outputStream.write("Hello Server I am conneting you!".getBytes());
outputStream.flush();
byte[] bytes = new byte[1024];
while (inputStream.read(bytes) > 0) {//这里必须一次读完才能进行下次读操作,否则下一次会将上一次的
String msg = new String(bytes).trim();
System.out.println("client receive the message of server:" + msg);
outputStream.write("client get the message !".getBytes());
outputStream.flush();
Thread.sleep(1*1000);
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
package com.xdong.bio.server;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BioServer {
private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
try {
server = new ServerSocket(8888);
while (!Thread.interrupted()) {
socket = server.accept();
THREAD_POOL.submit(new SocketHandler(socket));
}
} catch (Exception e) {
// TODO: handle exception
}
}
static class SocketHandler implements Runnable{
private Socket socket;
public SocketHandler(Socket socket) {
super();
this.socket = socket;
}
@Override
public void run() {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
byte[] bs = new byte[1024];
while (true) {
int available = inputStream.available();// 输入流中有多少可用字节
inputStream.read(bs, 0, available);// 从输出流中将数据写入字节数组(注:如果字节数组原本有100个字节,本次读取50个字节,那么它只会覆盖前面50个字节数据,字节数组中还是有100个字节数据)
String receive = new String(bs, 0,available).trim();
System.out.println("server receive the message of client:" + receive);
outputStream.write("server get the message!".getBytes());//write函数-阻塞
outputStream.flush();
Thread.sleep(1*1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
简单分析一下上面代码:
- 绑定一个端口,产生一个SocketServer对象,并调用accept()方法,监听这个端口,接收客户端连接。
- 当有客户端接入后,服务端单独起一个线程,利用socket与客户端交互。
- 利用socket.getInputStream流接收客户端发送的消息,利用socket.getOutputStream流发送消息给客户端。
- 注意:server.accept、inpustream.read、outputstream.write都会阻塞线程。
- 同步体现在服务端与客户端的交互只能一步步顺序进行。阻塞主要体现在第4点中的几个方法。即使采用了多线程技术,实现了主线程非阻塞,但是子线程中与客户端交互依旧是阻塞的。
Java NIO框架
NIO,全称是Non-Blocking IO或New IO,解释就是非阻塞IO和新版IO。下面将给出三份不同的Server端代码,分别表示为:单Selector-单线程模式、单Selector-多线程模式、多Selector-多线程模式。
NIO的特性如下:
- 面向缓冲区
- 同步
- 非阻塞
package com.xdong.nio.v1.server;
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;
/**
* v1=单线程、单reactor
* @author xiongchaoqun
* @date 2018年7月2日
*/
public class NioServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;// 声明服务端Channel
try {
serverSocketChannel = ServerSocketChannel.open();// 实例化
serverSocketChannel.bind(new InetSocketAddress(1234));// 绑定端口
Selector selector = Selector.open();// 实例化一个Selector
serverSocketChannel.configureBlocking(false);//设置非阻塞,才能进行下面的register
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 注册可接受连接监听
while (selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();// 如果不移除,那么下次遍历还会处理这个selectionKey
if (key.isAcceptable()) {
handleAcceptEvent(selector,key);
} else if (key.isReadable()) {
handleReadEvent(selector,key);
}
Thread.sleep(1000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理客户端接入事件
*
* @param key
* @throws IOException
*/
private static void handleAcceptEvent(Selector selector,SelectionKey key) throws IOException {
SocketChannel client = null;// 客户端
ServerSocketChannel server = (ServerSocketChannel) key.channel();// 服务端
try {
client = server.accept();// 客户端接入产生一个SocketChannel
System.out.println("server receive a connet request of client!");
if (client == null) {
return;
}
client.configureBlocking(false);// 设置客户端通道为非阻塞
client.register(selector, SelectionKey.OP_READ);//一般不会主动设置SelectionKey.OP_WRITE,因为缓冲区会一直处于可写状态,无限触发select()
} catch (Exception e) {
server.close();
}
}
/**
* 处理读监听事件
* @param key
* @throws IOException
*/
private static void handleReadEvent(Selector selector,SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
client.read(readBuffer);
byte[] data = readBuffer.array();
String msg = new String(data).trim();//消除空格
System.out.println("server receive the client msg:" + msg);
String outMsg = "I am the server!";
ByteBuffer writeBuffer = ByteBuffer.wrap(outMsg.getBytes());
client.write(writeBuffer);
}
}
package com.xdong.nio.v2.server;
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;
/**
* v2=线程池、单reactor
* @author xiongchaoqun
* @date 2018年7月2日
*/
public class NioServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;// 声明服务端Channel
try {
serverSocketChannel = ServerSocketChannel.open();// 实例化
serverSocketChannel.bind(new InetSocketAddress(1234));// 绑定端口
Selector selector = Selector.open();// 实例化一个Selector
serverSocketChannel.configureBlocking(false);//设置非阻塞,才能进行下面的register
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 注册可接受连接监听
while (selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();// 如果不移除,那么下次遍历还会处理这个selectionKey
if (key.isAcceptable()) {
handleAcceptEvent(selector,key);
} else if (key.isReadable()) {
new Processor().process(key);//使用线程池处理
}
Thread.sleep(1000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理客户端接入事件
*
* @param key
* @throws IOException
*/
private static void handleAcceptEvent(Selector selector,SelectionKey key) throws IOException {
SocketChannel client = null;// 客户端
ServerSocketChannel server = (ServerSocketChannel) key.channel();// 服务端
try {
client = server.accept();// 客户端接入产生一个SocketChannel
System.out.println("server receive a connet request of client!");
if (client == null) {
return;
}
client.configureBlocking(false);// 设置客户端通道为非阻塞
client.register(selector, SelectionKey.OP_READ);//一般不会主动设置SelectionKey.OP_WRITE,因为缓冲区会一直处于可写状态,无限触发select()
} catch (Exception e) {
server.close();
}
}
/**
* 处理读监听事件
* @param key
* @throws IOException
*/
private static void handleReadEvent(Selector selector,SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
client.read(readBuffer);
byte[] data = readBuffer.array();
String msg = new String(data).trim();//消除空格
System.out.println("server receive the client msg:" + msg);
String outMsg = "I am the server!";
ByteBuffer writeBuffer = ByteBuffer.wrap(outMsg.getBytes());
client.write(writeBuffer);
}
}
package com.xdong.nio.v3.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* v3=线程池+双reactor(双Selector)
* @author xiongchaoqun
* @date 2018年7月2日
*/
public class NioServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;// 声明服务端Channel
try {
serverSocketChannel = ServerSocketChannel.open();// 实例化
serverSocketChannel.bind(new InetSocketAddress(1234));// 绑定端口
Selector selector = Selector.open();// 实例化一个Selector
serverSocketChannel.configureBlocking(false);//设置非阻塞,才能进行下面的register
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 注册可接受连接监听
int coreNum = 2;
Processor[] processors = new Processor[coreNum];
for (int i = 0; i < processors.length; i++) {
processors[i] = new Processor();
}
int acceptNum = 0;
while (selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();// 如果不移除,那么下次遍历还会处理这个selectionKey
if (key.isAcceptable()) {
acceptNum++;
SocketChannel channel = null;// 客户端
ServerSocketChannel server = (ServerSocketChannel) key.channel();// 服务端
channel = server.accept();// 客户端接入产生一个SocketChannel
System.out.println("server receive a connet request of client!");
Processor processor = processors[acceptNum%coreNum];
System.out.println(1);
processor.addChannel(channel);//将SocketChannel交给另一个Selector去处理
System.out.println(142);
processor.wakeup();
}
Thread.sleep(1000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.xdong.nio.v2.client;
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.SocketChannel;
import java.util.Iterator;
/**
* v2=线程池、单reactor
* @author xiongchaoqun
* @date 2018年7月2日
*/
public class NioClient {
public static void main(String[] args) throws Exception{
SocketChannel socketChannel = null;
socketChannel = SocketChannel.open();
Selector selector = Selector.open();
socketChannel.configureBlocking(false);//设置非阻塞,这样才能使用Selector
socketChannel.connect(new InetSocketAddress(1234));//客户端连接--这里其实客户端并没有完成连接,是在channel.finishConnect();才能完成连接
socketChannel.register(selector, SelectionKey.OP_CONNECT);//注册可连接事件
while (selector.select() > 0) {
Iterator<SelectionKey>it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
it.remove();
if (key.isConnectable()) {
handleConnectEvent(selector, key);
}else if(key.isReadable()) {
handleReadEvent(selector, key);
}
Thread.sleep(1000);
}
}
}
public static void handleConnectEvent(Selector selector,SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
if (client.isConnectionPending()) {
client.finishConnect();
}
client.configureBlocking(false);//设置通道为非阻塞
client.write(ByteBuffer.wrap(new String("I want to connet to the server!").getBytes()));//服务端 读操作会读到
client.register(selector, SelectionKey.OP_READ);//注册可读时间
}
public static void handleReadEvent(Selector selector,SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);//声明一个bytebuffer
client.read(buffer);
byte[] bytes = buffer.array();
String getMsg = new String(bytes).trim();
System.out.println("client receive server msg is "+getMsg);
client.write(ByteBuffer.wrap(new String("I am a client!").getBytes()));
}
}
Demo项目代码已上传至我的Git上,有兴趣的可以去fork一下。