BIO的缺点:
public static void main(String[] args) throws FileNotFoundException {
try {
ServerSocket server = new ServerSocket(5555);
while (true) {
Socket socket = server.accept(); //这是阻塞的,没有客户端到来,一直停在这里
System.out.println("客户端连接来了");
byte[] bytes = new byte[1024];
//这是阻塞的,客户端连接过来后,若客户端不发数据,一直停在这里
socket.getInputStream().read(bytes);
//直到有客户端发送数据过来,才能走到这里
System.out.println("收到客户端的消息:" + new String(bytes));
//在这个处理过程中,server一直在处理这一个客户端的请求,如果有第二个客户端过来,server也处理不了
}
} catch (IOException e) {
e.printStackTrace();
}
}
针对上面,服务端必须等这个客户端完全处理完成才能处理下一个客户端的请求,我们做一次升级:
public static void main(String[] args) throws FileNotFoundException {
try {
ServerSocket server = new ServerSocket(5555);
while (true) {
final Socket socket = server.accept(); //这是阻塞的,没有客户端到来,一直停在这里
System.out.println("客户端连接来了");
//启动一个线程去执行后续的读写,server可以继续处理其他客户端的请求
new Thread(new Runnable() {
public void run() {
while (true) {
try {
byte[] bytes = new byte[1024];
socket.getInputStream().read(bytes);
System.out.println("收到客户端的消息:" + new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
这样做可以让server继续处理其他客户端请求,但每个socket都要new Thread,太过于浪费,当然如果进一步优化可以使用线程池,但这些都是治标不治本,性能较低。
所以有了NIO:
package nio;
import org.springframework.util.StringUtils;
import java.io.IOException;
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.Date;
import java.util.Iterator;
import java.util.Set;
public class ServerSocketChannels implements Runnable {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private volatile boolean stop;
public ServerSocketChannels(int port){
try {
//创建多路复用器selector,工厂方法
selector = Selector.open();
//创建ServerSocketChannel,工厂方法
serverSocketChannel = ServerSocketChannel.open();
//绑定ip和端口号,默认的IP=127.0.0.1,对连接的请求最大队列长度设置为backlog=1024,如果队列满时收到连接请求,则拒绝连接
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
//设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//注册serverSocketChannel到selector多路服用器上面,监听accrpt请求
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("the time is start port = " + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
this.stop = true;
}
@Override
public void run() {
//如果server没有停止
while(!stop){
try {
//selector.select()会一直阻塞到有一个通道在你注册的事件上就绪了
//selector.select(1000)会阻塞到1s后然后接着执行,相当于1s轮询检查
selector.select(1000);
//找到所有准备接续的key
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
SelectionKey key = null;
while(it.hasNext()){
key = it.next();
it.remove();
try {
//处理准备就绪的key
handle(key);
}catch (Exception e){
if(key != null){
//请求取消此键的通道到其选择器的注册
key.cancel();
//关闭这个通道
if(key.channel() != null){
key.channel().close();
}
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
if(selector != null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void handle(SelectionKey key) throws IOException {
//如果key是有效的
if(key.isValid()){
//监听到有新客户端的接入请求
//完成TCP的三次握手,建立物理链路层
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = (SocketChannel) ssc.accept();
//设置客户端链路为非阻塞模式
sc.configureBlocking(false);
//将新接入的客户端注册到多路复用器Selector上
sc.register(selector, SelectionKey.OP_READ);
}
//监听到客户端的读请求
if(key.isReadable()){
//获得通道对象
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//从channel读数据到缓冲区
int readBytes = sc.read(readBuffer);
if (readBytes > 0){
//Flips this buffer. The limit is set to the current position and then
// the position is set to zero,就是表示要从起始位置开始读取数据
readBuffer.flip();
//eturns the number of elements between the current position and the limit.
// 要读取的字节长度
byte[] bytes = new byte[readBuffer.remaining()];
//将缓冲区的数据读到bytes数组
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("the time server receive order: " + body);
String currenttime = "query time order".equals(body) ? new Date(System.currentTimeMillis()).toString(): "bad order";
doWrite(sc, currenttime);
}else if(readBytes < 0){
key.channel();
sc.close();
}
}
}
}
public static void doWrite(SocketChannel channel, String response) throws IOException {
if(!StringUtils.isEmpty(response)){
byte [] bytes = response.getBytes();
//分配一个bytes的length长度的ByteBuffer
ByteBuffer write = ByteBuffer.allocate(bytes.length);
//将返回数据写入缓冲区
write.put(bytes);
write.flip();
//将缓冲数据写入渠道,返回给客户端
channel.write(write);
}
}
}
NIO操作过于复杂、繁琐,进而产生了Netty
单线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup);
多线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(2);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup);
主从单线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
主从多线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(2);
EventLoopGroup workerGroup = new NioEventLoopGroup(10);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
Netty为什么优于NIO:
netty底层基于jdk的NIO,我们为什么不直接基于jdk的nio或者其他nio框架?
- 1.使用jdk自带的nio需要了解太多的概念,编程复杂
- 2.netty底层IO模型随意切换,而这一切只需要做微小的改动
- 3.netty自带的拆包解包,异常检测等机制让你从nio的繁重细节中脱离出来,让你只需要关心业务逻辑
- 4.netty解决了jdk的很多包括空轮训在内的bug
- 5.netty底层对线程,selector做了很多细小的优化,精心设计的reactor线程做到非常高效的并发处理
- 6.自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手
- 7.netty社区活跃,遇到问题随时邮件列表或者issue
- 8.netty已经历各大rpc框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大