版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/QQB67G8COM/article/details/90266509
//代码还是源于官方文档
package netty;
import java.util.Date;
//定义一个数据模型
//2208988800为1900年1月1日00:00:00~1970年1月1日00:00:00的总秒数
public class UnixTime {
private final long value;
public UnixTime() {
this(System.currentTimeMillis() / 1000L + 2208988800L);//计算时间的公式
}
public UnixTime(long value) {
this.value = value;
}
public long value() {
return value;
}
@Override
public String toString() {
return new Date((value() - 2208988800L) * 1000L).toString();
}
}
编码:将人类语言转化位机器语言为编码,即文字到0和1
解码:将机器语言转化为人类语言即解码
时间编码类:
package netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @description 编写一个编码器将UnixTime对象重新转换位一个ByteBuf
*/
//public class TimeEncoder extends ChannelOutboundHandlerAdapter {
//
// //第一,通过 ChannelPromise,当编码后的数据被写到了通道上 Netty 可以通过这个对象标记是成功还是失败。
// //第二, 我们不需要调用 cxt.flush()。因为处理器已经单独分离出了一个方法 void flush(ChannelHandlerContext cxt),如果像自己实现 flush() 方法内容可以自行覆盖这个方法,也就是默认会调用flush()
// @Override
// public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// UnixTime m = (UnixTime) msg;
// ByteBuf encoded = ctx.alloc().buffer();
// encoded.writeInt((int)m.value());
// ctx.write(encoded,promise);//(1)
//
// }
// @Override
// public void flush(ChannelHandlerContext ctx) throws Exception {
// super.flush(ctx);
// }
//}
/**
* @description 上面代码的简化操作
*/
public class TimeEncoder extends MessageToByteEncoder<UnixTime> {
@Override
protected void encode(ChannelHandlerContext ctx, UnixTime msg, ByteBuf out) throws Exception {
out.writeInt((int)msg.value());
}
}
时间解码类:
package netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* @description ByteToMessageDecoder是ChannelInboundHandler一个实现类,
* 他可以在处理数据拆分的问题上变得很简单。
* 每当有新数据接受的时候,ByteToMessageDecoder都会调用decode()方法来处理内部的那个积累的缓冲。
* Decode()方法可以决定当累积缓冲里没有足够数据时,可以往 out 对象里放任意数据。当有更多的数据被接
* 收了 ByteToMessageDecoder 会再一次调用 decode() 方法。
* 如果在 decode() 方法里增加了一个对象到 out 对象里,这意味着解码器解码消息成功。
* ByteToMessageDecoder 将会丢弃在累积缓冲里已经被读过的数据。请记得你不需要对多条消息调用
* decode(),ByteToMessageDecoder 会持续调用 decode() 直到不放任何数据到 out 里。
*/
public class TimeDecoder extends ByteToMessageDecoder { //(1)
//还可以通过继承解码类ReplayingDecoder<Void>
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { //(2)
if (in.readableBytes() < 4) {
return; //(3)
}
long inc = in.readUnsignedInt();//in只能读取一次,因此需要一个变量承载读取的内容来进行多次使用
System.out.println("ClientTimeDecoder:" + new UnixTime(inc));
// out.add(in.readBytes(4)); //(4)
out.add(new UnixTime(inc));
}
}
TIME服务器(main和handler写在一起了):
package netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @description 时间服务器,实现TIME协议,它只发送包含完整的32位数的消息,而不接受任何请求,
* 并且在发送消息之后关闭连接
*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
/**
//method(1)
//当建立连接准备产生流量的时候调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { //(1)
final ByteBuf time = ctx.alloc().buffer(); //(2)分配一个包含消息的新缓冲区
time.writeInt((int)(System.currentTimeMillis()/1000L + 2208988800L));
final ChannelFuture f = ctx.writeAndFlush(time);//(3)表示的是尚未发生的I/O
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
assert f == channelFuture;
System.out.println("sys closing,");
ctx.close();
}
});//(4)
// f.addListener(ChannelFutureListener.CLOSE);//(4)使用预定义的侦听器简化代码
//以下的写法可能在消息发出去的时候管道就会被关闭,因此Netty的所有操作都是异步的
// Channel ch = ...;
// ch.writeAndFlush(time);
// ch.close();
}*/
//method(2)
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(" server channel active... ");
ChannelFuture f = ctx.writeAndFlush(new UnixTime());
f.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelRead..");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup,workerGroup )
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new TimeEncoder(), new TimeServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = sb.bind(8088).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
TimeClientHandler:
package netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/**
* @description 时间客户端处理器。基于流的传输比如TCP/IP,接收到数据是存在socket接受的buffer中。
* 不幸的是,基于流传输的并不是一个数据包队列,而是一个字节队列。意味着,即使你发送了2个独立的数据包,
* 操作系统也不会作为2个消息处理,而仅仅作为一串字符串。因此这是不能保证你远程写入的数据就会准确地读取。
* 举个例子,让我们的操作系统的TCP/IP协议栈已经接受了3个数据包:
* 由于基于流传输的协议这种普通的性质,在你写的应用程序里读取数据的时候会有很高可能性被分段,因此,一个
* 接受方不管他是客户端还是服务端,都应该把接收到的数据整理成一个或者多个更有意思并且让程序的业务逻辑更
* 好理解的数据。
* TIME客户端例子:32位整形是非常小的数据,并不见得会被经常拆分到不同的数据段。然而,问题是他确实可能被
* 拆分到不同的数据段内,并且拆分的可能性会随着通信量的增加而增加。
*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
//method(1)
// private ByteBuf buf;
// @Override
// public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// buf = ctx.alloc().buffer(); //(1)
// }
//
// @Override
// public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// buf.release(); //(1)
// buf = null;
// }
// 处理器必须检查buf变量是否有足够的数据,在这个例子中是4个字节,然后处理实际的业务逻辑。否则,Netty
// 会重复调用channelRead当有更多数据到达知道4个字节的数据被积累。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//method(1)
// ByteBuf m = (ByteBuf) msg; //
// buf.writeBytes(m);//(2)
// m.release();
//
// if (buf.readableBytes() >= 4) { // (3)
// long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L) * 1000L;
// System.out.println(new Date(currentTimeMillis));
// ctx.close();
// }
//method(2)
UnixTime m = (UnixTime)msg;
System.out.println(m);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
TimeClient:
package netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
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.NioSocketChannel;
/**
* @description 时间客户端,和时间服务器的最大和唯一区别就是不同的Bootstrap和Channel实现。
* 不同于DISCARD于ECHO服务器,我们需要一个客户端TIME协议,因为人不能将32位的二进制数据转换
* 成日历上的日期。
*/
public class TimeClient {
public static void main(String[] args) throws Exception {
String host = "172.16.137.11";
int port = Integer.parseInt("8088");
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap(); // (1)
b.group(workerGroup); // (2)
b.channel(NioSocketChannel.class); // (3)
b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());
}
});
// 启动客户端,客户端连接到服务器是通过connect()的.
ChannelFuture f = b.connect(host, port).sync(); // (5)
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
//关闭一个 Netty 应用往往只需要简单地通过 shutdownGracefully() 方法
// 来关闭你构建的所有的 EventLoopGroup。当EventLoopGroup 被完全地终
// 止,并且对应的所有 channel 都已经被关闭时,Netty 会返回一个Future对象来通知你。
workerGroup.shutdownGracefully();
}
}
}