目录
1 BIO
BIO是同步阻塞模型,其核心是一个客户端连接对应一个处理线程。实现起来简单但是吞吐量低,而且客户端不做读写操作的话,服务端会被阻塞(可以做多线程处理)。
BIO的示例代码如下:
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
* 服务端示例
*
* @author Robert Hou
* @date 2020年05月01日 23:07
**/
@Slf4j
public class BIOServer {
@SneakyThrows
public static void main(String[] args) {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory);
ServerSocket serverSocket = new ServerSocket(9000);
while (true) {
log.info("等待连接。。。");
//阻塞方法
Socket socket = serverSocket.accept();
log.info("有客户端连接了。。。");
pool.execute(() -> handler(socket));
}
}
@SneakyThrows
private static void handler(Socket socket) {
if (log.isDebugEnabled()) {
log.debug("thread id = " + Thread.currentThread().getId());
}
byte[] bytes = new byte[1024];
log.info("准备read。。。");
//接收客户端的数据,阻塞方法,没有数据可读时就阻塞
int read = socket.getInputStream().read(bytes);
log.info("read完毕。。。");
if (read != -1) {
if (log.isInfoEnabled()) {
log.info("接收到客户端的数据:" + new String(bytes, 0, read));
}
log.debug("thread id = " + Thread.currentThread().getId());
}
socket.getOutputStream().write("HelloClient".getBytes());
socket.getOutputStream().flush();
}
}
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.net.Socket;
/**
* 客户端代码
*
* @author Robert Hou
* @date 2020年05月01日 23:08
**/
@Slf4j
public class BIOClient {
@SneakyThrows
public static void main(String[] args) {
Socket socket = new Socket("localhost", 9000);
//向服务端发送数据
socket.getOutputStream().write("HelloServer".getBytes());
socket.getOutputStream().flush();
log.info("向服务端发送数据结束");
byte[] bytes = new byte[1024];
//接收服务端回传的数据
socket.getInputStream().read(bytes);
if (log.isInfoEnabled()) {
log.info("接收到服务端的数据:" + new String(bytes));
}
socket.close();
}
}
2 NIO
NIO是同步非阻塞模型,服务器实现模式为一个线程可以处理多个请求连接,客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就交给后边的线程进行处理。Redis就是典型的NIO线程模型,selector收集所有连接的事件并且转交给后端线程,线程连续执行所有事件命令并将结果写回客户端。
NIO的示例代码如下:
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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;
/**
* 服务端代码
*
* @author Robert Hou
* @date 2020年05月01日 23:09
**/
@Slf4j
public class NIOServer {
@SneakyThrows
public static void main(String[] args) {
//创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
ServerSocketChannel ssc = ServerSocketChannel.open();
//必须配置为非阻塞才能往selector上注册,否则会报错,selector模式本身就是非阻塞模式
ssc.configureBlocking(false);
ssc.socket().bind(new InetSocketAddress(9000));
//创建一个选择器selector
Selector selector = Selector.open();
//把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
log.info("等待事件发生。。。");
//轮询监听channel里的key,select是阻塞的,accept()也是阻塞的
selector.select();
log.info("有事件发生了。。。");
//有客户端请求,被轮询监听到
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//删除本次已处理的key,防止下次select重复处理
it.remove();
handle(key);
}
}
}
@SneakyThrows
private static void handle(SelectionKey key) {
if (key.isAcceptable()) {
log.info("有客户端连接事件发生了。。。");
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//NIO非阻塞体现:此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完,不会阻塞
//处理完连接请求不会继续等待客户端的数据发送
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//通过Selector监听Channel时对读事件感兴趣
sc.register(key.selector(), SelectionKey.OP_READ);
} else if (key.isReadable()) {
log.info("有客户端数据可读事件发生了。。。");
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//NIO非阻塞体现:首先read方法不会阻塞,其次这种事件响应模型,当调用到read方法时肯定是发生了客户端发送数据的事件
int len = sc.read(buffer);
if (len != -1) {
if (log.isInfoEnabled()) {
log.info("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
}
}
ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
sc.write(bufferToWrite);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
key.channel();
log.info("write事件");
//NIO事件触发是水平触发
//使用Java的NIO编程的时候,在没有数据可以往外写的时候要取消写事件,
//在有数据往外写的时候再注册写事件
key.interestOps(SelectionKey.OP_READ);
}
}
}
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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;
/**
* 客户端代码
*
* @author Robert Hou
* @date 2020年05月01日 23:09
**/
@Slf4j
public class NIOClient {
/**
* 通道管理器
*/
private Selector selector;
/**
* 启动客户端测试
*/
@SneakyThrows
public static void main(String[] args) {
NIOClient client = new NIOClient();
client.initClient("127.0.0.1", 9000);
client.connect();
}
/**
* 获得一个Socket通道,并对该通道做一些初始化的工作
*
* @param ip 连接的服务器的ip
* @param port 连接的服务器的端口号
*/
@SneakyThrows
public void initClient(String ip, int port) {
//获得一个Socket通道
SocketChannel channel = SocketChannel.open();
//设置通道为非阻塞
channel.configureBlocking(false);
//获得一个通道管理器
this.selector = Selector.open();
//客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
//用channel.finishConnect();才能完成连接
channel.connect(new InetSocketAddress(ip, port));
//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
channel.register(selector, SelectionKey.OP_CONNECT);
}
/**
* 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
*/
@SneakyThrows
public void connect() {
//轮询访问selector
while (true) {
selector.select();
//获得selector中选中的项的迭代器
Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//删除已选的key,以防重复处理
it.remove();
//连接事件发生
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key.channel();
//如果正在连接,则完成连接
if (channel.isConnectionPending()) {
channel.finishConnect();
}
//设置成非阻塞
channel.configureBlocking(false);
//在这里可以给服务端发送信息哦
ByteBuffer buffer = ByteBuffer.wrap("HelloServer".getBytes());
channel.write(buffer);
//在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
//获得了可读的事件
channel.register(this.selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
read(key);
}
}
}
}
/**
* 处理读取服务端发来的信息 的事件
*
* @param key SelectionKey
*/
@SneakyThrows
public void read(SelectionKey key) {
//和服务端的read方法一样
//服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
//创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(512);
int len = channel.read(buffer);
if (len != -1) {
if (log.isInfoEnabled()) {
log.info("客户端收到信息:" + new String(buffer.array(), 0, len));
}
}
}
}
3 AIO
AIO是异步非阻塞模型,采用订阅-通知模式,由操作系统完成后回调通知服务端程序启动线程去处理。
AIO的示例代码如下:
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* 服务端代码
*
* @author Robert Hou
* @date 2020年05月01日 23:09
**/
@Slf4j
public class AIOServer {
@SneakyThrows
public static void main(String[] args) {
final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
@SneakyThrows
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
//在此接收客户端连接,如果不写这行代码后面的客户端连接连不上服务端
serverChannel.accept(attachment, this);
log.debug(String.valueOf(socketChannel.getRemoteAddress()));
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
log.info(new String(buffer.array(), 0, result));
socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
}
@Override
public void failed(Throwable exc, ByteBuffer buffer) {
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
Thread.sleep(Integer.MAX_VALUE);
}
}
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
/**
* 客户端代码
*
* @author Robert Hou
* @date 2020年05月01日 23:10
**/
@Slf4j
public class AIOClient {
@SneakyThrows
public static void main(String[] args) {
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
ByteBuffer buffer = ByteBuffer.allocate(512);
Integer len = socketChannel.read(buffer).get();
if (len != -1) {
if (log.isInfoEnabled()) {
log.info("客户端收到信息:" + new String(buffer.array(), 0, len));
}
}
}
}