目录
简单的一个C/S方式的网络请求模型:
客户端发送一个请求指令“QUERY TIME ORDER”(获取当前时间),服务端根据查询的指令做出应答
1、使用一个请求创建一个线程的方式进行处理;
2、把请求封装成一个线程,然后交给线程池执行;
3、原生NIO的方式;
4、基于AIO的方式;
5、基于netty的方式
1、传统的BIO编程
BIO通信模型图
特点:有个请求一个线程
缺点:随着请求量的增加,线程数膨胀
1.1、同步阻塞式I/O创建的TimeServer源码分析
public class TimeServer {
public static void main(String[] ars) throws IOException {
int port = 8080;
ServerSocket server = null;
try {
server = new ServerSocket(port);
System.out.println("ServerSocket:端口" + port);
Socket socket = null;
while (true) {
socket =server.accept();
new Thread(new TimeServerHandler(socket)).start(); //把每个链接构建个新的线程进行处理
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (server != null) {
System.out.println("Server close" );
server.close();
server = null;
}
}
}
}
public class TimeServerHandler implements Runnable{
private Socket socket;
public TimeServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
//根据socket构建输入流,读取用户请求
out = new PrintWriter(this.socket.getOutputStream(),true);
//根据socket构建输出流,返回响应数据
String currenttime = null;
String body = null;
while (true) {
body = in.readLine(); //读取客户端的请求
if (body == null) {
break;
}
System.out.println("the time receive order:" + body);
currenttime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER" ;
out.println(currenttime); //响应客户端的请求
}
} catch (IOException e) {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (out != null) {
out.close();
out = null;
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
启动服务端,在没有接受到客户端的请求时会一直阻塞在accept方法出,通过堆栈信息可以获取其阻塞的状态:
1.2、同步阻塞式I/O创建的TimeClient源码分析
public class TimeClient {
public static void main(String[] ars) {
int port = 8080;
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1",port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream())); //根据socket构建输入流
out = new PrintWriter(socket.getOutputStream(),true); //根据socket构建输出流
out.println("QUERY TIME ORDER"); //往输出流中写数据,发送请求
System.out.println("send order 2 server succeed.");
String res = in.readLine();//从输入流读取响应
System.out.println("now is:" + res);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
2、 伪异步I/O编程
在BIO的基础上使用线程池技术
伪异步I/O模型图
2.1、伪异步式I/O创建的TimeServer源码分析
public class TimeServer {
public static void main(String[] ars) throws IOException {
int port = 8080;
ServerSocket server = null;
try {
server = new ServerSocket(port);
System.out.println("ServerSocket:端口" + port);
Socket socket = null;
TimeServerHanderExecutePool executePool = new TimeServerHanderExecutePool(50,1000);
while (true) {
socket =server.accept();
executePool.execute(new TimeServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (server != null) {
System.out.println("Server close" );
server.close();
server = null;
}
}
}
}
public class TimeServerHanderExecutePool {
private ExecutorService executor;
public TimeServerHanderExecutePool(int maxPoolSize, int queueSize) {
executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxPoolSize,120L, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize)
);
}
public void execute(Runnable task) {
executor.execute(task);
}
}
public class TimeServerHandler implements Runnable{
private Socket socket;
public TimeServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(),true);
String currenttime = null;
String body = null;
while (true) {
body = in.readLine();
if (body == null) {
break;
}
System.out.println("the time receive order:" + body);
currenttime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER" ;
out.println(currenttime);
}
} catch (IOException e) {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (out != null) {
out.close();
out = null;
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
2.2、客户端代码
和1的客户端代码一样
public class TimeClient {
public static void main(String[] ars) {
int port = 8080;
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1",port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
out.println("QUERY TIME ORDER");
System.out.println("send order 2 server succeed.");
String res = in.readLine();
System.out.println("now is:" + res);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
2.3、总结
BIO和伪异步存在的核心问题还是同步阻塞。无论读操作还是写操作都会存在阻塞导致后续的请求得不到处理。
3、NIO编程
NIO的核心是Buffer、channel、selector
3.1、NIO类库简介
buffer缓冲区
通道channel
多路复用器selector
3.2 NIO服务端序列图
3.3 NIO创建的TimeServer源码分析
public class TimeServer {
public static void main(String[] ars) throws IOException {
int port = 8080;
MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);
new Thread(timeServer,"MultiplexerTimeServer-001").start();
}
}
public class MultiplexerTimeServer implements Runnable {
private Selector selector;
private ServerSocketChannel serverChannel;
private volatile boolean stop;
//初始化多路复用器,绑定监听的端口
public MultiplexerTimeServer(int port) {
try {
selector = Selector.open(); // 打开selector
serverChannel = ServerSocketChannel.open();// 打开服务端ServerSocketChannel
serverChannel.configureBlocking(false); // 设置为非阻塞模式
// 绑定一个本地端口,这样客户端便可以通过这个端口连接到服务器
serverChannel.socket().bind(new InetSocketAddress(port), 1024);
// 注意关心的事件是OP_ACCEPT,表示只关心接受事件即接受客户端到服务器的连接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("ServerSocket:端口" + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
this.stop = true;
}
public void run() {
while (!stop) {
try {
//休眠一秒,每隔一秒中selector被唤醒一次
// select()阻塞直到注册的某个事件就绪并会更新SelectionKey的状态
selector.select(1000);
// 得到就绪的key集合,key中保存有就绪的事件以及对应的Channel通道
Set<SelectionKey> SelectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = SelectionKeys.iterator();
SelectionKey key = null;
// 遍历选择键
while(it.hasNext()) {
key = it.next();
it.remove();
try {
handlerInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
//关闭多路复用器,所有注册在其上的channel和pipe资源都会被释放
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handlerInput(SelectionKey key) throws IOException {
if (key.isValid()) {
//处理新接入的请求消息
if (key.isAcceptable()) {
//接受新的连接
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//注册新的连接到selector
sc.register(selector,SelectionKey.OP_READ);// 注意此处新增关心事件OP_READ
}
//
if (key.isReadable()) {
//读数据
SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBuffers = sc.read(readBuffer);// 把channel中的数据读到buffer中
//readBuffers
if (readBuffers > 0) {
readBuffer.flip(); //读写切换,把指针移到数据的开头
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes); //把buffer中的数据读到字节数组中
String body = new String(bytes,"UTF-8"); //根据字节数组构建请求数据
System.out.println("the time receive order:" + body);
String currenttime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER" ;
dowrite(sc,currenttime); //把响应写回给客户端
} else if (readBuffers < 0) {
//链路关闭,释放资源
key.cancel();
sc.close();
} else {
//0 没有读取到字节
}
}
}
}
private void dowrite(SocketChannel channel,String response) throws IOException {
if(response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer wrietBuffer = ByteBuffer.allocate(bytes.length);
wrietBuffer.put(bytes);//把响应数据写到buffer中
wrietBuffer.flip();//读写切换
channel.write(wrietBuffer); //把buffer中的
}
}
}
3.4 NIO客户端序列图
3.5 NIO创建的TimeClient源码分析
public class TimeClient {
public static void main(String[] ars) {
int port = 8080;
new Thread(new TimeClientHandle("127.0.0.1",port),"TimeClientHandle-001").start();
}
}
public class TimeClientHandle implements Runnable{
private String host;
private Integer port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;
public TimeClientHandle(String host,Integer port) {
this.host = host == null ? "127.0.0.1" : host ;
this.port = port;
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void run() {
try {
doconnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> SelectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = SelectionKeys.iterator();
SelectionKey key = null;
while(it.hasNext()) {
key = it.next();
it.remove();
try {
handlerInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handlerInput(SelectionKey key) throws IOException {
if (key.isValid()) {
//判断是否连接成功
SocketChannel sc = (SocketChannel)key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector,SelectionKey.OP_READ);
doWrite(sc);
} else {
System.exit(1);
}
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBuffers = sc.read(readBuffer);
if (readBuffers > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes,"UTF-8");
System.out.println("now is : " + body);
this.stop = true;
} else if (readBuffers < 0) {
key.cancel();
sc.close();
} else {
//0
}
}
}
}
private void doconnect() throws IOException {
//如果连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress(host,port))) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else {
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel sc) throws IOException {
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if (!writeBuffer.hasRemaining()) {
System.out.println("send order 2 server succeed.");
}
}
}
4、AIO编程
核心是类CompletionHandler
4.1 AIO创建的TimeServer源码分析
public class TimeServer {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
new Thread(timeServer, "AIO-AsyncTimeServerHandler-001").start();
}
}
public class AsyncTimeServerHandler implements Runnable {
private int port;
CountDownLatch latch;
AsynchronousServerSocketChannel asynchronousServerSocketChannel;
public AsyncTimeServerHandler(int port) {
this.port = port;
try {
asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
System.out.println("The time server is start in port : " + port);
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
latch = new CountDownLatch(1);
doAccept();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void doAccept() {
asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
}
}
public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> {
@Override
public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) {
attachment.asynchronousServerSocketChannel.accept(attachment, this);
ByteBuffer buffer = ByteBuffer.allocate(1024);
result.read(buffer, buffer, new ReadCompletionHandler(result));
}
@Override
public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
exc.printStackTrace();
attachment.latch.countDown();
}
}
public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel channel;
public ReadCompletionHandler(AsynchronousSocketChannel channel) {
if (this.channel == null)
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] body = new byte[attachment.remaining()];
attachment.get(body);
try {
String req = new String(body, "UTF-8");
System.out.println("The time server receive order : " + req);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req)
? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(currentTime);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
private void doWrite(String currentTime) {
if (currentTime != null && currentTime.trim().length() > 0) {
byte[] bytes = (currentTime).getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
// 如果没有发送完成,继续发送
if (buffer.hasRemaining())
channel.write(buffer, buffer, this);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException e) {
// ingnore on close
}
}
});
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 AIO创建的TimeClient源码分析
public class TimeClient {
/**
* @param args
*/
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new Thread(new AsyncTimeClientHandler("127.0.0.1", port), "AIO-AsyncTimeClientHandler-001").start();
}
}
public class AsyncTimeClientHandler implements CompletionHandler<Void, AsyncTimeClientHandler>, Runnable {
private AsynchronousSocketChannel client;
private String host;
private int port;
private CountDownLatch latch;
public AsyncTimeClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
client = AsynchronousSocketChannel.open();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
latch = new CountDownLatch(1);
client.connect(new InetSocketAddress(host, port), this, this);
try {
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void completed(Void result, AsyncTimeClientHandler attachment) {
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
client.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
if (buffer.hasRemaining()) {
client.write(buffer, buffer, this);
} else {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
client.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body;
try {
body = new String(bytes, "UTF-8");
System.out.println("Now is : " + body);
latch.countDown();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
client.close();
latch.countDown();
} catch (IOException e) {
// ingnore on close
}
}
});
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
client.close();
latch.countDown();
} catch (IOException e) {
// ingnore on close
}
}
});
}
@Override
public void failed(Throwable exc, AsyncTimeClientHandler attachment) {
exc.printStackTrace();
try {
client.close();
latch.countDown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5、选择Netty的理由
Hadoop的RPC框架avro采用的底层就是netty
6、基于netty的方式
6.1、netty服务端
public class TimeServer {
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());//绑定IO事件处理类
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new TimeServer().bind(port);
}
}
public class TimeServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("The time server receive order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)
? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
6.2、netty的客户端
public class TimeClient {
public void connect(int port, String host) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new TimeClient().connect(port, "127.0.0.1");
}
}
public class TimeClientHandler extends ChannelHandlerAdapter {
private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
private final ByteBuf firstMessage;
/**
* Creates a client-side handler.
*/
public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("Now is : " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 释放资源
logger.warning("Unexpected exception from downstream : " + cause.getMessage());
ctx.close();
}
}
参考:
《netty权威指南》