主要功能
1、(Clinet)断点要能重连
2、Server接收Client发送的消息
3、人数统计
主要使用
ChannelInitializer、ChannelInboundHandlerAdapter
注意点
1、在pipeline()使用Handler处理消息时需要使用正确的Handler顺序(错误的顺序会读不出来 可能不会走到channelRead()方法)
2、在channelRead中使用 ctx.write/writeAndFlush方法时 不要复用Unpooled.buffer() 每次都重新写入 复用会报引数错误rcf:0
正确:
ctx.writeAndFlush(Unpooled.buffer().writeBytes("aaa".getBytes()));
错误:
ByteBuf buf = Unpooled.buffer();
ctx.writeAndFlush(buf.writeBytes("aaa".getBytes()));
3、Client使用EventLoop.addListenter时复用之前的eventLoop对象 如果不复用客户端会达到最大的线程数就不新增了
// 使用futureListener.channel(),eventLoop()的EventLoop对象
final EventLoop eventLoop = futureListener.channel().eventLoop();
4、也可以继承SimpleChannelInboundHandler在channelRead0()中通过ctx.channel()得到Channel,然后就通过ThreadLocal变量或其他方法,只要能把这个Channel保存住就行
顺便提一下 SimpleChannelInboundHandler 继承 ChannelInboundHandlerAdapter
主要区别在于在使用channelRead方法时 Simple类的channelRead多了一个finally块 release了Bytebuf对象(可能会造成某些情况具体还没遇到过)
ReferenceCountUtil.release(msg);
IMClient.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class IMClient {
static final int PORT = 8001;
static final String HOST = "127.0.0.1";
private static EventLoopGroup GROUP = new NioEventLoopGroup();
private static IMClientChannelInitializer imClientChannelInitializer= new IMClientChannelInitializer();
//1
public void start() throws InterruptedException {
connection(new Bootstrap(),GROUP);
}
public void connection(Bootstrap bootstrap, final EventLoopGroup group) throws InterruptedException {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE,true)
.remoteAddress(HOST,PORT)
.handler(imClientChannelInitializer);
;
ChannelFuture future = bootstrap.connect().addListener((ChannelFuture futureListener)->{
final EventLoopGroup eventLoopGroup = futureListener.channel().eventLoop();
if(futureListener.isSuccess()){
log.info("客户端 "+futureListener.channel().localAddress()+" 连接成功!");
}else {
log.info("客户端 "+futureListener.channel().localAddress()+" 连接失败! 10s 后重连 ... ...");
};
eventLoopGroup.schedule(()->{
// 使用futureListener.channel(),eventLoop()的EventLoop对象
final EventLoop eventLoop = futureListener.channel().eventLoop();
try {
connection(new Bootstrap(),eventLoop);
} catch (InterruptedException e) {
e.printStackTrace();
}
},10,TimeUnit.SECONDS);
});
future.channel().closeFuture().sync();
}
public static void main(String ...args) throws InterruptedException {
new IMClient().start();
}
}
IMClientChannelInitializer.java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class IMClientChannelInitializer extends ChannelInitializer {
int i= 0;
private static IMClientHandler imClientHandler= new IMClientHandler();
@Override
protected void initChannel(Channel ch) throws Exception {
// ch.pipeline().addLast("decoder", new StringDecoder());
// ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().write(ch.localAddress()+": 已上线");
ch.pipeline().write(ch.localAddress()+"Hello");
ch.write(("客户端发出第"+(++i)+"条消息").getBytes());
log.info("客户端发出第"+i+"条消息");
ch.pipeline().addLast("handler",imClientHandler);
log.info("Client init !");
ch.flush();
}
}
IMClientHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ChannelHandler.Sharable
public class IMClientHandler extends ChannelInboundHandlerAdapter {
private int i =0;
User user = new User();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(!(msg instanceof ByteBuf)){
log.error("未知数据 : "+msg.toString());
return;
}
ByteBuf in = (ByteBuf) msg;
log.info("Client received: "+ByteBufUtil.hexDump(in.readBytes(in.readableBytes())));
ReferenceCountUtil.release(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
user.chat("Tom");
Long firstTime = System.currentTimeMillis();
Long secondTime =firstTime;
int i = 0;
//当消息在5条或间隔时间不大于5s时继续读
while(i<5&&(secondTime-firstTime<=500)){
if(user.getlist().iterator().hasNext()){
//不要复用对象 ByteBuf buf = Unpooled.buffer()会出现计数rcf0问题
ByteBuf buf = Unpooled.buffer();
ctx.writeAndFlush(Unpooled.buffer().writeBytes(user.getlist().getFirst().getBytes()));
user.getlist().removeFirst();
}
i++;
secondTime=System.currentTimeMillis();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
IMServer.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class IMServer {
static final int PORT = 8001;
private static EventLoopGroup BOSS_GROUP = new NioEventLoopGroup();
private static EventLoopGroup WORKER_GROUP = new NioEventLoopGroup();
static IMServerHandler IMSERVER_HANDLER= new IMServerHandler();
private static IMServerChannelInitializer IMSERVER_CHANNEL_INITIALIZER= new IMServerChannelInitializer();
public void start() throws InterruptedException {
connection(new ServerBootstrap(),BOSS_GROUP,WORKER_GROUP);
}
public void connection(ServerBootstrap bootstrap,EventLoopGroup boss,EventLoopGroup worker) throws InterruptedException {
bootstrap.localAddress(PORT)
.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(IMSERVER_CHANNEL_INITIALIZER);
}
});
//可设置是否开启自动读
//.childOption(ChannelOption.AUTO_READ,true);
ChannelFuture future =bootstrap.bind().sync();
future.channel().closeFuture().sync();
BOSS_GROUP.shutdownGracefully();
WORKER_GROUP.shutdownGracefully();
}
public static void main(String ...args) throws InterruptedException {
new IMServer().start();
}
}
IMServerChannelInitializer.java
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class IMServerChannelInitializer extends ChannelInitializer {
@Override
protected void initChannel(Channel ch) throws Exception {
//方法一
//ch.pipeline().addLast("decoder", new StringDecoder());
//ch.pipeline().addLast("encoder", new StringEncoder());
//需要改写handler对接受msg的处理方式
// ch.pipeline().addLast(IMServer.IMSERVER_HANDLER);
//方法二
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE,Unpooled.copiedBuffer("$_".getBytes())));
ch.pipeline().addLast(IMServer.IMSERVER_HANDLER);
}
IMServerHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;
@Slf4j
@ChannelHandler.Sharable
public class IMServerHandler extends ChannelInboundHandlerAdapter {
//存储客户状态
private static Set<String> set = new HashSet<>();
private int i =0;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
log.info("Server 收到 : "+new String(bytes,"UTF-8")+"在线 "+set.size()+" 人");
++i;
ctx.writeAndFlush(("Server accept "+i+" message").getBytes());;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
set.add(insocket.getAddress().toString());
}
}
User.java
import lombok.extern.slf4j.Slf4j;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
@Slf4j
public class User {
int i = 0;
//消息列表
public static LinkedList<String> MESSAGE_LIST = new LinkedList<String>();
//DateFormat
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//生产消息
public void chat(String userName ) throws InterruptedException {
while (i!=100){
MESSAGE_LIST.add(DATE_FORMAT.format(new Date())+"\t"+userName+" ; "+(++i)+"$_");
System.out.println(MESSAGE_LIST.size());
}
}
public LinkedList<String> getlist(){
return MESSAGE_LIST;
}
}
pom.java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>netty</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.37.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
</project>