一、BIO
阻塞io,存在无法同时支持多个连接问题,新建线程处理数据优化该问题,引发的问题是线程数可能过多,内存资源不足;引入线程池会限制并发数,由线程池决定连接数,阻塞等待数据也会占用线程池线程资源。
public class SocketServer {
public static void main(String[] args) throws IOException{
//初始化ServerSocket ,绑定端口
ServerSocket sc = new ServerSocket(9000);
while(true){
System.out.println("等待连接。。。。");
//阻塞等待连接,accept接收客户端连接
Socket clientSocket = sc.accept();
//新开线程处理数据
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(clientSocket);
}catch (IOException e){
}
}
});
System.out.println("存在连接。。。");
//不新开线程处理
// handler(clientSocket);
}
}
private static void handler( Socket clientSocket ) throws IOException{
byte[] bytes = new byte[1024];
System.out.println("read....");
//阻塞接收数据,没有数据则阻塞
int read = clientSocket.getInputStream().read(bytes);
if(read!=-1){
System.out.println("data:"+new String(bytes,0,read));
}
}
}
二、NIO
非阻塞io,相比BIO可以支持多个连接,优化数据读取。
多路复用底层原理
epoll_create:创建epoll实例,使用文件描述符表示
epoll_ctl:将连接等绑定到epoll上,监听事件
epoll_wait:阻塞等待事件发生,通过操作系统中断机制感知事件
public class NioServer {
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException {
//创建nio的serverSocketChannel,与Bio的ServerSocket类似
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
//连接配置为非阻塞
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();//epoll实例对象文件描述符表示,后续存放注册事件
//Channel注册搭配多路复用器,selector注册连接事件,优化无效遍历连接消耗了性能的问题
SelectionKey selection = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while(true){
//阻塞等待需要处理的事件
selector.select();
//获取注册的全部事件实例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isAcceptable()){
//是连接事件,进行连接获取,事件注册
ServerSocketChannel server =(ServerSocketChannel)key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
//可以注册读写事件
SelectionKey selKey = socketChannel.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
SocketChannel socketChannel = (SocketChannel)key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
//非阻塞
int len = socketChannel.read(byteBuffer);
if(len>0){
System.out.println("data:"+new String(byteBuffer.array()));
}else
//客户端断开连接关闭socket
if(len == -1){
socketChannel.close();
}
}
//事件集合去除key,避免重复处理
iterator.remove();
}
}
//简易NIO,一个线程可处理多个连接
//创建nio的serverSocketChannel,与Bio的ServerSocket类似
// ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// serverSocketChannel.socket().bind(new InetSocketAddress(9000));
//连接配置为非阻塞
// serverSocketChannel.configureBlocking(false);
// while(true){
// //非阻塞,底层调用Linux的accept方法
// SocketChannel socketChannel = serverSocketChannel.accept();
// if(socketChannel!=null){//如果有客户端连接
// //读取数据设置非阻塞
// socketChannel.configureBlocking(false);
// //保存连接
// channelList.add(socketChannel);
// }
//遍历连接读取数据,存在问题大量连接无数据也需遍历,消耗性能
// Iterator<SocketChannel> iterator = channelList.iterator();
// while(iterator.hasNext()){
// SocketChannel sc = iterator.next();
// ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// //非阻塞
// int len = sc.read(byteBuffer);
// if(len>0){
// System.out.println("data:"+new String(byteBuffer.array()));
// }else
// //客户端断开连接,去掉
// if(len == -1){
// iterator.remove();
// }
// }
// }
}
}
三、Netty
netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高。
Netty对NIO做了封装优化,不关心如何建立连接注册事件,只关心如何处理收发数据,更加简便开发NIO程序,使用也很简单,以下就是简单示例,主要就是实现HandlerAdapter,覆写方法。
public class NettyServer {
public static void main(String[] args) throws Exception {
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
try{
//服务端启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boosGroup,workerGroup)//绑定两个线程池
.channel(NioServerSocketChannel.class)//服务器通道
//服务器队列大小,排队
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception{
//设置处理器
ch.pipeline().addLast(new NettyServerHandler());
}
});
//启动服务器绑定端口
ChannelFuture cf = bootstrap.bind(9000).sync();
cf.channel().closeFuture().sync();
}catch (Exception e){
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
/**
* 自定义处理器,需要继承netty规定的HandlerAdapter
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 客户端连接服务器完成触发
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx){
System.out.println("连接建立。。。");
}
/**
* 读取客户端数据
* @param ctx 上下文对象,通道channnel 管道 pipeline
* @param msg 数据
*/
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg){
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();//双向链接
ByteBuf buf = (ByteBuf)msg;
System.out.println("data。。。"+buf.toString(CharsetUtil.UTF_8));
}
/**
* 数据读取完毕处理方法
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx){
ByteBuf buf = Unpooled.copiedBuffer("aa".getBytes(CharsetUtil.UTF_8));
//发送数据
ctx.writeAndFlush(buf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
ctx.close();
}
}
相比于NIO,netty优化大量连接建立进行读写,后续新建连接等待超时的问题,利用主selector进行连接建立,多selector处理数据。worker group最终回调pipeline中的hander里处理数据方法。
原理:Reactor响应式模型
零拷贝:使用直接内存(本机物理内存),利用的是直接内存的引用。
[1] https://www.jianshu.com/p/b9f3f6a16911
[2] https://www.bilibili.com/video/BV1Ui4y1T7yf
[3] https://www.bilibili.com/video/BV1Vy4y1J7DG