多客户端启动方式:
Runnable client = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
BootNettyClientThread thread = new BootNettyClientThread(31700,"127.0.0.1");
thread.start();
}
for (int i = 0; i < 2; i++) {
BootNettyClientThread thread = new BootNettyClientThread(31701,"127.0.0.1");
thread.start();
}
for (int i = 0; i < 2; i++) {
BootNettyClientThread thread = new BootNettyClientThread(31702,"127.0.0.1");
thread.start();
}
}
};
service.execute(client);
BootNettyClientThread代码:
public class BootNettyClientThread extends Thread {
private final int port;
private final String address;
public BootNettyClientThread(int port, String address){
this.port = port;
this.address = address;
}
public void run() {
try {
new BootNettyClient().connect(port, address);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
BootNettyClient代码:
public class BootNettyClient {
static Integer waitTimes = 1;
static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
/**
* 初始化Bootstrap
*/
public static final Bootstrap getBootstrap(EventLoopGroup group) {
if (null == group) {
group = eventLoopGroup;
}
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true).handler(new BootNettyChannelInitializer<SocketChannel>());
return bootstrap;
}
public void connect(int port, String host) throws Exception {
// System.err.println("正在进行连接:" + host);
eventLoopGroup.shutdownGracefully();
eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = getBootstrap(null);
try {
bootstrap.remoteAddress(host, port);
// 异步连接tcp服务端
ChannelFuture future = bootstrap.connect().addListener((ChannelFuture futureListener) -> {
final EventLoop eventLoop = futureListener.channel().eventLoop();
if (futureListener.isSuccess()) {
BootNettyClientChannel bootNettyClientChannel = new BootNettyClientChannel();
Channel channel = futureListener.channel();
String id = futureListener.channel().id().toString();
// String id = host;
bootNettyClientChannel.setChannel(channel);
bootNettyClientChannel.setCode("clientId:" + id);
BootNettyClientChannelCache.save("clientId:" + id, bootNettyClientChannel);
System.out.println("netty client start success=" + id);
} else {
// System.err.println("连接失败," + waitTimes.toString() + "秒后重新连接:" + host);
try {
Thread.sleep(waitTimes * 1000);
} finally {
connect(port, host);
}
}
});
future.channel().closeFuture().sync();
} catch (Exception e) {
// System.err.println("连接异常," + waitTimes.toString() + "秒后重新连接:" + host);
try {
Thread.sleep(waitTimes * 1000);
} finally {
connect(port, host);
}
e.printStackTrace();
} finally {
/**
* 退出,释放资源
*/
eventLoopGroup.shutdownGracefully().sync();
}
}
}
BootNettyChannelInitializer
@ChannelHandler.Sharable
public class BootNettyChannelInitializer<SocketChannel> extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
ch.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
/**
* 自定义ChannelInboundHandlerAdapter
*/
ch.pipeline().addLast(new BootNettyChannelInboundHandlerAdapter());
}
}
BootNettyChannelInboundHandlerAdapter
@ChannelHandler.Sharable
public class BootNettyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{
/**
* 从服务端收到新的数据时,这个方法会在收到消息时被调用
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception, IOException {
if(msg == null){
return;
}
System.out.println("channelRead:read msg:"+msg.toString());
BootNettyClientChannel bootNettyClientChannel = BootNettyClientChannelCache.get("clientId:"+ctx.channel().id().toString());
if(bootNettyClientChannel != null){
System.out.println("to do");
bootNettyClientChannel.setLast_data(msg.toString());
}
//回应服务端
//ctx.write("I got server message thanks server!");
}
/**
* 从服务端收到新的数据、读取完成时调用
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws IOException {
System.out.println("channelReadComplete");
ctx.flush();
}
/**
* 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException {
// System.out.println("exceptionCaught");
cause.printStackTrace();
ctx.close();//抛出异常,断开与客户端的连接
}
/**
* 客户端与服务端第一次建立连接时 执行
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception, IOException {
super.channelActive(ctx);
InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = inSocket.getAddress().getHostAddress();
// System.out.println("连接成功:"+clientIp);
}
/**
* 客户端与服务端 断连时 执行
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception, IOException {
super.channelInactive(ctx);
InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = inSocket.getAddress().getHostAddress();
Integer port = inSocket.getPort();
ctx.close(); //断开连接时,必须关闭,否则造成资源浪费
// System.out.println("channelInactive:"+port);
System.err.println("异常断开,重新连接:" + clientIp);
BootNettyClientThread thread = new BootNettyClientThread(port,clientIp);
thread.start();
}
}
BootNettyClientChannelCache
public class BootNettyClientChannelCache {
public static volatile Map<String, BootNettyClientChannel> channelMapCache = new ConcurrentHashMap<String, BootNettyClientChannel>();
public static void add(String code, BootNettyClientChannel channel){
channelMapCache.put(code,channel);
}
public static BootNettyClientChannel get(String code){
return channelMapCache.get(code);
}
public static void remove(String code){
channelMapCache.remove(code);
}
public static void save(String code, BootNettyClientChannel channel) {
if(channelMapCache.get(code) == null) {
add(code,channel);
}
}
}
BootNettyClientChannel(补充get set即可)
public class BootNettyClientChannel {
// 连接客户端唯一的code
private String code;
// 客户端最新发送的消息内容
private String last_data;
private transient volatile Channel channel;
测试:
断开连接,服务器先后启动,都长保持总数为6个连接。
心跳(Springboot的定时框架):
@Component
@EnableScheduling
public class ClientHelper {
// 使用定时器发送心跳
@Scheduled(cron = "0/3 * * * * ?")
public void heart_timer() {
String back = "heart";
System.err.println("BootNettyClientChannelCache.channelMapCache.size():"+BootNettyClientChannelCache.channelMapCache.size());
if(BootNettyClientChannelCache.channelMapCache.size() > 0){
for (Map.Entry<String, BootNettyClientChannel> entry : BootNettyClientChannelCache.channelMapCache.entrySet()) {
BootNettyClientChannel bootNettyChannel = entry.getValue();
System.out.println(bootNettyChannel.getCode());
try {
bootNettyChannel.getChannel().writeAndFlush(Unpooled.buffer().writeBytes(back.getBytes()));
} catch (Exception e) {
continue;
}
}
}
}
也可在连接成功的时候新建线程,单独的线程去发送,线程要保存,在发生异常时要停止。