netty-简易聊天

界面:

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

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值