服务端:
package com.example.demo.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;
/**
* netty服务端 它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么
*/
public class EchoServer {
/**
* 线程组
*/
private EventLoopGroup eventLoopGroup;
/**
* 绑定端口
*/
private int port;
public EchoServer(int port) {
this.port = port;
}
/**
* 执行方法
*/
public void start() throws InterruptedException {
try {
//创建引导类:端口绑定,启动NETTY服务
ServerBootstrap serverBootstrap = new ServerBootstrap();
//创建NioEventLoopGroup 线程组来处理事件,如接受新连接、接收数据、写数据等等 包括boss,worker
eventLoopGroup = new NioEventLoopGroup();
//指定通道类型为NioServerSocketChannel,设置InetSocketAddress让服务器监听某个端口已等待客户端连接,并配置线程组
serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress("localhost",port)).childHandler(new ChannelInitializer<Channel>() {
//设置childHandler执行所有的连接请求
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new EchoInServerHandler());
}
});
// 线程同步阻塞等待服务器绑定到指定端口
ChannelFuture channelFuture = serverBootstrap.bind().sync();
//成功绑定到端口之后,给channel增加一个 管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程。
channelFuture.channel().closeFuture().sync();
}finally {
//优雅的关机
eventLoopGroup.shutdownGracefully().sync();
}
}
/**
* 启动方法
*/
public static void main(String[] args) throws InterruptedException {
EchoServer echoServer = new EchoServer(8899);
echoServer.start();
}
/**
* public Promise<V> sync() throws InterruptedException {
* await();
* rethrowIfFailed(); // 异步操作失败抛出异常
* return this;
* }
*
* Future类
* // 异步操作完成且正常终止
* boolean isSuccess();
* // 异步操作是否可以取消
* boolean isCancellable();
* // 异步操作失败的原因
* Throwable cause();
* // 添加一个监听者,异步操作完成时回调,类比javascript的回调函数
* Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
* Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
* // 阻塞直到异步操作完成
* Future<V> await() throws InterruptedException;
* // 同上,但异步操作失败时抛出异常
* Future<V> sync() throws InterruptedException;
* // 非阻塞地返回异步结果,如果尚未完成返回null
* V getNow();
*/
}
package com.example.demo.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import java.util.Date;
public class EchoInServerHandler extends ChannelInboundHandlerAdapter {
/**
*ChannelHandlerContext pipeline 中handle共享的
* 客户端请求读取方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("server 读取数据……");
//读取数据
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
//序列化
String body = new String(req, "UTF-8");
System.out.println("接收客户端数据:" + body);
//向客户端写数据
System.out.println("server向client发送数据" + body);
String currentTime = new Date(System.currentTimeMillis()).toString();
//bytes转byteBuff
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
//然后从byteBuff中写入通道,但是这个write动作也是异步,不是及时执行得。
ChannelFuture channelFuture = ctx.writeAndFlush(resp); //刷新后才将数据发出到SocketChannel
channelFuture.addListener(ChannelFutureListener.CLOSE); //关闭跟客户端的连接通道
//所以可能下面得代码会先执行
// System.out.println("通道写入");
}
/**
* 读取数据完毕走的方法
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("server 读取数据完毕..");
}
/**
* 异常捕获处理
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
//停止往下进行handle
ctx.close();
}
}
客户端
package com.example.demo.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
/**
* NETTY客户端
* 连接服务器 • 写数据到服务器 • 等待接受服务器返回相同的数据 • 关闭连接
*/
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup nioEventLoopGroup = null;
try {
//创建Bootstrap对象用来引导启动客户端
Bootstrap bootstrap = new Bootstrap();
//创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
nioEventLoopGroup = new NioEventLoopGroup();
//创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
bootstrap.group(nioEventLoopGroup).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
//添加一个ChannelHandler,客户端成功连接服务器后就会被执行
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new EchoInClientHandler());
}
});
// • 调用Bootstrap.connect()来连接服务器,阻塞式连接
ChannelFuture f = bootstrap.connect().sync();
//阻塞关闭,直到通道关闭
f.channel().closeFuture().sync();
} finally {
// • 最后关闭EventLoopGroup来释放资源
nioEventLoopGroup.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
new EchoClient("localhost", 8899).start();
}
}
package com.example.demo.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class EchoInClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private ChannelFuture channelFuture;
//客户端连接服务器成功后被调用
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端连接服务器,开始发送数据……");
byte[] req = "你好啊,请给我发送现在的时间(客户端)".getBytes();
ByteBuf firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
channelFuture = ctx.writeAndFlush(firstMessage);
}
//• 从服务器接收到数据后调用
@Override
protected void messageReceived(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
System.out.println("client 读取server数据..");
//服务端返回消息后
ByteBuf buf = byteBuf;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("服务端数据为 :" + body);
channelFuture.addListener(ChannelFutureListener.CLOSE);//关闭和服务端的连接通道,接着会出发通道的关闭,然后回继续执行sync后面的代码,因为对应的通道为连接通道
}
//• 发生异常时被调用
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("client exceptionCaught..");
// 释放资源
ctx.close();
}
/**
* 这三个Listener对象定义了对Channel处理时常用的操作,如果符合需求,可以直接使用。
* ChannelFutureListener CLOSE = (future) --> {
* future.channel().close(); //操作完成时关闭Channel
* };
*
* ChannelFutureListener CLOSE_ON_FAILURE = (future) --> {
* if (!future.isSuccess()) {
* future.channel().close(); // 操作失败时关闭Channel
* }
* };
*
* ChannelFutureListener FIRE_EXCEPTION_ON_FAILURE = (future) --> {
* if (!future.isSuccess()) {
* // 操作失败时触发一个ExceptionCaught事件
* future.channel().pipeline().fireExceptionCaught(future.cause());
* }
* };
*/
}