Netty
记录学习,开心学习,来源尚硅谷韩顺平netty视频
1 NettyServer
package com.fs.netty.simple;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建boosGroup 和 workerGroup
//创建两个线程组,bossGroup workerGroup
//bossGroup只是处理链接请求
//workerGroup 真正的与客户端业务处理,会交给workerGroup完成,自己不做处理
//两个都是无线循环
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
//处理整个服务的异常
try {
//创建服务器端启动的配置参数对象
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来进行参数设置
bootstrap.group(bossGroup, workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//设置服务器的通道实现使用NioServerSocketChannel
.option(ChannelOption.SO_BACKLOG,128)//设置线程队列等待的个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置链接保持活动链接状态
.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象,使用匿名内部类方式
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//addLast给ChannelPipeline添加我们自定义的handler
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});//给我们的workerGroup的EventLoop对应的管道设置处理器
//给我们服务端绑定端口并且同步处理,生成一个ChannelFuture,启动服务器
ChannelFuture channelFuture = bootstrap.bind(6668).sync();
System.out.println("----服务器is ready");
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}finally {
//出现异常,优雅的关闭
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
1.1 NettyServerHandler
package com.fs.netty.simple;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
//继承ChannelInboundHandlerAdapter
/**
* 我们自定义一个Handler,需要继承某个handler适配器(ChannelInboundHandlerAdapter)
* 这时我们自定义的handler才能称之为一个handler
* 因为我们要遵守某些规范,有些方法需要从写的
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取数据事件(这里我们可以读取客户端发送的消息)
* @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
* @param msg 客户端发送的数据,默认Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程:"+Thread.currentThread().getName());
System.out.println("server ctx :"+ctx);
//将msg转成byteBuf(是netty提供的,不是NIO的byteBuffer,ByteBuf性能更高)
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("看看channel与pipeline的关系");
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();//pipeline 本质是一个双向链表,本质是出站入站
//Debug得知,channle与pipeline是相互包含的关系,你中有我,我中有你
System.out.println("客户端发送消息是:"+byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:"+ channel.remoteAddress());
}
/**
* 数据读取完毕
* @param ctx 上下文对象
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入到缓存,并刷新
//一般对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端~xixixi",CharsetUtil.UTF_8));
}
/**
* 发生异常处理,一般需要关闭通道
* @param ctx 上下文对象
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
2 NettyClient
package com.fs.netty.simple;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//客户端只需要一个事件循环组
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
//创建客户端启动对象
//注意客户端使用的不是 ServerBootStrap 而是BootStarp
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(eventLoopGroup)//设置线程组
.channel(NioSocketChannel.class)//设置客户端通道的实现类(反射处理)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());//加入自定义客户端处理器
}
});
System.out.println("客户端 is --- ok");
//启动客户端去链接服务的
//关于ChannelFuture 要分析,涉及到netty的异步模型
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}finally {
//出现异常,优雅的关闭
eventLoopGroup.shutdownGracefully();
}
}
}
2.2 NettyClientHandler
package com.fs.netty.simple;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/*
客户端处理器
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当通道就绪就会触发
* @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client :"+ ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello,Server:hahaha", CharsetUtil.UTF_8));
}
/**
* 当通道有读取事件的时候,会触发
* @param ctx 上下文对象
* @param msg 消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
System.out.println("服务器回复的消息:"+byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("服务器地址:"+ctx.channel().remoteAddress());
}
/**
* 异常触发
* @param ctx 上下文对象
* @param cause 异常对象
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
3 用户自定义普通任务队列 异步执行(将任务提交到taskQueue)
在channelRead方法中将消息存放在taskQueue队列中
(NettyServerHandler extends ChannelInboundHandlerAdapter 重写的channelRead()方法)
/**
* 读取数据事件(这里我们可以读取客户端发送的消息)
* @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
* @param msg 客户端发送的数据,默认Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//这里有一个非常耗时的时间业务,---》异步执行,提交到channel、对应的
//NIOEventLoop 的 TaskQueue
/*
Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完",CharsetUtil.UTF_8));
System.out.println("继续执行");
这个是阻塞的,那这样netty还是阻塞的
*/
//解决方案一,用户程序自定义的普通程序
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完",CharsetUtil.UTF_8));
}
});
//下面这个线程是上面10秒后20秒在执行,所以taskQueue是阻塞队列。在同一个线程下
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(20*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完2",CharsetUtil.UTF_8));
}
});
System.out.println("继续执行");
}
sc
4 用户自定义定时任务 异步执行(将任务提交到scheduleTaskQueue中)
/**
* 读取数据事件(这里我们可以读取客户端发送的消息)
* @param ctx 上下文对象,包含了管道pipeline(做业务逻辑) 通道channel(做数据读写) 地址等
* @param msg 客户端发送的数据,默认Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//这里有一个非常耗时的时间业务,---》异步执行,提交到channel、对应的 scheduleTaskQueue
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer("hello-客户端-读完",CharsetUtil.UTF_8));
}
},5, TimeUnit.SECONDS);//参数:线程,时间,时间参数
System.out.println("继续执行");
}
5 FutureListener 监听事件
例如:我们的服务端执行了 bootstrap.bind(6668).sync(); 得到ChannelFuture
//给我们服务端绑定端口并且同步处理,生成一个ChannelFuture,启动服务器
ChannelFuture channelFuture = bootstrap.bind(6668).sync();
//使用FutureListener 给channelFuture 注册监听器,监控绑定成功的时间
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()){
System.out.println("监听端口6668成功");
} else {
System.out.println("监听端口6668失败");
}
}
});
6 HTTP案例
6.1 TestHTTPServer
package com.fs.netty.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/*
http案例
*/
public class TestHTTPServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TestServerInitialize());//使用自定义的解码器
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()){
System.out.println("监听8080成功");
}else {
System.out.println("监听8080失败");
}
}
});
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
6.2 TestServerInitialize
package com.fs.netty.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
public class TestServerInitialize extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//向管道加入处理器
//得到管道
ChannelPipeline pipeline = socketChannel.pipeline();
//加入netty提供的httpServerCodec codec 是编解码器
//HttpServerCodec是netty提供的http的编解码器
pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
//增加一个自己的处理器
pipeline.addLast("MyTestServerHandler",new TestServerHandler());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
6.3 TestServerHandler
package com.fs.netty.http;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
/*
自定义handler
SimpleChannelInboundHandler 是ChannelInboundHandlerAdapter的子类
HttpObject 表示的客户端和服务器端相互通信的数据被封装成httpObject
*/
public class TestServerHandler extends SimpleChannelInboundHandler<HttpObject> {
/**
* 读取客户端数据
* @param channelHandlerContext 上下文
* @param msg 封装的消息为HttpObject
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {
//判断msg是不是HttpRequest请求
if (msg instanceof HttpRequest){
System.out.println("msg 类型 = " + msg.getClass());
System.out.println("客户端地址 : "+ channelHandlerContext.channel().remoteAddress());
//回复信息给浏览器,要发送http协议信息
ByteBuf byteBuf = Unpooled.copiedBuffer("Hello-我是服务器", CharsetUtil.UTF_8);
//构造一个http的响应,即 httpResponse
//参数:HTTP版本 HTTP详情状态码 响应内容
HttpResponse defaultHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
//设置其他的信息
defaultHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");//设置返回参数类型
defaultHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,byteBuf.readableBytes());//设置返回参数长度
//将数据写入并刷新
channelHandlerContext.writeAndFlush(defaultHttpResponse);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
执行结果
7 Unpooled对象
7.1 Unpooled案例1 ByteBuf的使用1
package com.fs.netty.buf;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class NettyByteBuf01 {
public static void main(String[] args) {
//向创建一个bytebuf
//说明:先创建对象,该对象包含一个byte数组,是一个byte[10]
//在netty的buffer中不需要slip进行反转,
//因为它维护了 readerindex 和 writeindex 和 capacity ,将buffer分成了三个区域
//0----》readerindex已经读取的区域
//readerindex-----》writeindex 可读的区域
//writeindex------》capacity 表示可写的区域
ByteBuf buffer = Unpooled.buffer(10);
for (int i = 0; i < 10; i++) {
buffer.writeByte(i);
}
//通过debug就可以看到readerindex和writeindex在不断的变化
System.out.println("capacity:"+buffer.capacity());
//输出
for (int i= 0;i<buffer.capacity();i++){
// System.out.println(buffer.getByte(i));//这个不会照成readerindex变化,因为指定了索引
System.out.println(buffer.readByte());//这会照成readerindex变化
}
}
}
7.2 Unpooled案例2 ByteBuf的使用2
package com.fs.netty.buf;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import java.nio.charset.Charset;
public class NettyByteBuf02 {
public static void main(String[] args) {
//向创建一个bytebuf
ByteBuf byteBuf = Unpooled.copiedBuffer("hello,world", CharsetUtil.UTF_8);
//使用相关方法
if (byteBuf.hasArray()){
byte[] content = byteBuf.array();
//将 content 转成字符串
System.out.println(new String(content, Charset.forName("utf-8")));
//byteBuf=UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 11, cap: 33)
System.out.println("byteBuf="+byteBuf);
System.out.println(byteBuf.arrayOffset());//0
System.out.println(byteBuf.readerIndex());//0
System.out.println(byteBuf.writerIndex());//11
System.out.println(byteBuf.capacity());//33
//由此可得,copiedBuffer这样的方式创建的byteBuf的容量不是给定的字符串的大小
System.out.println(byteBuf.readableBytes());//11
System.out.println(byteBuf.readByte());//这里读一下 值:104 因为H的阿斯克码是104
System.out.println(byteBuf.readableBytes());//10
}
}
}
8 Netty实现群聊系统
8.1服务端
GroupChatServer
package com.fs.netty.groupchat.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/*
群聊服务端
*/
public class GroupChatServer {
private int port;
public GroupChatServer(int port){
this.port = port;
}
//编写一个run方法,处理客户端的请求
public void run() throws InterruptedException {
//创建两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.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 socketChannel) throws Exception {
//获取到pipeline
ChannelPipeline pipeline = socketChannel.pipeline();
//向pipeline加入解码器
pipeline.addLast("decoder",new StringDecoder());
//向pipeline加入编码器
pipeline.addLast("encoder",new StringEncoder());
//加入自己的业务处理类
pipeline.addLast(new GroupChatServerHandler());
}
});
System.out.println("netty服务器已启动:"+port);
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//监听关闭
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
GroupChatServer groupChatServer = new GroupChatServer(7000);
groupChatServer.run();
}
}
GroupChatServerHandler
package com.fs.netty.groupchat.server;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
//创建服务端处理器,指定发送数据为String,重写channelRead0方法
public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {
//定义一个channle组,管理所有的channel
// GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单列
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//handlerAdded 表示链接建立,一旦链接,第一个执行
//一旦链接,将当前的channel加入到 channelGroup
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
//将该客户加入聊天的信息推送给其他在线的客户端
//channelGroup.writeAndFlush 该方法回将channelGroup中所有的channel便利,并发送信息
channelGroup.writeAndFlush(simpleDateFormat.format(new Date())+"[客户端]"+channel.remoteAddress()+"加入聊天\n");
channelGroup.add(channel);
}
/**
* 表示channel处于活动状态,,提示某某上线
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress()+":上线了");
}
/**
* 当channel处于非活动状态,提示离线了
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.err.println(ctx.channel().remoteAddress()+":离线了");
}
/**
* 表示断开链接触发,将某某客户离开信息推送给当前在线的客户
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush(simpleDateFormat.format(new Date())+"[客户端]"+channel.remoteAddress()+"离开了\n");
//这个方法执行后,当前Channel会自动从channelGroup中移除
System.out.println("当前channelGroup大小:"+channelGroup.size());
}
/**
* 读取数据,将数据转发所有在线的人员
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
//获取当前channel
Channel channel = channelHandlerContext.channel();
//这时遍历channelGroup,根据不同情况回送不同的消息
channelGroup.forEach(ch -> {
if (ch != channel) {//说明不是当前的channel、就转发消息
ch.writeAndFlush(simpleDateFormat.format(new Date())+"[客户]"+channel.remoteAddress() + " 发送了消息:"+msg+"\n");
}else {
//显示下自己发送的消息
ch.writeAndFlush(simpleDateFormat.format(new Date())+"[自己]发送了消息"+msg+"\n");
}
});
}
/**
* 异常处理
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭通道
ctx.close();
}
}
8.2 客户端
GroupChatClient
package com.fs.netty.groupchat.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Scanner;
/*
群聊客户端
*/
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 InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入相关的handler
pipeline.addLast("decoder",new StringDecoder());
pipeline.addLast("encoder",new StringEncoder());
//加入自己的处理器
pipeline.addLast(new GroupChatClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
Channel channel = channelFuture.channel();
System.out.println(channel.localAddress()+"客户端已经准备好了");
//客户端输入信息,创建扫描器
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String s = scanner.nextLine();
//通过channel发送服务器端
channel.writeAndFlush(s+"\r\n");
}
}finally {
eventLoopGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
GroupChatClient groupChatClient = new GroupChatClient("127.0.0.1", 7000);
groupChatClient.run();
}
}
GroupChatClientHandler
package com.fs.netty.groupchat.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/*
客户端处理,接受String、
*/
public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {
/*
读取消息
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
System.out.println("[服务端发送的消息] "+ msg);
}
}
8.3 运行效果
9 netty心跳处理(读写空闲)
HeartbeatNettyServer
package com.fs.netty.heartbeat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* netty心跳机制
*/
public class HeartbeatNettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))//加入日志处理类
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入一个netty提供的 IdleStateHandler 处理空闲状态的处理器
//参数说明
//1.long readerIdleTime,表示多长时间没有读了,就会发送一个心跳检测包
//2. long writerIdleTime,表示多长时间没有写了。就会发送一个心跳检测包
//3. long allIdleTime,表示多长时间没有读写了,就会发送一个心跳检测包
//4 unit 时间单位
//当IdleStateEvent触发后,就会传递给管道的下一个handler去处理
//通过调用(触发)下一个handler 的 userEventTinggered。在该方法中去处理,
pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
//加入一个空闲检测进一步处理的handler(自定义)
pipeline.addLast(new HeartbeatNettyHandler());
}
});
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
HeartbeatNettyHandler
package com.fs.netty.heartbeat;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
public class HeartbeatNettyHandler extends ChannelInboundHandlerAdapter {
/**
* @param ctx 上下文
* @param evt 事件
*/
@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("服务器做相应处理");
//如果发生空闲,我们关闭通道
ctx.channel().close();
}
}
}
10 WebSocket案例
案例运行结果
先运行服务,后打开html
MyServer
package com.fs.netty.websocket;
import com.fs.netty.heartbeat.HeartbeatNettyHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
public class MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))//加入日志处理类
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//因为是基于HTTP协议的,因此使用HTTP的编解码器
pipeline.addLast(new HttpServerCodec());
//是以块方式写的,添加ChunkedWriteHeandler
pipeline.addLast(new ChunkedWriteHandler());
/*
1.因为HTTP数据在传输过程中是分段。
HttpObjectAggregator 就是可以将多个段聚合起来
2.这就是为什么当浏览器发送大量数据时,就会发出多次http请求的原因
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/*
对于websocket,他的数据是以帧的形式传递的
可以看到websocketFrame类下面有六个子类
浏览器请求时候,ws://localhost:7000/hello 表示请求的uri
WebSocketServerProtocolHandler 核心功能。是将HTTP协议升级为WS协议,即websocket协议,即长连接
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
//自定义的handler,处理业务逻辑
pipeline.addLast(new MyWebSocketFrameHandler());
}
});
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
MyWebSocketFrameHandler
package com.fs.netty.websocket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.time.LocalDateTime;
//自定义的handler
//TextWebSocketFrame 表示一个文本帧
public class MyWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame msg) throws Exception {
System.out.println("服务器收到的消息:"+msg.text());
//回复客户端
channelHandlerContext.channel().writeAndFlush(new TextWebSocketFrame("服务器时间:"+ LocalDateTime.now()+",收到的消息:"+msg.text()));
}
/*
当web客户端链接后就会触发这个方法
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//id 表示唯一值,asLongText 是唯一的,asShortText不是唯一的,有可能重复
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();
}
}
hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var socket;
//判断当前浏览器是否支持websocket编程
if (window.WebSocket){
socket = new WebSocket("ws://localhost:7000/hello");
//相当于channelRead0 ev 收到服务器端回送的消息
socket.onmessage = function (ev){
var rt = document.getElementById("responseText");
rt.value = rt.value+"\n"+ev.data;
}
//相当于连接服务器开启
socket.onopen = function (ev){
var rt = document.getElementById("responseText");
rt.value = "连接开启"
}
//连接关闭
socket.onclose = function (ev){
var rt = document.getElementById("responseText");
rt.value = rt.value+"\n"+"连接关闭了";
}
//发送消息到服务器
function send(message){
//判断socket是否创建好
if (!window.socket){
return;
}
//判断连接是否开启
if (socket.readyState == WebSocket.OPEN){
//通过socket 发送消息
socket.send(message)
}else {
alert("连接没有开启")
}
}
}else {
alert("您当前的浏览器不支持webSocket")
}
</script>
<form onsubmit="return false">
<textarea name="message" style="height: 300px;width: 300px"></textarea>
<input type="button" value="发送消息" onclick="send(this.form.message.value)">
<textarea id="responseText" style="height: 300px;width: 300px"></textarea>
<input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
</form>
</body>
</html>