界面:
package com.zy.nettychat;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ClientFrame extends Frame {
public static final ClientFrame INSTANCE = new ClientFrame();
private TextArea ta = new TextArea();
private TextField tf = new TextField();
private ChatClient chatClient;
public ClientFrame() {
this.setTitle("聊天室");
this.setSize(300,400);
this.setLocation(400, 20);
this.add(ta, BorderLayout.CENTER);
this.add(tf, BorderLayout.SOUTH);
// 输入监听
tf.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
chatClient.send(tf.getText());
tf.setText("");
}
});
// 关闭监听
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
chatClient.closeConnect();
System.exit(0);
}
});
}
// 启动时,客户端连接服务器
public void connectToServer() {
chatClient = new ChatClient();
chatClient.connect();
}
public static void main(String[] args) {
ClientFrame clientFrame = ClientFrame.INSTANCE;
clientFrame.setVisible(true);
clientFrame.connectToServer();
}
public void updateText(String str) {
ta.setText(ta.getText() + str + System.lineSeparator());
}
}
客户端:
package com.zy.nettychat;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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 io.netty.util.ReferenceCountUtil;
public class ChatClient {
private Channel channel;
public void connect() {
// 负责服务
EventLoopGroup workerGroup = new NioEventLoopGroup(4);
// 服务类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
// netty帮我们内部处理了accept过程
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
channel = socketChannel;
socketChannel.pipeline().addLast(new MyHandler());
}
});
try {
ChannelFuture channelFuture = bootstrap.connect("localhost", 8888).sync();
// closeFuture表示如果有人调用了channel的close的方法,那么才会拿到close的future
// 如果没有关,就会阻塞到这里
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
public void send(String text) {
channel.writeAndFlush(Unpooled.copiedBuffer(text.getBytes()));
}
public void closeConnect() {
send("__bye__");
channel.close();
}
}
class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = null;
try {
buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
String str = new String(bytes);
System.out.println(str);
System.out.println(buf.refCnt());
// 将数据更新到界面上
ClientFrame.INSTANCE.updateText(str);
} finally {
if (buf != null) {
ReferenceCountUtil.release(buf);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
服务器:
package com.zy.nettychat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.util.Objects;
public class ChatServer {
// 负责装载所有的客户端chnnel,方便向客户端写数据
public static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static void main(String[] args) {
// 负责接客
EventLoopGroup bossGroup = new NioEventLoopGroup(2);
// 负责服务
EventLoopGroup workerGroup = new NioEventLoopGroup(4);
// 服务类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
// netty帮我们内部处理了accept过程
bootstrap.childHandler(new MyServerChildInitializer());
try {
ChannelFuture channelFuture = bootstrap.bind(8888).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class MyServerChildInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MyChildHandler());
System.out.println("a client connected !");
}
}
class MyChildHandler extends ChannelInboundHandlerAdapter {
// 表示客户端通道可用,因此在这里面添加客户端chnnel
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ChatServer.clients.add(ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
String str = new String(bytes);
System.out.println("客户端数据:" + str);
if (Objects.equals(str, "__bye__")) {
System.out.println("client ready to quit");
ChatServer.clients.remove(ctx.channel()); // 会自动释放buf,不需要显式关闭
ctx.close();
} else {
ChatServer.clients.writeAndFlush(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ChatServer.clients.remove(ctx.channel());
cause.printStackTrace();
ctx.close();
}
}
Encoder和Decoder
例如将坦克的位置传输给服务器,可以有两种方式:
第一种:
"30,20".getBytes() ->服务器
第二种:
30->[0100110011011...] 32位字节数组
20->[...] 32位字节数组
将8byte send to Server->
两种方式哪种更好???
第二种最好
字符串长度不固定,编码不固定,甚至语言不固定。并且转字符串效率比较低。(为什么低呢???)
编码器和解码器由Netty自动识别调用,不用担心在需要Encoder时会误调用Decoder,也不用担心在需要Decoder时误用encoder
多种不同的编解码器可以混在一起使用共同实现程序的业务逻辑。
定义消息
package com.zy.nettycoder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class TankMsg {
private int x;
private int y;
public TankMsg(int x, int y) {
this.x = x;
this.y = y;
}
}
客户端:
package com.zy.nettycoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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 io.netty.util.ReferenceCountUtil;
public class TankClient {
private Channel channel;
public void connect() {
// 负责服务
EventLoopGroup workerGroup = new NioEventLoopGroup(4);
// 服务类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
// netty帮我们内部处理了accept过程
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
channel = socketChannel;
socketChannel.pipeline()
.addLast(new TankMsgEncoder()) // 天添加编码器,貌似必 须在handler之前啊!!!
.addLast(new TankMsgClientHandler());
}
});
try {
ChannelFuture channelFuture = bootstrap.connect("localhost", 8888).sync();
// closeFuture表示如果有人调用了channel的close的方法,那么才会拿到close的future
// 如果没有关,就会阻塞到这里
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
public void send(String text) {
channel.writeAndFlush(Unpooled.copiedBuffer(text.getBytes()));
}
public void closeConnect() {
send("__bye__");
channel.close();
}
}
class TankMsgClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(new TankMsg(5, 8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = null;
try {
buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
String str = new String(bytes);
System.out.println(str);
System.out.println(buf.refCnt());
} finally {
if (buf != null) {
ReferenceCountUtil.release(buf);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
public static void main(String[] args) {
new TankClient().connect();
}
}
定义编码器:
package com.zy.nettycoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class TankMsgEncoder extends MessageToByteEncoder<TankMsg> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, TankMsg tankMsg, ByteBuf byteBuf) throws Exception {
byteBuf.writeInt(tankMsg.getX());
byteBuf.writeInt(tankMsg.getY());
}
}
定义服务器:
package com.zy.nettycoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.GlobalEventExecutor;
public class TankMsgServer {
// 负责装载所有的客户端chnnel,方便向客户端写数据
public static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static void serverStart() {
// 负责接客
EventLoopGroup bossGroup = new NioEventLoopGroup(2);
// 负责服务
EventLoopGroup workerGroup = new NioEventLoopGroup(4);
// 服务类
ServerBootstrap bootstrap = new ServerBootstrap();
// 异步全双工
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
// netty帮我们内部处理了accept过程
bootstrap.childHandler(new TankMsgServerChildInitializer());
try {
ChannelFuture channelFuture = bootstrap.bind(8888).sync();
TankMsgServerFrame.INSTANCE.updateServerMsg("server started!");
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class TankMsgServerChildInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new TankMsgDecoder())
.addLast(new TankMsgChildHandler());
System.out.println("a client connected !");
}
}
class TankMsgChildHandler extends ChannelInboundHandlerAdapter {
// 表示客户端通道可用,因此在这里面添加客户端chnnel
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
TankMsgServer.clients.add(ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("开始读取数据!!!");
TankMsg tankMsg = (TankMsg) msg;
TankMsgServerFrame.INSTANCE.updateClientMsg(tankMsg.toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
TankMsgServer.clients.remove(ctx.channel());
cause.printStackTrace();
ctx.close();
}
}
定义解码器:
package com.zy.nettycoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class TankMsgDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
System.out.println("TankMsgDecoder1111");
if (byteBuf.readableBytes() < 8) {
return;
}
int x = byteBuf.readInt();
int y = byteBuf.readInt();
list.add(new TankMsg(x, y));
}
}
定义服务器界面:
package com.zy.nettycoder;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.HeadlessException;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class TankMsgServerFrame extends Frame {
public static final TankMsgServerFrame INSTANCE = new TankMsgServerFrame();
private TextArea taServer = new TextArea();
private TextArea taClient = new TextArea();
public TankMsgServerFrame() throws HeadlessException {
this.setSize(800, 600);
this.setLocation(300, 30);
Panel panel = new Panel(new GridLayout(1, 2));
panel.add(taServer);
panel.add(taClient);
this.add(panel);
taServer.setFont(new Font("Consolas", Font.PLAIN, 25));
taClient.setFont(new Font("Consolas", Font.PLAIN, 25));
updateServerMsg("server:");
updateClientMsg("client:");
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void updateServerMsg(String text) {
taServer.setText(taServer.getText() + text + System.lineSeparator());
}
public void updateClientMsg(String text) {
taClient.setText(taClient.getText() + text + System.lineSeparator());
}
public static void main(String[] args) {
TankMsgServerFrame.INSTANCE.setVisible(true);
TankMsgServer.serverStart();
}
}
效果:
单元测试:
package com.zy.nettycoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.Assert;
public class TankMsgTest {
@org.junit.Test
public void decode() {
EmbeddedChannel embeddedChannel = new EmbeddedChannel();
embeddedChannel.pipeline().addLast(new TankMsgDecoder());
ByteBuf buf = Unpooled.buffer();
buf.writeInt(5);
buf.writeInt(8);
embeddedChannel.writeInbound(buf);
TankMsg tankMsg = embeddedChannel.readInbound();
Assert.assertEquals(tankMsg.getX(), 5);
Assert.assertEquals(tankMsg.getY(), 8);
}
@org.junit.Test
public void encode() {
EmbeddedChannel embeddedChannel = new EmbeddedChannel();
embeddedChannel.pipeline().addLast(new TankMsgEncoder());
TankMsg tankMsg = new TankMsg(5, 8);
embeddedChannel.writeOutbound(tankMsg);
ByteBuf buf = embeddedChannel.readOutbound();
int x = buf.readInt();
int y = buf.readInt();
Assert.assertEquals(x, 5);
Assert.assertEquals(y, 8);
}
}
使用EmbeddedChannel模拟出来一个通道。
Encode是Out Bound
Decode是in Bound