通过前面的文章我们可以了解Nio以及Nio中使用的Reactor模式和多路复用器,这是非常必要的因为netty是java编写的一个通讯框架,底层也是使用jdk提供的api来和操作系统交互的,所以这么来说前面两章其实也是学习了netty最底层的实现,下面来看看netty实现通讯……
一、客户端
package com.test3;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* 客户端
* @author whd
* @date 2017年9月24日 上午12:26:34
*/
public class Client {
public void start(String host, int port) {
// Netty中的线程池,管理者eventLoop,之前说过一个EventLoop对应着一个Thread线程
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 引导类,这个类负责Netty客户端启,在启动时相关类的初始化
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
// 设置底层通讯方式,可以是BioSocketChannel也可以是EpollSocketChannel
// 这就是Netty的好处之一,我们变化不同的通讯方式的时候只需要修改很少的一部分,或者使用一个简单的配置文件就OK了,几乎不用修改代码。
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true); // 其实就是设置tcp的相关属性
bootstrap.handler(new InitializerChannel()); // 设置channel处理类
// 向指定的ip+port进行tcp连接
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
System.out.println(e.getMessage());
;
} finally {
// 连接的优雅关闭,和java线程池shutdown相似,在调用关闭方法后不接受其他请求但也不会立刻关闭知道已有任务执行完才会关闭!!!
workerGroup.shutdownGracefully();
}
}
// channelHandler的初始化,这个类中添加的就是Netty的请求处理类。
// 也就是之前我们学习的Netty的ChannelHandler、ChannelPipeline
// ,我们知道netty中所有的请求事件处理类都在容器ChannelPipeline中。
// 这里就是是channel相关的……
private class InitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// TODO Auto-generated method stub
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ClientHandler());
}
}
public static void main(String[] args) {
String ip = "127.0.0.1";
int port = 8080;
if (null != args && args.length == 2) {
ip = args[0];
port = Integer.valueOf(args[1]);
}
Client client = new Client();
client.start(ip, port);
}
}
二、客户端处理类
package com.test3;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 客户端对请求响应事件的处理
* @author whd
* @date 2017年9月24日 上午12:27:22
*/
public class ClientHandler extends ChannelHandlerAdapter {
// client 连接 server server返回消息触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
byte[] req = "client active".getBytes();
ByteBuf buf = Unpooled.buffer(req.length);
buf.writeBytes(req);
ctx.writeAndFlush(buf);// 将请求消息发送给服务端
}
// 该channel有读事件发生时触发该方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println(body);
}
// 当读事件结束后触发该方法
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();// 将消息发送队列中的消息写入到SocketChannel中发送给对方。
ctx.close();
}
// 出现异常触发该方法
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
三、服务端
package com.test3;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* 服务端
* @author whd
* @date 2017年9月24日 上午12:27:08
*/
public class Server {
public void bind(int port) throws InterruptedException {
// 配置NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();// 连接管理线程池
EventLoopGroup workerGroup = new NioEventLoopGroup();// 具体业务处理线程池
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG, 10);
bootstrap.childHandler(new ChildChannelHandler());
// 绑定端口,同步等待成功
ChannelFuture future = bootstrap.bind(port).sync();
// 等待服务端监听端口关闭,等待服务端链路关闭之后main函数才退出
future.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());
}
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
if (args != null && args.length > 0) {
port = Integer.parseInt(args[0]);
}
new Server().bind(port);
}
}
四、服务端处理类
package com.test3;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 对客户端请求事件的处理
* @author whd
* @date 2017年9月24日 上午12:27:53
*/
public class ServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];// 获得缓冲区可读的字节数
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("from client info:" + body);
String currentTime = "server time " + new Date(System.currentTimeMillis()).toString();
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);// 性能考虑,仅将待发送的消息发送到缓冲数组中,再通过调用flush方法,写入channel中
buf.release();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();// 将消息发送队列中的消息写入到SocketChannel中发送给对方。
ctx.close();
}
}
ok上面代码就是一个Netty通讯框架的基本demo在不考虑tcp粘包拆包、传输文件大小以及序列化等问题的情况下可以使用这个demo进行socket通讯了……
和前一篇java Nio实现socket通讯demo相比这里的代码量比较少,而且我们不用从服务器中循环遍历获取触发的事件、不用判断channel中的事件类型、不用从inputstream中获取byte在进行转化,这些所有的通用的操作netty都给我们做了,我们只需要写具体业务就OK而且在类型转化部分以及底层的传输类型改变都很简单,传输类型的改变我们在client代码中有注释而类型的转化即编码解码在后面的代码中也会体现出来
一、客户端
package com.test;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
*
* @author whd
* @date 2017年9月24日 上午1:11:42
*/
public class HelloWorldClient {
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
public void start() throws Exception {
// Configure the client.
// 线程池,用来处理请求资源
// 其实这种方式就是开启了不同的传输方式,如果是nio传输则说明使用的是nio方式也就是select或poll方式而如果是EpollEventLoopGroup
// 则使用的是linux的epoll方式
EventLoopGroup work = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(work).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true).handler(new InitializerChannel());
// 获取通道连接
ChannelFuture future = b.connect(HOST, PORT).sync();
future.channel().closeFuture().sync();
} finally {
work.shutdownGracefully();
}
}
// 初始化事件处理类,之前的demo中只有我们自己写的一个处理类,但这里有另外两个字符串解码器和字符串编码器,他们都添加了ChannelPipeline中,而且是有顺序的编码器解码器是在我们自己写的处理器之前
// 字符串编码器,解码器是netty提供的公用处理类,类似的还有很多到后面我们在来看,这里写这个只是想说netty框架的设计确实方便了开发,我们需要的功能只需要添加到ChannelPipeline中就可以……
private class InitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("decoder", new StringDecoder());
p.addLast("encoder", new StringEncoder());
p.addLast(new HelloWorldClientHandler());
}
}
public static void main(String[] args) {
}
}
二、客户端处理类
package com.test;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author whd
* @date 2017年9月24日 上午1:12:33
*/
public class HelloWorldClientHandler extends ChannelHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 因为我们使用了字符串编码器,所以这里可以直接写字符串,netty的字符串编码器会帮我们把string转变为ByteBuf,在netty中使用ByteBuf交互的而不是ByteBuffer
// 而最底层是使用的ByteBuffer,所以netty会吧ByteBuf转变为ByteBuffer的……
ctx.channel().writeAndFlush("Hello Netty Server ,I am a common client");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(msg.toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
三、服务端
package com.test;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.net.InetSocketAddress;
/**
*
* @author whd
* @date 2017年9月24日 上午1:11:58
*/
public class HelloWorldServer {
private int port;
public HelloWorldServer(int port) {
this.port = port;
}
public void start() {
// 多路复用的话其实是底层操作系统帮我们干的,会调用epoll来帮我们做的,当有请求来的时候会调用callback函数之前是对所有的channel进行的select而现在是事件驱动的方式实现的。
EventLoopGroup bossGroup = new NioEventLoopGroup(10); // 多路复用线程池,指定线程个数,这个eventloop用来处理请求事件是串行执行
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 线程池,进行工作的,这个是使用了默认的线程池
try {
// serverBootstrap就是一个netty启动类,用来实现启动管理所有部分
ServerBootstrap sbs = new ServerBootstrap().group(bossGroup, workerGroup);
sbs.channel(NioServerSocketChannel.class);// 设置通道,这个通道就是和javanio中的serverscoketchannel一样是进行端口监听的
sbs.localAddress(new InetSocketAddress(port)).childHandler(new InitializerChannel())
.option(ChannelOption.SO_BACKLOG, 128) // option设置tcp协议的一些参数,backlog设置tcp队列的长度,如果请求存储长度超出这个长度则,不接受这个请求
.childOption(ChannelOption.SO_KEEPALIVE, true); // 设置长连接
// 绑定端口,开始接收进来的连接
ChannelFuture future = sbs.bind(port).sync();
future.channel().closeFuture().sync(); // 同步关闭
} catch (Exception e) {
// 关闭连接池,释放资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class InitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// socketChannel就是进行客户端的连接通道的获取
// 配置了三个处理器,解码器、编码器、和自定义的处理器
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
// 具体定义的处理器,处理客户的需求,当请求来的时候回触发这个处理器中的相关
ch.pipeline().addLast(new HelloWorldServerHandler());
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new HelloWorldServer(port).start();
}
}
四、服务端处理类
package com.test;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 具体处理器,这个是用来做程序相关处理的
* netty是事件驱动的异步触发的,所有的请求都是eventloop来处理的一个eventloop对应一个thread
*
* @author whd
* @date 2017年9月24日 上午1:12:07
*/
public class HelloWorldServerHandler extends ChannelHandlerAdapter {
// 读事件的触发
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("from client info " + msg.toString());
ctx.write("hello client this info from server");
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("服务端接收客户端请求");
}
// 异常事件的触发
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
// ctx中保存着channel 和channel的事件,ctx.close()方法就是关闭客户端连接
ctx.close();
}
// 读取结束后触发这个事件
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
ctx.close();
}
}
OK 这个demo你能看到netty给拱的字符编码器和解码器的使用,以及ChannelPipeline 中添加处理类的方便性,从这些你可以看到使用框架的好处,框架就是封装底层的和业务无关的细节只对外提供业务相关的api或扩展接口已实现快速开发以及简化开发技术,其实这些已经在netty中有所体现,还有比如tcp的粘包拆包netty也有提供,对http协议和websocket的支持都有提供,确实很方便开发,而且能快速开发出稳定高效的功能模块,但问题来了如果只是了解框架的api和使用这些api不了解框架底层的实现则出了问题一脸懵逼不知如何解决、第二做性能优化也无从下手,框架给的默认值一般的大众化的,但我们要按照我们自己的需要进行参数调整优化,所以我们有必要深入学习框架底层的实现原理,从下一篇开始我们就进入netty源码学习。
哦对了我们的demo和源码学习都是基于Netty5.+版本的……
netty的jar包下载:点击打开链接