package com.xiyou.netty3.heart;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.jboss.netty.util.HashedWheelTimer;
import java.net.InetSocketAddress;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* netty的客户端
*/
public class Client {
public static void main(String[] args) {
// 声明一个服务类
ClientBootstrap bootstrap = new ClientBootstrap();
// 声明两个线程池
// 监听和服务端的连接
ExecutorService boss = Executors.newCachedThreadPool();
// 监听和服务端的数据的交互
ExecutorService worker = Executors.newCachedThreadPool();
// socket工厂
bootstrap.setFactory(new NioClientSocketChannelFactory(boss, worker));
// 声明管道工厂
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("clientHandler", new ClientHandler());
return pipeline;
}
});
// 连接服务端
ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10101));
System.out.println("客户端已经正常连接");
Channel channel = connect.getChannel();
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请输入");
String value = scanner.next();
if("stop".equals(value)){
break;
}
// 可以直接写字符串的原因就是设置了编码器,可以自动将字符串转换成ChannelBuffer
channel.write(value);
}
System.out.println("我已经退出了客户端的发送....");
try {
channel.close().sync();
System.exit(0);
}catch (Exception e){
e.printStackTrace();
}
}
}
package com.xiyou.netty3.heart;
import org.jboss.netty.channel.*;
import java.util.Scanner;
/**
* 是管道中添加的过滤器
*/
public class ClientHandler extends SimpleChannelHandler {
/**
* 关闭连接,即使连接没有成功连接,我们也会在关闭的时候调用该方法
* @param ctx
* @param e
* @throws Exception
*/
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("------channelClosed------");
super.channelClosed(ctx, e);
}
/**
* 连接建立成功的时候,我们就会调用该方法
* @param ctx
* @param e
* @throws Exception
*/
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("------channelConnected------");
super.channelConnected(ctx, e);
}
/**
* 关闭连接的时候会调用该方法(只有当连接建立成功后再关闭才会调用)
* @param ctx
* @param e
* @throws Exception
*/
@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("------channelDisconnected------");
super.channelDisconnected(ctx, e);
}
/**
* messageRecived方法抛出异常的时候调用该方法。该方法用来捕获异常
* @param ctx
* @param e
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
System.out.println("------exceptionCaught------");
super.exceptionCaught(ctx, e);
}
/**
* 用来接收客户端发来的消息
* @param ctx
* @param e
* @throws Exception
*/
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
System.out.println("------messageReceived------: " + e.getMessage());
super.messageReceived(ctx, e);
}
}
上面的代码和netty3实现的helloworld中的代码没有区别,我们这里主要看看服务端的代码
package com.xiyou.netty3.heart;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
import org.jboss.netty.handler.timeout.IdleStateEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* IdleStateAwareChannelHandler 是工具类,用来监听连接状态的
*/
public class ServerHandlerHeart extends IdleStateAwareChannelHandler implements ChannelHandler {
@Override
public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat("ss");
// 显示当前的会话状态
System.out.println(e.getState() + " : " + dateFormat.format(new Date()));
}
}
上面的类继承了IdleStateAwareChannelHandler ,该类的主要作用就是用来监听客户端的连接状态
package com.xiyou.netty3.heart;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.jboss.netty.util.HashedWheelTimer;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* netty的服务端
* 可以利用telnet ip 端口号 进行调用
* ctrl + ] 可以发送数据
* 发送数据用 send XXX
*/
public class Server {
public static void main(String[] args) {
// 1.先创建一个服务类
ServerBootstrap bootstrap = new ServerBootstrap();
// 2.创建两个线程池,一个负责监控客户端的连接,一个负责监控客户端发送的数据
ExecutorService boss = Executors.newCachedThreadPool();
ExecutorService worker = Executors.newCachedThreadPool();
// 给服务类设置一个工厂
// 将两个线程池传入
// 设置NIO socket工厂
bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
// 新增一个定时器
final HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
// 设置NIO的管道的工厂
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
// 先声明一个管道
ChannelPipeline pipeline = Channels.pipeline();
// 需要有一个触发IdleStateAwareChannelHandler的过滤器
// IdleStateHandler有四个参数,第一个是一个定时器
// 第二个参数是规定多久时间没有读数据,抛出读数据的异常
// 第三个参数是规定多久时间没有写数据,抛出写数据的异常
// 第三个参数是规定多久时间没有进行读写数据,抛出读写数据的异常
// 单位都是s
pipeline.addLast("idle", new IdleStateHandler(hashedWheelTimer,
5,
7,
9));
pipeline.addLast("stringEncoder", new StringEncoder());
pipeline.addLast("stringDecoder", new StringDecoder());
// 管道里装了一堆过滤器
pipeline.addLast("helloHandler", new ServerHandlerHeart());
return pipeline;
}
});
// 用服务类绑定端口
bootstrap.bind(new InetSocketAddress(10101));
System.out.println("服务端已经启动!!!");
}
}
这里我们抽取出重要的方法:
// 新增一个定时器
final HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
// 需要有一个触发IdleStateAwareChannelHandler的过滤器
// IdleStateHandler有四个参数,第一个是一个定时器
// 第二个参数是规定多久时间没有读数据,抛出读数据的异常
// 第三个参数是规定多久时间没有写数据,抛出写数据的异常
// 第三个参数是规定多久时间没有进行读写数据,抛出读写数据的异常
// 单位都是s
pipeline.addLast("idle", new IdleStateHandler(hashedWheelTimer,
5,
7,
9));
第一行我们新增了一个定时器,该定时器用来传入pipleLine的过滤器中,用来监听指定的时间内有没有对其进行读,写,读写操作,单位是s。如果在规定的时间内没有进行相应的操作,则会抛出异常触发handler中的channelIdle方法,我们在该方法中进行了打印,我们可以看到结果如下。
服务端已经启动!!!
READER_IDLE : 06
WRITER_IDLE : 08
ALL_IDLE : 10
但是我们很快就发现了一个问题,我们继承的IdleStateAwareChannelHandler 这个类,并不能够对客户端发送的数据进行操作,即没有receiveMessage方法,经过我们的仔细研究,我们发现SimpleChannelHandler类中有一个handleUpstream方法可以获取IdleStateEvent事件,因此我们将代码进行修改。使其既可以监听发送的消息,又可以对连接进行监控。
public class ServerHandlerHeart extends SimpleChannelHandler{
/**
* 用来接收客户端的消息
* @param ctx
* @param e
* @throws Exception
*/
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
System.out.println("接收到的客户端的信息是: " + e.getMessage());
super.messageReceived(ctx, e);
}
/**
* 该方法可以用来监听连接,判断是否超时未读,未写,未读写
* @param ctx
* @param e
* @throws Exception
*/
@Override
public void handleUpstream(final ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
// 判断事件e是否是IdleStateEvent事件,如果是该事件,可以针对不同的时间进行处理
if(e instanceof IdleStateEvent){
/**
* IdleState有状态
* READER_IDLE, 读超时
* WRITER_IDLE, 写超时
* ALL_IDLE; 读写超时
*/
if(((IdleStateEvent)e).getState() == IdleState.ALL_IDLE){
System.out.println("长时间未读写,断开连接");
// 关闭会话
// 向客户端写数据,通知客户端,应该关闭连接
ChannelFuture channel = ctx.getChannel().write("您长时间未读写,断开连接");
// 给该Channel设置监听,数据写给客户端成功后,调用该方法
channel.addListener(new ChannelFutureListener() {
// 回调方法
public void operationComplete(ChannelFuture channelFuture) throws Exception {
// 关闭客户端的连接
ctx.getChannel().close();
}
});
}
}else{
// 如果不是IdleStateEvent事件
super.handleUpstream(ctx, e);
}
}
}
上面的handleUpstream方法会判断是否是IdleStateEvent事件,如果是该事件,监听是否是读写超时,如果是读写超时,断开连接,并给客户端通知,并设置监听事件,当给客户端发送完数据之后,调用该监听器的operationComplete方法,上面的代码中是关闭客户端的连接。
上面的代码是用netty3实现的,netty5将其方法名和类名进行了改变。
将handleUpstream方法改为userEventTriggered(ChannelHandlerContext ctx, Object evt)
package com.heart;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
/**
* 服务端消息处理
* @author -琴兽-
*
*/
public class ServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
ctx.channel().writeAndFlush("hi");
ctx.writeAndFlush("hi");
}
@Override
public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent){
IdleStateEvent event = (IdleStateEvent)evt;
if(event.state() == IdleState.ALL_IDLE){
//清除超时会话
ChannelFuture writeAndFlush = ctx.writeAndFlush("you will close");
writeAndFlush.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
ctx.channel().close();
}
});
}
}else{
super.userEventTriggered(ctx, evt);
}
}
/**
* 新客户端接入
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
/**
* 客户端断开
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
}
/**
* 异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
}
}
package com.heart;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
/**
* netty5服务端
* @author -琴兽-
*
*/
public class Server {
public static void main(String[] args) {
//服务类
ServerBootstrap bootstrap = new ServerBootstrap();
//boss和worker
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
//设置线程池
bootstrap.group(boss, worker);
//设置socket工厂、
bootstrap.channel(NioServerSocketChannel.class);
//设置管道工厂
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new IdleStateHandler(5, 5, 10));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ServerHandler());
}
});
//netty3中对应设置如下
//bootstrap.setOption("backlog", 1024);
//bootstrap.setOption("tcpNoDelay", true);
//bootstrap.setOption("keepAlive", true);
//设置参数,TCP参数
bootstrap.option(ChannelOption.SO_BACKLOG, 2048);//serverSocketchannel的设置,链接缓冲池的大小
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);//socketchannel的设置,维持链接的活跃,清除死链接
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);//socketchannel的设置,关闭延迟发送
//绑定端口
ChannelFuture future = bootstrap.bind(10101);
System.out.println("start");
//等待服务端关闭
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally{
//释放资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
总结下: