简介
本案例会从两个简单的demo入手,从无到有手写一个netty简单的服务端和客户端,体验一下netty的强大和简便。
案例源码:https://github.com/itwwj/netty-learn.git 中的 netty-day02-introduction项目
一、服务端
1.1启动类
/**
* netty服务端启动类
*
* @author jie
*/
public class NettyServer {
/**
* 设置端口号
*/
private static int port = 1100;
public static void main(String[] args) {
//用于处理服务端接受客户端连接
EventLoopGroup boosGroup = new NioEventLoopGroup();
//用于SocketChannel的网络读写操作
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度
ServerBootstrap b = new ServerBootstrap();
b.group(boosGroup, workerGroup)
//对应JDK NIO类库中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
//配置NioServerSocketChannel的TCP参数
.option(ChannelOption.SO_BACKLOG, 1024)
//绑定I/O的事件处理类
.childHandler(new MyServerChannelInitializer());
//调用它的bind操作监听端口号,调用同步阻塞方法sync等待绑定操作完成
ChannelFuture f = b.bind(port).sync();
//异步操作的通知回调
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅的退出,释放线程池资源
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
说明:
NioEventLoopGroup: 在代码的一开始创建了两个NioEventLoopGroup实例。NioEventLoopGroup是线程组,它包含了一组NIO线程,专门用于网络事件的处理,实际上它们就是Reactor线程组.这里创建两个的原因是一个用于服务端接受客户端连接,一个用于SocketChannel的网络读写操作。
ServerBootstrap: 是用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度,ServerBootstrap的group方法,将两个NIO线程组当做参数传递到ServerBootstrap中,接着设置创建Channel为NioServerSocketChannel,它的功能是对应JDK NIO类库中的ServerSocketChannel,然后配置NioServerSocketChannel的TCP参数,将他的backlog设置为1024,最后绑定I/O的事件处理类:MyServerChannelInitializer,主要作用为处理消息编解码及消息的粘包及io操作。
启动辅助类完成以后,调用它的bind操作监听端口号,随后调用同步阻塞方法sync等待绑定操作完成,完成之后会返回一个ChannelFuture,用于异步操作的通知回调
1.2事件处理类:
/**
*
* MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始
* 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己
* 从pipeline中移除,不会影响后续的操作。
* @author jie
*/
public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* 这个方法在Channel被注册到EventLoop的时候会被调用
* @param socketChannel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
System.out.println("=========有客户端连接服务器=========");
System.out.println("ip:"+socketChannel.localAddress().getHostString()+" port:"+socketChannel.localAddress().getPort());
socketChannel.pipeline().addLast(new MyServerHandler());
}
}
事件处理类,继承了抽象类ChannelInitializer ,类图:
MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己从pipeline中移除,不会影响后续的操作。
继承ChannelInitializer需要重写方法 initChannel ,initChannel 在Channel被注册到EventLoop的时候会被调用,可以在initChannel 中通过对socketChannel的操作进行对消息的编码、解码、以及自定义消息的处理。
1.3 事件操作类
/**
* 操作类
*
* @author jie
*/
public class MyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端主动连接服务端,通道活跃后触发
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//在接收到客户端连接的时候通知客户端连接成功
String msg = "与服务端建立连接成功" + new Date();
ByteBuf buf = Unpooled.buffer(msg.getBytes().length);
buf.writeBytes(msg.getBytes("utf-8"));
ctx.writeAndFlush(buf);
}
/**
* 通道有消息触发
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接收msg消息
ByteBuf buf = (ByteBuf) msg;
byte[] msgByte = new byte[buf.readableBytes()];
buf.readBytes(msgByte);
System.out.print(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "接收到消息:");
System.out.println(new String(msgByte, Charset.forName("utf-8")));
}
/**
* 当客户端主动断开连接,通道不活跃触发
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("===================客户端:" + ctx.channel().localAddress().toString() + " 断开连接===================");
}
/**
* 当连接发生异常时触发
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//在发生异常时主动关掉连接
ctx.close();
System.out.println("发现异常:\r\n" + cause.getMessage());
}
}
在介绍此类的作用之前先来看一下父类ChannelInboundHandlerAdapter
在这里主要介绍ChannelInboundHandlerAdapter的以下几个常用方法:
channelActive: 当客户端主动连接服务端,通道活跃后触发
channelRead: 通道有消息触发
channelInactive: 当客户端主动断开连接,通道不活跃触发
exceptionCaught: 当连接发生异常时触发
二、客户端
2.1启动类
/**
* netty客户端启动类
*
* @author jie
*/
public class NettyClient {
/**
* 服务端ip
*/
private static String ip = "127.0.0.1";
/**
* 服务端监听端口
*/
private static int port = 1100;
public static void main(String[] args) {
//用于SocketChannel的网络读写操作
EventLoopGroup group = new NioEventLoopGroup();
try {
//客户端启动辅助类
Bootstrap b = new Bootstrap();
b.group(group).
//设置为NioSocketChannel
channel(NioSocketChannel.class).
option(ChannelOption.AUTO_READ, true).
//事件处理类
handler(new MyClientChannelInitializer());
//调用它的connect操作连接服务端,调用同步阻塞方法sync等待绑定操作完成
ChannelFuture future = b.connect(ip, port).sync();
//异步操作的通知回调
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅的退出,释放线程池资源
group.shutdownGracefully();
}
}
}
客户端的创建与服务端很相像,客户端在创建时没有用于处理连接请求的线程组,只有一个处理I/O事件的线程组,另外在启动辅助类的创建上也有所不同,客户端用的是Bootstrap,服务端用的ServerBootstrap。客户端启动辅助类设置完成后调用的是connect方法发起异步连接,然后调用同步方法等待连接成功
2.2事件处理类:
/**
*
* MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始
* 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己
* 从pipeline中移除,不会影响后续的操作。
* @author jie
*/
public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* 这个方法在Channel被注册到EventLoop的时候会被调用
* @param socketChannel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
System.out.println("=========连接到服务端=========");
System.out.println("channelId:"+socketChannel.id());
socketChannel.pipeline().addLast(new MyClientHandler());
}
}
此事件处理类与服务端功能一致
2.3事件操作类
/**
* 客户端事件操作类
* @author jie
*/
public class MyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端主动链接服务端的链接后,通道就是活跃的了此时触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//通知服务端链接建立成功
String str = "与客户端链接建立成功" + " " + new Date();
ctx.writeAndFlush(str);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接收msg消息
ByteBuf buf = (ByteBuf) msg;
byte[] msgByte = new byte[buf.readableBytes()];
buf.readBytes(msgByte);
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 接收到消息:" );
System.out.println(new String(msgByte, Charset.forName("utf-8")));
}
/**
* 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("===================" + ctx.channel().localAddress().toString() + " 断开连接===================");
}
/**
* 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
System.out.println("发现异常:\r\n" + cause.getMessage());
}
}
此类与服务端功能及用法一致
案例源码:https://github.com/itwwj/netty-learn.git 中的 netty-day02-introduction项目
上一篇:
io模型及nio进阶
下一篇:
netty(二)–解决粘包拆包及编码解码问题