欢迎大家关注github.com/hsfxuebao,期望对大家有所协助,要是觉得能够的话费事给点一下Star哈
1. 群聊系统
实例要求: 编写一个 Netty 群聊系统,完成服务器端和客户端之间的数据简略通讯(非阻塞),完成多人群聊
服务器端
:能够监测用户上线,离线,并完成音讯转发功能
客户端
:经过channel 能够无阻塞发送音讯给其它一切用户,同时能够接受其它用户发送的音讯(有服务器转发得到)
意图
:进一步理解Netty非阻塞网络编程机制
GroupChatServer端的代码:
public class GroupChatServer {
private int port; //监听端口
public GroupChatServer(int port) {
this.port = port;
}
//编写run办法,处理客户端的恳求
public void run() throws Exception{
//创立两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//获取到pipeline
ChannelPipeline pipeline = ch.pipeline();
//向pipeline参加解码器
pipeline.addLast("decoder", new StringDecoder());
//向pipeline参加编码器
pipeline.addLast("encoder", new StringEncoder());
//参加自己的业务处理handler
pipeline.addLast(new GroupChatServerHandler());
}
});
System.out.println("netty 服务器发动");
ChannelFuture channelFuture = b.bind(port).sync();
//监听封闭
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new GroupChatServer(7000).run();
}
}
public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {
//public static List<Channel> channels = new ArrayList<Channel>();
//运用一个hashmap 管理
//public static Map<String, Channel> channels = new HashMap<String,Channel>();
//定义一个channle 组,管理一切的channel
//GlobalEventExecutor.INSTANCE) 是全局的事情执行器,是一个单例
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static final DateTimeFormatter DATE_KEY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//handlerAdded 表明衔接建立,一旦衔接,第一个被执行
//将当时channel 参加到 channelGroup
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
//将该客户参加谈天的信息推送给其它在线的客户端
/*
该办法会将 channelGroup 中一切的channel 遍历,并发送 音讯,
咱们不需求自己遍历
*/
channelGroup.writeAndFlush("[客户端]" + channel.remoteAddress() + " 参加谈天" + LocalDateTime.now().format(DATE_KEY_FORMATTER) + " \n");
channelGroup.add(channel);
}
//断开衔接, 将xx客户脱离信息推送给当时在线的客户
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("[客户端]" + channel.remoteAddress() + " 脱离了\n");
System.out.println("channelGroup size" + channelGroup.size());
}
//表明channel 处于活动状况, 提示 xx上线
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress() + " 上线了~");
}
//表明channel 处于不活动状况, 提示 xx离线了
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress() + " 离线了~");
}
//读取数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//获取到当时channel
Channel channel = ctx.channel();
//这时咱们遍历channelGroup, 根据不同的状况,回送不同的音讯
channelGroup.forEach(ch -> {
if (channel != ch) { //不是当时的channel,转发音讯
ch.writeAndFlush("[客户]" + channel.remoteAddress() + " 发送了音讯" + msg + "\n");
} else { //回显自己发送的音讯给自己
ch.writeAndFlush("[自己]发送了音讯" + msg + "\n");
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//封闭通道
ctx.close();
}
}
GroupChatClient客户端代码:
public class GroupChatClient {
//属性
private final String host;
private final int port;
public GroupChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//得到pipeline
ChannelPipeline pipeline = ch.pipeline();
//参加相关handler
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
//参加自定义的handler
pipeline.addLast(new GroupChatClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
//得到channel
Channel channel = channelFuture.channel();
System.out.println("-------" + channel.localAddress() + "--------");
//客户端需求输入信息,创立一个扫描器
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String msg = scanner.nextLine();
//经过channel 发送到服务器端
channel.writeAndFlush(msg + "\r\n");
}
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new GroupChatClient("127.0.0.1", 7000).run();
}
}
public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg.trim());
}
}
GroupCharServer执行成果如下:
netty 服务器发动
/127.0.0.1:53907 上线了~
/127.0.0.1:53924 上线了~
/127.0.0.1:53924 离线了~
channelGroup size1
/127.0.0.1:53949 上线了~
/127.0.0.1:53957 上线了~
GroupCharClient 1 成果如下:
-------/127.0.0.1:53907--------
[客户端]/127.0.0.1:53924 参加谈天2022-05-07 17:05:41
[客户端]/127.0.0.1:53924 脱离了
[客户端]/127.0.0.1:53949 参加谈天2022-05-07 17:07:03
[客户端]/127.0.0.1:53957 参加谈天2022-05-07 17:07:12
[客户]/127.0.0.1:53949 发送了音讯我是 2
我是1
[自己]发送了音讯我是1
[客户]/127.0.0.1:53957 发送了音讯我是3
GroupCharClient 2 成果如下:
-------/127.0.0.1:53949--------
[客户端]/127.0.0.1:53957 参加谈天2022-05-07 17:07:12
我是 2
[自己]发送了音讯我是 2
[客户]/127.0.0.1:53907 发送了音讯我是1
[客户]/127.0.0.1:53957 发送了音讯我是3
GroupCharClient 3成果如下:
-------/127.0.0.1:53957--------
[客户]/127.0.0.1:53949 发送了音讯我是 2
[客户]/127.0.0.1:53907 发送了音讯我是1
我是3
[自己]发送了音讯我是3
2. 心跳检测机制案例
实例要求:
编写一个 Netty心跳检测机制案例, 当服务器超越3秒没有读时,就提示读闲暇。当服务器超越5秒没有写操作时,就提示写闲暇。完成当服务器超越7秒没有读或许写操作时,就提示读写闲暇
Server端代码:
public class MyServer {
public static void main(String[] args) throws Exception{
//创立两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//参加一个netty 供给 IdleStateHandler
/*
阐明
1. IdleStateHandler 是netty 供给的处理闲暇状况的处理器
2. long readerIdleTime : 表明多长时刻没有读, 就会发送一个心跳检测包检测是否衔接
3. long writerIdleTime : 表明多长时刻没有写, 就会发送一个心跳检测包检测是否衔接
4. long allIdleTime : 表明多长时刻没有读写, 就会发送一个心跳检测包检测是否衔接
5. 文档阐明
triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
* read, write, or both operation for a while.
* 6. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理
* 经过调用(触发)下一个handler 的 userEventTiggered , 在该办法中去处理 IdleStateEvent(读闲暇,写闲暇,读写闲暇)
*/
pipeline.addLast(new IdleStateHandler(10,13,20, TimeUnit.SECONDS));
//参加一个对闲暇检测进一步处理的handler(自定义)
pipeline.addLast(new MyServerHandler());
}
});
//发动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class MyServerHandler extends ChannelInboundHandlerAdapter {
// 事情触发
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
//将 evt 向下转型 IdleStateEvent
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
case READER_IDLE:
eventType = "读闲暇";
break;
case WRITER_IDLE:
eventType = "写闲暇";
break;
case ALL_IDLE:
eventType = "读写闲暇";
break;
}
System.out.println(ctx.channel().remoteAddress() + "--超时时刻--" + eventType);
System.out.println("服务器做相应处理.." + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//如果产生闲暇,咱们封闭通道
// ctx.channel().close();
}
}
}
客户端运用群聊 那个就能够,服务端执行成果如下:
/127.0.0.1:54273--超时时刻--读闲暇
服务器做相应处理..2022-05-07 17:22:53
/127.0.0.1:54273--超时时刻--写闲暇
服务器做相应处理..2022-05-07 17:22:56
/127.0.0.1:54273--超时时刻--读写闲暇
服务器做相应处理..2022-05-07 17:23:03
/127.0.0.1:54273--超时时刻--读闲暇
服务器做相应处理..2022-05-07 17:23:03
/127.0.0.1:54273--超时时刻--写闲暇
服务器做相应处理..2022-05-07 17:23:09
3. WebSocket编程完成长衔接
实例要求:
Http协议是无状况的, 浏览器和服务器间的恳求响应一次,下一次会从头创立衔接.
要求:完成基于webSocket的长衔接的全双工的交互
改变Http协议屡次恳求的束缚,完成长衔接了, 服务器能够发送音讯给浏览器
客户端浏览器和服务器端会相互感知,比方服务器封闭了,浏览器会感知,同样浏览器封闭了,服务器会感知
服务端:
public class MyServer {
public static void main(String[] args) throws Exception{
//创立两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//由于基于http协议,运用http的编码和解码器
pipeline.addLast(new HttpServerCodec());
//是以块方法写,增加ChunkedWriteHandler处理器
pipeline.addLast(new ChunkedWriteHandler());
/*
阐明
1. http数据在传输过程中是分段, HttpObjectAggregator ,便是能够将多个段聚合
2. 这就便是为什么,当浏览器发送大量数据时,就会发出屡次http恳求
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/*
阐明
1. 对应websocket ,它的数据是以 帧(frame) 方式传递
2. 能够看到WebSocketFrame 下面有六个子类
3. 浏览器恳求时 ws://localhost:7000/hello 表明恳求的uri
4. WebSocketServerProtocolHandler 核心功能是将 http协议升级为 ws协议 , 坚持长衔接
5. 是经过一个 状况码 101
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/hello2"));
//自定义的handler ,处理业务逻辑
pipeline.addLast(new MyTextWebSocketFrameHandler());
}
});
//发动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
//这儿 TextWebSocketFrame 类型,表明一个文本帧(frame)
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("服务器收到音讯 " + msg.text());
//回复音讯
ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时刻" + LocalDateTime.now() + " " + msg.text()));
}
//当web客户端衔接后, 触发办法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//id 表明仅有的值,LongText 是仅有的 ShortText 不是仅有
System.out.println("handlerAdded 被调用" + ctx.channel().id().asLongText());
System.out.println("handlerAdded 被调用" + ctx.channel().id().asShortText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved 被调用" + ctx.channel().id().asLongText());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常产生 " + cause.getMessage());
ctx.close(); //封闭衔接
}
}
参考文档
Netty学习和源码分析github地址
Netty从入门到精通视频教程(B站)
Netty威望攻略 第二版