ServerSocketChannel.open() -> ServerSocketChannel
Selector.open() -> Selector
把ServerSocketChannel的accept事件注册给selector进行监听
accept事件就表示有一个客户端要向服务端进行连接
Selector.select()是一个阻塞函数,一直等待他监听的事件,这个函数触发代表着有一个SocketChannel要连接,然后我们把这个SocketChannel的read和write事件注册到selector上,此时selector就监听了三个事件
同时要给SocketChannel加key,当key可读时,就会读取key里面的ByteBuffer,按协议切割每一个完整消息包,通过反序列化转成具体的Object,再进行处理
以上是整个的读取流程
来看一个场景:
现在正常读取key1里面的数据,处理数据的过程中,有另一个SocketChannel要来进行连接,此时无法处理这个连接
为什么?
因为我们虽然是多路复用非阻塞模型,但是只有一个线程来进行处理,如果想处理accept事件,就要先处理完手中的read事件
为了在处理数据的同时还可以接收新的客户端连接,我们增加了一个Selector
selector1监听ServerSocketChannel的accept事件,selector2监听所有SocketChannel的read、write事件,对应两个线程分别持有两个selector,这样就把建立连接和读写事件分开了
那如果连接非常多,也就是SocketChannel非常多,一个读写线程可能就处理不过来这么多的读写事件了,我们可以把读写线程变成多个,把读写连接平衡地分给每一个selector,把这些处理读写事件的线程放到一个线程组里,称为worker线程组,之前的accetp线程称为boss线程(组)。
为什么Boss线程只有一个:因为ServerSocketChannel只有一个
线程是在一个循环中不断处理事件,可以起一个更有意义的名字 -> EventLoop事件循环,线程组也分别命名为boss EventLoopGroup、worker EventLoopGroup
ChannelHandler分为ChannelInboundHandler和ChannelOutboundHandler
对于每一个Channel,都表示一个Server和Client互相通信的通道,Server和Client都维护了一条处理器的链路,称为pipeline,这个管道里面就维护着若干个入站处理器和出站处理器。
在pipline中,每一个消息处理器都会被维护成一个双向链表的节点:
读数据:入站处理器h1 -> h3 -> h5 -> h7 -> tail
写数据:出站处理器h6 -> h4 -> h2 -> head
组合pipline,handler,eventLoop,eventLoopGroup这几个组件,完成整个通信流程,称为BootStrap。
Server:
public class NettyServer {
public static void main(String[] args) {
// 增加服务器收到消息后,持久化到数据库以及客户端断开连接后,打印messageList中所有内容的功能
Map<Channel, List<String>> db = new ConcurrentHashMap<>();//模拟数据库
// 配置BootStrap,表示服务器接收到数据后,解成字符串并且打印
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new LineBasedFrameDecoder(1024))//以换行符为标准解包 按换行符来分割出一条一条消息
.addLast(new StringDecoder())//添加解码器 反序列化 得到String
.addLast(new StringEncoder())
.addLast(new SimpleChannelInboundHandler<String>() { //泛型:处理什么类型的消息
@Override //真正读取了消息的函数
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
// 服务端也可以给客户端发消息 需要出站处理器
String message = msg + " world\n";
ctx.channel().writeAndFlush(message);
ctx.fireChannelRead(msg);
}
}).addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
List<String> messageList = db.computeIfAbsent(ctx.channel(), k -> new ArrayList<>());
messageList.add(msg);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel() + "注册了");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel() + "解除注册了");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel() + "可以使用了");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// channel不活跃
System.out.println(db.get(ctx.channel()));
}
});
}
});
// netty中,所有异步的返回值,都需要用ChannelFuture来接收
ChannelFuture bindFuture = serverBootstrap.bind(8080);
bindFuture.addListener(f -> {
if (f.isSuccess()) {
System.out.println("服务器监听端口成功" + 8080);
} else{
System.out.println("服务器监听端口失败");
}
});
}
}
重构一下:
public class NettyServer {
public static void main(String[] args) {
// 增加服务器收到消息后,持久化到数据库以及客户端断开连接后,打印messageList中所有内容的功能
Map<Channel, List<String>> db = new ConcurrentHashMap<>();//模拟数据库
// 配置BootStrap,表示服务器接收到数据后,解成字符串并且打印
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new LineBasedFrameDecoder(1024))//以换行符为标准解包 按换行符来分割出一条一条消息
.addLast(new StringDecoder())//添加解码器 反序列化 得到String
.addLast(new StringEncoder())
.addLast(new responseHandler())
.addLast(new DBHandler(db));
}
});
// netty中,所有异步的返回值,都需要用ChannelFuture来接收
ChannelFuture bindFuture = serverBootstrap.bind(8080);
bindFuture.addListener(f -> {
if (f.isSuccess()) {
System.out.println("服务器监听端口成功" + 8080);
} else{
System.out.println("服务器监听端口失败");
}
});
}
static class responseHandler extends SimpleChannelInboundHandler<String> {
@Override //真正读取了消息的函数
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
// 服务端也可以给客户端发消息 需要出站处理器
String message = msg + " world\n";
ctx.channel().writeAndFlush(message);
ctx.fireChannelRead(msg);
}
}
static class DBHandler extends SimpleChannelInboundHandler<String> {
private Map<Channel, List<String>> db;
public DBHandler(Map<Channel, List<String>> db){
this.db = db;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
List<String> messageList = db.computeIfAbsent(ctx.channel(), k -> new ArrayList<>());
messageList.add(msg);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel() + "注册了");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel() + "解除注册了");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel() + "可以使用了");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// channel不活跃
System.out.println(db.get(ctx.channel()));
}
}
}
Client:
public class NettyClient {
public static void main(String[] args) throws InterruptedException{
Bootstrap bootstrap = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline()
.addLast(new LineBasedFrameDecoder(1024))
.addLast(new StringEncoder())//将来字符串编码成字节数组
.addLast(new StringDecoder())
.addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
});
}
});
ChannelFuture connect = bootstrap.connect("localhost", 8080);
connect.addListener(f -> {
if (f.isSuccess()) {
System.out.println("连接服务器8080成功");
EventLoop eventLoop = connect.channel().eventLoop();//EventLoop拥有执行定时任务的功能
// 每一秒钟写一个hello
eventLoop.scheduleAtFixedRate(() -> {
connect.channel().writeAndFlush("hello" + System.currentTimeMillis() + "\n");
}, 0, 1, TimeUnit.SECONDS);
} else{
System.out.println("连接服务器8080失败");
}
});
}
}