一.BIO下的client-server通信模式
BIO即Blocking IO,同步阻塞式IO,在BIO的通信模式下,
「一个线程只能在一个accept、read或write方法中阻塞,对应着一条IO连接线路」
以下程序即BIO下的client对server的通信模式:
- BIOClient
/**
* @Auther: ARong
* @Description: BIO 的 客户端
*/
public class BIOClient {
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket("127.0.0.1", 6666);
// 每2s向服务端发送helloword
for (int i = 0; i < 10; i++) {
Thread.sleep(2*1000);
String message = String.format("HelloWorld From %s", new SimpleDateFormat("hh:mm:ss").format(new Date()));
socket.getOutputStream().write(message.getBytes());
}
}
}
- BIOServer
/**
* @Auther: ARong
* @Description: BIO 的 服务端
*/
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
// 阻塞直到有客户端连接
Socket socket = serverSocket.accept();
// 每次有连接即创建一个线程接受处理
new Thread(()->{
// 处理请求
int len = -1;
byte[] data = new byte[1024];//每次读取1kb
try {
InputStream inputStream = socket.getInputStream();
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
传统的BIO模型每个连接创建成功都需要一个线程来维护,每个线程包含一个while死循环,那么1w个连接对应1w个线程,继而1w个while死循环带来如下几个问题:
- 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起
- 线程切换效率低下:单机CPU核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降
- 数据读写是以字节流为单位效率不高:每次都是从操作系统底层一个字节一个字节地读取数据
二.NIO下的通信模式
NIO即New IO或NoBloking-IO,同步非阻塞式IO,它是基于Buffer、Channel和Selector的「IO多路复用通信模式,一个线程不会在accpet、read或write方法中阻塞,而是可以同时处理多个IO连接线路」
NIO相较于BIO的优点有:
- 只需要使用少量的线程,通过IO多路复用的方式即可处理多个IO连接,而不需要阻塞在某一IO连接中,也就不会存在线程上下文切换问题,提高了通信效率
- 数据读写不再以字节流为单位,而是以缓冲块为单位进行读写
以下是Java实现的NIO Demo
- NIOServer(Copy From 闪电侠)
public class NIOServer {
/**
* serverSelector负责轮询是否有新的连接,clientSelector负责轮询连接是否有数据可读.
* 服务端监测到新的连接不再创建一个新的线程,而是直接将新连接绑定到clientSelector上,这样不用IO模型中1w个while循环在死等
* clientSelector被一个while死循环包裹,如果在某一时刻有多条连接有数据可读通过 clientSelector.select(1)方法轮询出来进而批量处理
* 数据的读写以内存块为单位
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Selector serverSelector = Selector.open();
Selector clientSelector = Selector.open();
new Thread(() -> {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8000));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
while (true) {
// 轮询监测是否有新的连接
if (serverSelector.select(1) > 0) {
Set<SelectionKey> selectionKeys = serverSelector.selectedKeys();
Iterator<SelectionKey> keyIterator;
keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
if (selectionKey.isAcceptable()) {
try {
//(1)每来一个新连接不需要创建一个线程而是直接注册到clientSelector
SocketChannel socketChannel = ((ServerSocketChannel) selectionKey.channel()).accept();
socketChannel.configureBlocking(false);
socketChannel.register(clientSelector, SelectionKey.OP_READ);
} finally {
keyIterator.remove();
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
while (true) {
// (2)批量轮询是否有哪些连接有数据可读
if (clientSelector.select(1) > 0) {
Set<SelectionKey> selectionKeys = serverSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
if (selectionKey.isReadable()) {
try {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//(3)读取数据以块为单位批量读取
socketChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer)
.toString());
} finally {
keyIterator.remove();
selectionKey.interestOps(SelectionKey.OP_READ);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
三.Netty入门
1. 什么是Netty
Netty 是一个基于 Java NIO 类库的异步通信框架,它的架构特点是:异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性。使用Netty可以很好地替代掉繁琐的、难以使用的Java NIO类库,并且Netty提供了更多可用的功能。
2.Netty的实际应用
- 高性能HTTP服务器
- 高性能RPC框架(例如Dubbo)
- Redission(Redis For Java)
- IM即时通信应用
- 大量需要网络通信的框架底层实现
3.Netty入门Demo
- 导入maven依赖
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
- NIOServer
public class NIOServer {
public static void main(String[] args) {
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup bossGroup = new NioEventLoopGroup(); // 监听组
NioEventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作组
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) //NIO模式
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
// 初始化channel
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(String.format("服务端接收到消息:%s", msg));
}
});
}
}).bind(8000);
}
}
- NIOCLient
public class NIOClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
while (true) {
String message = String.format("HelloWorld From %s", new SimpleDateFormat("hh:mm:ss").format(new Date()));
channel.writeAndFlush(message);
Thread.sleep(2000);
}
}
}
执行程序: