Netty介绍
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
Netty框架的组成
第一个Netty程序
服务器端
/**
* 服务端
*/
public class Server {
public static void main(String[] args) throws Exception {
//1.创建两个线程组
//用于处理服务器端接收客户端连接
EventLoopGroup pGroup = new NioEventLoopGroup();
//用于进行网络通信
EventLoopGroup cGroup = new NioEventLoopGroup();
try {
//2.创建辅助工具类,用于服务器通道的配置
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup) //绑定两个线程组
.channel(NioServerSocketChannel.class) //指定NIO模式
.option(ChannelOption.SO_BACKLOG, 1024) //设置tcp缓冲区
.option(ChannelOption.SO_SNDBUF, 32*1024) //设置发送缓冲大小
.option(ChannelOption.SO_RCVBUF, 32*1024) //设置接收缓冲大小
.option(ChannelOption.SO_KEEPALIVE, true) //保持连接
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//3.配置接收方法的处理
sc.pipeline().addLast(new ServerHandler());
}
});
//4.进行绑定,调用sync()方法会阻塞直到服务器完成绑定
ChannelFuture cf = b.bind(9000).sync();
//5.等待关闭
cf.channel().closeFuture().sync();
} finally {
//6.释放资源
pGroup.close();
cGroup.close();
}
}
}
服务器业务逻辑
/**
* 服务器处理方法
*/
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("服务器通道激活...");
}
@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("Server: " + body);
String response = "返回给客户端的响应: " + body;
ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("读取数据完毕");
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Handler很简单,它的每个方法都可以被重写,它的所有的方法中只有channelRead方法是必须要重写的。
实现服务器步骤:
- 配置服务器功能,如线程、端口
- 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么
客户端
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture cf = b.connect("127.0.0.1",9000).sync();
//发送信息
cf.channel().writeAndFlush(Unpooled.copiedBuffer("hello server".getBytes()));
cf.channel().closeFuture().sync();
} finally{
group.shutdownGracefully();
}
}
}
客户端的业务逻辑
/**
* 客户端处理方法
*/
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"utf-8");
System.out.println("Client: " + body);
String response = "服务器端返回信息: " + body;
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
}
}
- channelActive():客户端连接服务器后被调用
- channelRead():从服务器接收到数据后调用
- exceptionCaught():发生异常时被调用
Netty核心概念
- Bootstrap or ServerBootstrap
- EventLoop
- EventLoopGroup
- ChannelPipeline
- Channel
- Future or ChannelFuture
- ChannelInitializer
- ChannelHandler
ChannelInitializer
ChannelInitializer类用来配置Handlers。ChannelInitializer是通过ChannelPipeline来添加ChannelHandler的,如发送和接收消息,这些Handlers将确定发的是什么消息。ChannelInitializer自身也是一个ChannelHandler,在添加完其他的handlers之后会自动从ChannelPipeline中删除自己。
Future or ChannelFuture
Future注册一个监听,当操作成功或失败时会通知。ChannelFuture封装的是一个操作的相关信息,操作被执行时会立刻返回ChannelFuture。
Channel
EventLoop就是一个Channel执行实际工作的线程。EventLoop总是绑定一个单一的线程,在其生命周期内不会改变。当注册一个Channel后,Netty将这个Channel绑定到一个EventLoop,在Channel的生命周期内总是被绑定到一个EventLoop。
Bootstrap or ServerBootstrap
“引导”是Netty中配置程序的过程,当你需要连接客户端或服务器绑定指定端口时需要使用bootstrap。如前面所述,“引导”有两种类型,一种是用于客户端的Bootstrap(也适用于DatagramChannel),一种是用于服务端的ServerBootstrap。
差异:
- Bootstrap用来连接远程主机,有1个EventLoopGroup
- ServerBootstrap用来绑定本地端口,有2个EventLoopGroup
第一个差异很明显,“ServerBootstrap”监听在服务器监听一个端口轮询客户端的“Bootstrap”或DatagramChannel是否连接服务器。通常需要调用“Bootstrap”类的connect()方法,但是也可以先调用bind()再调用connect()进行连接,之后使用的Channel包含在bind()返回的ChannelFuture中。
第二个差别也许是最重要的。客户端bootstraps/applications使用一个单例EventLoopGroup,而ServerBootstrap使用2个EventLoopGroup(实际上使用的是相同的实例),它可能不是显而易见的,但是它是个好的方案。一个ServerBootstrap可以认为有2个channels组,第一组包含一个单例ServerChannel,代表持有一个绑定了本地端口的socket;第二组包含所有的Channel,代表服务器已接受了的连接。
EventLoop与EventLoopGroup
EventLoopGroup可以包含很多个EventLoop,每个Channel绑定一个EventLoop不会被改变,因为EventLoopGroup包含少量的EventLoop的Channels,很多Channel会共享同一个EventLoop。这意味着在一个Channel保持EventLoop繁忙会禁止其他Channel绑定到相同的EventLoop。我们可以理解为EventLoop是一个事件循环线程,而EventLoopGroup是一个事件循环集合。
ChannelPipeline与 ChannelHandler
ChannelPipeline的作用我们可以理解为用来管理ChannelHandler的一个容器,每个ChannelHandler处理各自的数据(例如入站数据只能由ChannelInboundHandler处理),处理完成后将转换的数据放到ChannelPipeline中交给下一个ChannelHandler继续处理,直到最后一个ChannelHandler处理完成。
ChannelHandler是一段执行业务逻辑处理数据的代码,它们来来往往的通过ChannelPipeline。
当一个ChannelHandler添加到ChannelPipeline中时获得一个ChannelHandlerContext。通常是安全的获得这个对象的引用,但是当一个数据报协议如UDP时这是不正确的,这个对象可以在之后用来获取底层通道,因为要用它来read/write消息,因此通道会保留。
Netty中发送消息有两种方法:直接写入通道或写入ChannelHandlerContext对象。
这两种方法的主要区别如下:
- 直接写入通道导致处理消息从ChannelPipeline的尾部开始
- 写入ChannelHandlerContext对象导致处理消息从ChannelPipeline的下一个handler开始
拆包、黏包
粘包、拆包的解决方案:
1. 将消息分为消息头和消息体,在消息头中包含表示消息总长度的字段,然后进行业务逻辑处理。
2. 在包尾部添加特殊字符进行分隔,分隔符类DellmiterBasedFrameDecoder(自定义分隔符)。
3. 消息定长,如果不够,空位补空格,FixedLengthFrameDecoder(定长) 。
消息定长
服务端
/**
* 服务端
*/
public class Server {
public static void main(String[] args) throws Exception {
//1.创建两个线程
EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup();
try {
//2.创建服务器辅助类
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_SNDBUF, 32*1024)
.option(ChannelOption.SO_RCVBUF, 32*1024)
.option(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//设置定长字符串接收
sc.pipeline().addLast(new FixedLengthFrameDecoder(5));
//设置字符串形式的解码
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ServerHandler());
}
});
//绑定连接
ChannelFuture cf = b.bind(9002).sync();
//等待服务器监听端口关闭
cf.channel().closeFuture().sync();
} finally {
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
}
客户端
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new FixedLengthFrameDecoder(5));
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ClientHandler());
}
});
//绑定端口
ChannelFuture cf = b.connect("127.0.0.1",9002).sync();
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("aaaaaabbbbbbb ".getBytes()));
cf.channel().writeAndFlush(Unpooled.copiedBuffer("cccccc ".getBytes()));
//等待客户端端口关闭
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
服务端、客户端业务逻辑不变
尾部添加特殊字符
服务端
/**
* 服务端
*/
public class Server {
public static void main(String[] args) throws Exception {
//1.创建两个线程
//负责接收客户端的连接
EventLoopGroup pGroup = new NioEventLoopGroup();
//负责进行数据传输
EventLoopGroup cGroup = new NioEventLoopGroup();
try {
//2创建服务辅助类
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_SNDBUF, 32*1024)
.option(ChannelOption.SO_RCVBUF, 32*1024)
.option(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//3.设置分隔符,解决拆包粘包问题
ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buf));
//设置字符串形式的解码
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ServerHandler());
}
});
//4.绑定连接
ChannelFuture cf = b.bind(9001).sync();
//5.等待服务器监听端口关闭
cf.channel().closeFuture().sync();
} finally {
//6.释放资源
pGroup.close();
cGroup.close();
}
}
}
/**
* 服务器端处理方法
*/
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("服务器端通道激活。。。");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String request = (String) msg;
System.out.println("Server: " + msg);
String response = "服务器响应: " + msg + "$_";
ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//处理分隔符
ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buf));
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ClientHandler());
}
});
//连接服务端
ChannelFuture cf = b.connect("127.0.0.1",9001).sync();
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("aa$_".getBytes()));
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("bb$_".getBytes()));
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("ccc$_".getBytes()));
//等待客户端端口关闭
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
/**
* 客户端处理方法
*/
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端通道激活。。。");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
String response = (String) msg;
System.out.println("Client: " + response);
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
编解码器
解码器负责将消息从字节或其他序列形式转成指定的消息对象,编码器则相反;解码器负责处理“入站”数据,编码器负责处理“出站”数据。
压缩解压
/**
* 压缩解压工具类
*/
public class GzipUtils {
/**
* 压缩
* @param data
* @return
* @throws Exception
*/
public static byte[] gzip(byte[] data) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] ret;
try {
GZIPOutputStream gzip = new GZIPOutputStream(bos);
gzip.write(data);
gzip.finish();
gzip.close();
ret = bos.toByteArray();
} finally {
if (bos != null) {
bos.close();
}
}
return ret;
}
/**
* 解压
* @param data
* @return
* @throws Exception
*/
public static byte[] unzip(byte[] data) throws Exception{
ByteArrayInputStream bis = new ByteArrayInputStream(data);
GZIPInputStream gzip = new GZIPInputStream(bis);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] ret;
try {
byte[] buf = new byte[1024];
int num = -1;
while ((num = gzip.read(buf, 0, buf.length)) != -1) {
bos.write(buf,0,num);
}
ret = bos.toByteArray();
bos.flush();
} finally {
if (gzip != null) {
gzip.close();
}
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
}
return ret;
}
}
Marshalling
Jboss Marshalling 是一个java对象序列化包,对jdk默认的序列化框架进行了优化,但又保持跟java.io.Serializable接口兼容,同时增加了一些可调的参数和附加特性。
/**
* Marshalling工厂
*/
public class MarshallingCodecFactory {
/**
* 创建Jboss Marshalling解码器MarshallingDecoder
* @return
*/
public static MarshallingDecoder buildMarshallingDecoder(){
//通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//创建了MarshallingConfiguration对象,配置了版本号为5
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//根据marshallerFactory和configuration创建provider
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
//构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度
MarshallingDecoder decoder = new MarshallingDecoder(provider,1024 * 1024 * 1);
return decoder;
}
/**
* 创建Jboss MarshallingEncoder编码器
* @return
*/
public static MarshallingEncoder buildMarshallingEncoder(){
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
//构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
JavaBean
/**
* 请求消息
*/
public class ReqData implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
private String requestMessage;
private byte[] attachment;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRequestMessage() {
return requestMessage;
}
public void setRequestMessage(String requestMessage) {
this.requestMessage = requestMessage;
}
public byte[] getAttachment() {
return attachment;
}
public void setAttachment(byte[] attachment) {
this.attachment = attachment;
}
}
/**
* 响应信息
*/
public class RespData implements Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String name;
private String responseMessage;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
}
服务端
/**
* 服务端
*/
public class Server {
public static void main(String[] args) throws Exception {
EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup,cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO)) //设置日志
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ServerHandler());
}
});
//绑定端口
ChannelFuture cf = b.bind(9003).sync();
cf.channel().closeFuture().sync();
} finally {
pGroup.close();
cGroup.close();
}
}
}
/**
* 服务端处理方法
*/
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ReqData req= (ReqData) msg;
System.out.println("Server: " + req.getId() + "," + req.getName() + "," + req.getRequestMessage());
byte[] attachment = GzipUtils.unzip(req.getAttachment());
String path = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "001.jpg";
FileOutputStream fos = new FileOutputStream(path);
fos.write(attachment);
fos.close();
RespData resp = new RespData();
resp.setId(req.getId());
resp.setName("resp" + req.getId());
resp.setResponseMessage("响应内容: " + req.getId());
ctx.writeAndFlush(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
try {
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture cf = b.connect("127.0.0.1",9003).sync();
for (int i = 0; i < 6; i++) {
ReqData req = new ReqData();
req.setId(""+i);
req.setName("pro"+i);
req.setRequestMessage("数据信息" + i);
String path = System.getProperty("user.dir") + File.separatorChar
+ "sources" + File.separatorChar + "001.jpg";
File file = new File(path);
FileInputStream in = new FileInputStream(file);
byte[] data = new byte[in.available()];
in.read(data);
in.close();
req.setAttachment(GzipUtils.gzip(data));
cf.channel().writeAndFlush(req);
}
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
/**
* 客户端处理方法
*/
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
RespData resp = (RespData) msg;
System.out.println("Client:" + resp.getId() + "," + resp.getName() + "," + resp.getResponseMessage());
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
应用场景
断开后重新连接
服务端,加上一行设置超时时间的代码,其它内容不变。
b.group(pGroup,cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO)) //设置日志
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingEncoder());
//设置连接超时时间
sc.pipeline().addLast(new ReadTimeoutHandler(3));
sc.pipeline().addLast(new ServerHandler());
}
});
客户端
/**
* 客户端
*/
public class Client {
private static class SingletonHolder {
static final Client instance = new Client();
}
public static Client getInstance(){
return SingletonHolder.instance;
}
private EventLoopGroup group;
private Bootstrap b;
private ChannelFuture cf ;
private Client(){
group = new NioEventLoopGroup();
b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodecFactory.buildMarshallingEncoder());
//超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭响应的通道,主要为减小服务端资源占用)
sc.pipeline().addLast(new ReadTimeoutHandler(3));
sc.pipeline().addLast(new ClientHandler());
}
});
}
public void connect(){
try {
this.cf = b.connect("127.0.0.1", 9003).sync();
System.out.println("远程服务器已经连接, 可以进行数据交换..");
} catch (Exception e) {
e.printStackTrace();
}
}
public ChannelFuture getChannelFuture(){
if(this.cf == null){
this.connect();
}
if(!this.cf.channel().isActive()){
this.connect();
}
return this.cf;
}
public static void main(String[] args) throws Exception{
final Client c = Client.getInstance();
//c.connect();
ChannelFuture cf = c.getChannelFuture();
for(int i = 1; i <= 3; i++ ){
ReqData request = new ReqData();
request.setId("" + i);
request.setName("pro" + i);
request.setRequestMessage("数据信息" + i);
cf.channel().writeAndFlush(request);
TimeUnit.SECONDS.sleep(4);
}
cf.channel().closeFuture().sync();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("进入子线程...");
ChannelFuture cf = c.getChannelFuture();
System.out.println(cf.channel().isActive());
System.out.println(cf.channel().isOpen());
//再次发送数据
ReqData request = new ReqData();
request.setId("" + 4);
request.setName("pro" + 4);
request.setRequestMessage("数据信息" + 4);
cf.channel().writeAndFlush(request);
cf.channel().closeFuture().sync();
System.out.println("子线程结束.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("断开连接,主线程结束..");
}
}