客户端
NettyClient
public class NettyClient {
private final static Logger LOGGER = LoggerFactory.getLogger(NettyClient.class);
public static void main(String[] args) throws InterruptedException {
// 首先,netty通过Bootstrap启动客户端,服务端一般使用ServerBootstrap
Bootstrap client = new Bootstrap();
//第1步 定义线程组,处理读写和链接事件,没有了accept事件
EventLoopGroup group = new NioEventLoopGroup();
client.group(group);
//第2步 绑定客户端通道
client.channel(NioSocketChannel.class);
//第3步 给NIoSocketChannel初始化handler, 处理读写事件
client.handler(new ChannelInitializer() { //通道是NioSocketChannel
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//字符串编码器,一定要加在SimpleClientHandler 的上面
pipeline.addLast(new StringEncoder());
pipeline.addLast(new DelimiterBasedFrameDecoder(
Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
//找到他的管道 增加他的handler
pipeline.addLast(new SimpleClientHandler());
}
});
//连接服务器
ChannelFuture future = client.connect("localhost", 8080).sync();
Channel channel = future.channel();
//代码分析的很复杂,但结论很简单:被Bootstrap引导的NioSocketChannel在构造好之后就进入了open状态,之后通过把自己注册进EventLoop进入registered状态,接着连接服务器进入active状态。
// channel.isOpen() --> channel.isRegistered() -->channel.isActive()
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()){
LOGGER.info("---客户端启动成功,连接的是===port:"+8080);
//发送数据给服务器
StationInforationCheck user = new StationInforationCheck();
user.setAcStakemanufactor("李静");
user.setAcStakeNum(12);
user.setDcStakemanufactor("LOL");
//发送给客户端信息
channel.writeAndFlush(JSONObject.toJSONString(user)+"\r\n");
for(int i=0;i<5;i++){
//这里发送的收必须是换行符之类的,不然不能编码和解码
String msg = "ssss"+i+"\r\n";
channel.writeAndFlush(msg);
}
}else {
Throwable cause = future.cause();
cause.printStackTrace();
}
}
});
//当通道关闭了,就继续往下走
channel.closeFuture().sync();
//接收服务端返回的数据
AttributeKey key = AttributeKey.valueOf("ServerData");
Object result = channel.attr(key).get();
System.out.println(result.toString());
}
}
SimpleClientHandler:clientd的业务处理类
public class SimpleClientHandler extends ChannelInboundHandlerAdapter {
public NettyClient nettyClient;
//向新的连接的发送连接问候
//当一个新的连接已经被建立时,channelActive(ChannelHandlerContext)将会被调用,随后才调用channelRead去读取
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// Send greeting for a new connection.
//InetAddress.getLocalHost().getHostName() 获取电脑主机名
ctx.write("Welcome to " + InetAddress.getLocalHost().getHostName() + "!\r\n");
ctx.write("It is " + new Date() + " now.\r\n");
ctx.flush();
}
//channelRead 读取远端消息的
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
String value = ((ByteBuf) msg).toString(Charset.defaultCharset());
System.out.println("服务器端返回的数据:" + value);
}
AttributeKey key = AttributeKey.valueOf("ServerData");
ctx.channel().attr(key).set("客户端处理完毕");
//把客户端的通道关闭
ctx.channel().close();
}
//channel关闭后触发这个方法
//这有两种可能,一种服务端主动 close,还有客户端 colse,你的 handler 里重写捕获异常了吗,如果没有捕获异常,则操作此 channel 的任何异常都会关闭此 channel
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("失败断开连接触发channelInactive方法==========");
closeChannelAndReConnect(ctx);
}
//在读取操作期间,有异常抛出时会调用。
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
cause.printStackTrace();
}
//通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息,此时会调用
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("这是读取的最后一条数据================================================================================================");
}
public void closeChannelAndReConnect(ChannelHandlerContext ctx){
Channel channel = ctx.channel();
if (channel !=null){
channel.close();
ctx.close();
}
//触发重连
}
}
服务端
NettyServer
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 首先,netty通过ServerBootstrap启动服务端,netty通过Bootstrap启动客户端
ServerBootstrap server = new ServerBootstrap();
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup =new NioEventLoopGroup();
//第1步定义两个线程组,用来处理客户端通道的accept和读写事件
//parentGroup用来处理accept事件,childgroup用来处理通道的读写事件
//parentGroup获取客户端连接,连接接收到之后再将连接转发给childgroup去处理
server.group(parentGroup, childGroup);
//用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
//用来初始化服务端可连接队列
//服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
server.option(ChannelOption.SO_BACKLOG, 128);
//第2步绑定服务端通道
server.channel(NioServerSocketChannel.class);
//第3步绑定handler,处理读写事件,ChannelInitializer是给通道初始化
server.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//解码器,接收的数据进行解码,一定要加在SimpleServerHandler 的上面
//maxFrameLength表示这一贞最大的大小
//delimiter表示分隔符,我们需要先将分割符写入到ByteBuf中,然后当做参数传入;
//需要注意的是,netty并没有提供一个DelimiterBasedFrameDecoder对应的编码器实现(笔者没有找到),因此在发送端需要自行编码添加分隔符,如 \r \n分隔符
pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
//把传过来的数据 转换成byteBuf
pipeline.addLast(new SimpleServerHandler());
}
});
//第4步绑定8080端口
ChannelFuture future = server.bind(8080).sync();
//当通道关闭了,就继续往下走
future.channel().closeFuture().sync();
}
}
SimpleServerHandler :服务端业务处理类
public class SimpleServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取客户端通道的数据 //channelRead 读取远端消息的
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//可以在这里面写一套类似SpringMVC的框架
//让SimpleServerHandler不跟任何业务有关,可以封装一套框架
if(msg instanceof ByteBuf){
System.out.println(((ByteBuf)msg).toString(Charset.defaultCharset()));
}
//业务逻辑代码处理框架。。。
//返回给客户端的数据,告诉客户端已经读到服务端数据了
String result = "hello client ";
//ByteBuf 用于存入数据和从里面读取数据,相当于申请了一块内存。
ByteBuf buf = Unpooled.buffer();
buf.writeBytes(result.getBytes());
ctx.channel().writeAndFlush(buf);
ByteBuf buf2 = Unpooled.buffer();
buf2.writeBytes("\r\n".getBytes());
ctx.channel().writeAndFlush(buf2);
System.out.println("==========");
}
}
其实客户端和服务端使用SimpleChannelInboundHandler 与 ChannelInboundHandler的问题:
SimpleChannelInboundHandler里主要源码:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
一般用netty来发送和接收数据都会继承SimpleChannelInboundHandler和ChannelInboundHandlerAdapter这两个抽象类,那么这两个到底有什么区别呢?
其实用这两个抽象类是有讲究的,在客户端的业务Handler继承的是SimpleChannelInboundHandler,而在服务器端继承的是ChannelInboundHandlerAdapter。
最主要的区别就是SimpleChannelInboundHandler在接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而服务器端有可能在channelRead方法返回前还没有写完数据,因此不能让它自动release。
在客户端,当 channelRead0()方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler 负责释放指向保存该消息的 ByteBuf 的内存引用。
在服务端中,你仍然需要将传入消息回送给发送者,而 write()操作是异步的,直到 channelRead()方法返回后可能仍然没有完成。为此,EchoServerHandler
扩展了 ChannelInboundHandlerAdapter,其在这个时间点上不会释放消息而是将消息传递给下一个ChannelHandler处理。消息在 EchoServerHandler 的 channelReadComplete()方法中,当 writeAndFlush()方法被调用时被释放。
从定义类上看:
public abstract class SimpleChannelInboundHandler extends ChannelInboundHandlerAdapter {
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler
从类的定义中,我们可以看出
SimpleChannelInboundHandler是抽象类,而ChannelInboundHandlerAdapter是普通类。
SimpleChannelInboundHandler支持泛型的消息处理,而ChannelInboundHandlerAdapter不支持泛型