简介
- BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠的网络服务器和客户端程序。Netty简化了网络程序的开发,是很多框架和公司都在使用的技术。Netty并非横空出世,它是在BIO,NIO,AIO演变中的产物,是一种NIO框架。
BIO
- 网络编程的基本模型是C/S模型,即两个进程间的通信。
- 服务端提供IP和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。
- 传统的同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就挂了。这种通信模型在高并发的场景是没法应用的。
**同步阻塞式BIO创建的Server源码**:
package com.scoket.bio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServer {
public static void main(String[] args) throws IOException {
ExecutorService executor = Executors.newFixedThreadPool(3);
RequestHandler requestHandler = new RequestHandler();
try(ServerSocket serverSocket = new ServerSocket(8888)){
System.out.println("BIOServer has started,listenning on port: "+serverSocket.getLocalSocketAddress());
//多个客户端的请求连接
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("Connection from" + clientSocket.getRemoteSocketAddress());
//为了给线程池可以去进行任务的执行
executor.submit(new ClientHandler(clientSocket,requestHandler));
}
}
}
}
同步阻塞式BIO创建的Client源码:
package com.scoket.bio;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
public class ClientHandler implements Runnable {
private final Socket clientSocket;
private final RequestHandler requestHandler;
public ClientHandler(Socket clientSocket, RequestHandler requestHandler) {
this.clientSocket = clientSocket;
this.requestHandler = requestHandler;
}
@Override
public void run() {
try (Scanner input = new Scanner(clientSocket.getInputStream())) {
//针对每个请求能够进行无限的交互操作
while (true) {
//真正阻塞的原因在这边,一直要等待客户端的输入信息,线程被耽搁的点
String request = input.nextLine();
if ("quit".equals(request)) {
break;
}
System.out.println(request);
String response = requestHandler.handler(request);
clientSocket.getOutputStream().write(response.getBytes());
}
}catch (IOException e){
throw new RuntimeException(e);
}
}
}
同步阻塞式BIO创建的RequestHandler源码:
package com.scoket.bio;
public class RequestHandler {
public String handler(String request){
return "From BIOServer Hello" + request + "\n";
}
}
NIO
- NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。
- 新增的着两种通道都支持阻塞和非阻塞两种模式。
- 阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。
- 对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。
- NIO编程是面向通道的(BIO是面向流的),流分为写入/写出,是单向的,意味着通道是可以进行双向读写的。NIO所有基于channel的API对数据的操作都是间接通过操作缓冲区ByteBuffer
- In : 磁盘 --通道–> ByteBuffer(内存)–>数据(内存)
- Out: 数据(内存)–>ByteBuffer(内存) --> 通道 --> 磁盘
NIO编程涉及ServerSocketChannel、SocketChannel、ByteBuffer、Selector(通道选择器)
同步非阻塞式NIO创建的服务端源码:
package com.scoket.nio;
import com.scoket.bio.RequestHandler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
//主线程 Channel[Server Client], Selector, Buffer
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(9999));
System.out.println("NIOServer has started,listening on port: "+serverSocketChannel.getLocalAddress());
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer buffer = ByteBuffer.allocate(1024);
RequestHandler requestHandler = new RequestHandler();
while (true){
int select = selector.select();
if (select == 0){
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
//处理每个客户端的连接操作
SelectionKey key = iterator.next();
if (key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = channel.accept();
System.out.println("Connection from: "+clientChannel.getRemoteAddress());
clientChannel.configureBlocking(false);
clientChannel.register(selector,SelectionKey.OP_READ);
}
if (key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
channel.read(buffer);
String request = new String(buffer.array()).trim();
buffer.clear();
System.out.println(String.format("From %s : %s",channel.getRemoteAddress(),request));
String response = requestHandler.handler(request);
channel.write(ByteBuffer.wrap(response.getBytes()));
}
iterator.remove();;
}
}
}
}
Netty
-
Netty的通讯管道:通过AOP的编程思想(责任链设计模式),实现消息的编解码。
服务端编程:
1)创建ServerBootstrap sbt=new ServerBootstrap();
2)创建EventLoopGroup boss、worker
3)关联boss和worker> sbt.group(boss,worker);
4)设置ServerSocketChannel实现类sbt.channel(NioServerSocketChannel);
5)初始化通道sbt.childHandler(初始化通道);
6)绑定端口并启动服务ChannelFuture future=sbt.bind(port).sync();
7)等待服务关闭 future.channel().closeFuture().sync();
8 ) 释放线程资源boss、worker # shutdownGraceFully();
Netty创建的客户端源码:
package com.cherry.socket.netty.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class ClientBootStrap {
public static void main(String[] args) throws InterruptedException {
//1.Create Bootstrap
Bootstrap bt = new Bootstrap();
//2.Create thread pool(worker)
EventLoopGroup worker = new NioEventLoopGroup();
//3.关联线程池
bt.group(worker);
//4.Set up the client
bt.channel(NioSocketChannel.class);
//5.Initialize the communication channel (key point)
bt.handler(new ClientChannelInitializer());
//6.连接
ChannelFuture channelFuture = bt.connect("127.0.0.1",9999).sync();
//7.Wait for the server to close
channelFuture.channel().closeFuture().sync();
//8.release the resource
worker.shutdownGracefully();
}
}
package com.cherry.socket.netty.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import javax.xml.transform.Source;
public class ClientChannelHander extends ChannelHandlerAdapter {
/*捕获数据在通道传输过程的异常*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//super.exceptionCaught(ctx, cause);
System.out.println("错误:"+cause.getMessage());
}
/*接收数据*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//super.channelRead(ctx, msg);
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(((ByteBuf)msg).toString(CharsetUtil.UTF_8));
}
/*链接服务器时发送数据*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//super.channelActive(ctx);
ByteBuf buf = Unpooled.buffer();
buf.writeBytes("你好,我是客户端!".getBytes());
ctx.writeAndFlush(buf);
}
}
package com.cherry.socket.netty.client;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//communication channel
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new ClientChannelHander());
}
}
上述BIO NIO Netty所用的pom.xml
<!-- socket 相关 注意版本,不然有些方法不能用-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
通过对Netty的分析,我们将他的优点总结如下:
1.API使用简单,开发门槛低;
2.功能强大,预设了很多编码功能,支持多种主流协议;
3.定制能力强,可以通过ChannelHandler对通信框架进行灵活扩展;
4.性能高,通过与其他的主流NIO框架对比,Netty的综合性能最优;
5.成熟,稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG烦恼;
6.社区活跃,版本迭代周期短;
7.经过大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多领域已经得到了成功商用,也 证明他已经完全能满足不同行业的商应用了;