第一节简单提了什么是网络编程,Netty 做了什么,Netty 都有哪些功能组件。这一节就具体进入 Netty 的世界,我们从用 Netty 的功能实现基本的网络通信开始分析 各个组件的使用。
1. 一个简单的发送接收消息的例子
话不多说,先来实现一个发送接收消息的例子。本实例基于 SpringBoot 工程搭建。
项目类文件如下:

客户端和服务端的主要代码分为3个部分:启动器,ChannelInitializer,eventHandler。
相关代码已经上传 GitHub,请参阅:点我 (๑¯ ³ ¯๑)
Server端:
package com.rickiyang.learn.helloWorld;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
/**
* @author: rickiyang
* @date: 2020/3/15
* @description: server 端
*/
@Slf4j
public class HwServer {
private int port;
public HwServer(int port) {
this.port = port;
}
public void start(){
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap().group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer());
try {
ChannelFuture future = server.bind(port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server start fail",e);
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
HwServer server = new HwServer(7788);
server.start();
}
}
server initializer:
package com.rickiyang.learn.helloWorld;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
/**
* @author: rickiyang
* @date: 2020/3/15
* @description:
*/
@Slf4j
public class HwServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("server channelActive");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info(ctx.channel().remoteAddress()+"===>server: "+msg.toString());
ctx.write("hi, received your msg");
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
}
server handler:
package com.rickiyang.learn.helloWorld;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
/**
* @author: rickiyang
* @date: 2020/3/15
* @description:
*/
@Slf4j
public class HwServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("server channelActive");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info(ctx.channel().remoteAddress()+"===>server: "+msg.toString());
ctx.write("hi, received your msg");
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
}
客户端代码:
client:
package com.rickiyang.learn.helloWorld;
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;
import lombok.extern.slf4j.Slf4j;
/**
* @author: rickiyang
* @date: 2020/3/15
* @description:
*/
@Slf4j
public class HwClient {
private int port;
private String address;
public HwClient(int port, String address) {
this.port = port;
this.address = address;
}
public void start(){
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ClientChannelInitializer());
try {
ChannelFuture future = bootstrap.connect(address,port).sync();
future.channel().writeAndFlush("Hello world, i'm online");
future.channel().closeFuture().sync();
} catch (Exception e) {
log.error("client start fail",e);
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
HwClient client = new HwClient(7788,"127.0.0.1");
client.start();
}
}
client initializer :
package com.rickiyang.learn.helloWorld;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* Created by Administrator on 2017/3/11.
*/
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
// 客户端的逻辑
pipeline.addLast("handler", new HwClientHandler());
}
}
client handler :
package com.rickiyang.learn.helloWorld;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
/**
* @author: rickiyang
* @date: 2020/3/15
* @description:
*/
@Slf4j
public class HwClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("server say : " + msg.toString());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("client channelActive");
ctx.fireChannelActive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("Client is close");
}
}
代码很简单,主要功能就是启动 服务端 和 客户端, 然后实现一个简单的 handler ,在handler 中获取消息并打印,本地先启动服务端 main 函数,再启动客户端即可。
2. 从 EventLoopGroup 开始说起
观看客户端 和 服务端 的启动类,我们看到都有相同的特性:
创建 EventLoopGroup 去监听 channel,然后使用定义的 handler处理对应的事件。
Netty 是一个异步事件驱动的 NIO 框架,所有IO操作都是异步非阻塞的。Netty 实际上是使用 Threads(多线程)处理 I/O 事件。
EventLoopGroup 是个啥东西呢?我们回想一下 Reactor 模型,主要的操作是使用 Selector 监听 channel 上的事件,Reactor 模型有三种结构,首先是单线程模型:

这种模型显而易见始终只有一个 Acceptor 线程在处理客户端连接事件和服务端产生的读写事件,好处是始终只有一个线程在工作不会产生并发带来的一系列问题。但是不足之处也显而易见:
- 一个线程来处理对于现在的多核系统来说有点浪费资源;
- 虽然是使用异步非阻塞I/O处理,但是面对大并发的请求场景,很有可能会负载过重,堆积事件,这样客户端就会有超时发生,然后重复发送请求,必然会造成系统超载;
- 单线程如果挂掉了系统就停止了,这种场景如何处理。
所以这种单线程模型对于当今系统的发展是没有适用场景的。接着又演变出多线程的 Reactor 模型。

在多线程模型下,Acceptor 是一个单独的线程专门处理 Client 的请求连接事件,所有的 I/O 操作都由一个特定的 NIO 线程池负责,每个客户端都与一个特定的 NIO 线程池绑定,因此这个客户端连接中的所有 I/O 操作都是在同一个线程中完成的。客户端连接是很多的,但是 NIO 线程很少,所以 一个 NIO 线程可以同时绑定到多个客户端连接中。
从上面的模型找缺点的话,很显然能发现还是有单点的问题:处理客户端连接请求的线程仍旧只有一个,如果这个线程挂了,整个系统将不可用。所以这种超级并发的情况也要考虑啊,系统不能有单点,接着改:

现在的系统就没有单点问题了,但是也增加了复杂性。
我们刚才在说 EventLoopGroup ,为啥突然又转到了 Reactor 模型上去了呢? 前面说过,Netty 是基于 NIO 编程的,NIO 又是基于 Reactor 模型的,自然 Netty 的编程模型也是 Reactor 。而 EventLoopGroup 其实就是来设置 Reactor 模型的类型根据不同的参数方式。
我们来看 Server 启动类中的写法:
EventLoopGroup bossGroup = new NioEventLoopGroup


3844

被折叠的 条评论
为什么被折叠?



