netty的聊天demo

来源:互联网 发布:夏达 抄袭 知乎 编辑:程序博客网 时间:2024/05/06 02:22
    netty是一个基于nio的通讯框架,如果想要看懂netty那么就先要对nio有一定了解,但这篇文章并不讨论nio。    传统的socket通信的话一个线程只能应对一个客户端,但是nio技术中就可以在一条线程开启多个channel通道应对多个客户端,从而降低服务端的压力。    直接上代码:
public class Server {    private int port;    public Server(int port) {        this.port = port;    }    public void run() throws Exception {        //创建两个EventLoopGroup对象,boss用于接收所有进来的连接然后下发给worker。        // 而worker是用于专门对这个客户端进行服务,有多少个客户端就有多少worker。        EventLoopGroup bossGroup = new NioEventLoopGroup();        EventLoopGroup workerGroup = new NioEventLoopGroup();        try {            //创建一个server端引导项进行各项属性的设置。            ServerBootstrap b = new ServerBootstrap();            b.group(bossGroup, workerGroup);            b.channel(NioServerSocketChannel.class);            b.childHandler(new ServerInitializer());            b.option(ChannelOption.SO_BACKLOG, 128);            b.childOption(ChannelOption.SO_KEEPALIVE, true);            System.out.println("server 启动");            // 同步绑定端口,开始接收进来的连接            ChannelFuture f = b.bind(port).sync();            // 同步等待服务器  socket 关闭 。            f.channel().closeFuture().sync();        } finally {            workerGroup.shutdownGracefully();            bossGroup.shutdownGracefully();            System.out.println("server关闭了");        }    }    public static void main(String[] args) throws Exception {        new Server(16666).run();    }}
    netty的server端的大体完成了,但还需要一个ChannelInitializer以及SimpleChannelInboundHandler。
public class ServerInitializer extends        ChannelInitializer<SocketChannel> {    @Override    public void initChannel(SocketChannel ch) throws Exception {        ChannelPipeline pipeline = ch.pipeline();        /**         * 分割器,遇到换行符则进行分隔并对这个1024字节数据进行处理。这可以用于解决黏包问题,当然更高级的黏包解决方案就需要自己开动脑筋了。         * 但如果你的客户端并不发送带有\n换行符,那么就有可能客户端一直发送消息但你就是没能及时处理         * 导致buffer被写满抛出tooLongFrameLength异常。         */        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()));        /**         * 解码器,如果你的项目是要接收字节数组这种数据的话就不需要StringEncoder,否则它就会将接收到的数据进行默认环境下编码成字符串;         */        pipeline.addLast("decoder", new StringDecoder());        /**         * 编码器,将你要发送给客户端的数据编码为字符串。         */        pipeline.addLast("encoder", new StringEncoder());        /**         * handler         */        pipeline.addLast("handler", new ServerHandler());        System.out.println("客户端:" + ch.remoteAddress() + "连接上");    }}
public class ServerHandler extends SimpleChannelInboundHandler<String> {    //创建一个全局的channelGroup以方便调用。    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);    @Override    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {        Channel incoming = ctx.channel();        channels.writeAndFlush(incoming.remoteAddress() + " 加入\n");        //将连接上的channel添加进Group中去,以便调用        channels.add(ctx.channel());    }    /**     * 客户端close时调用该方法。     *     * @param ctx     * @throws Exception     */    @Override    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {        Channel incoming = ctx.channel();        channels.writeAndFlush(incoming.remoteAddress() + " 离开\n");    }    /**     * 读取内容。     *     * @param ctx     * @param s     * @throws Exception     */    @Override    protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {        Channel incoming = ctx.channel();        for (Channel channel : channels) {            if (channel != incoming) {                //向客户端写入数据                channel.writeAndFlush("服务端已收到\n");                System.out.println("客户端说:" + s);            } else {                //向客户端写入数据                channel.writeAndFlush("服务端已收到\n");                System.out.println("客户端说:" + s);            }        }    }    /**     * 检测是否在线方法。     *     * @param ctx     * @throws Exception     */    @Override    public void channelActive(ChannelHandlerContext ctx) throws Exception {        Channel incoming = ctx.channel();        System.out.println("客户端:" + incoming.remoteAddress() + "在线");    }    /**     * 客户端掉线时调用该方法     *     * @param ctx     * @throws Exception     */    @Override    public void channelInactive(ChannelHandlerContext ctx) throws Exception {        Channel incoming = ctx.channel();        System.out.println("客户端:" + incoming.remoteAddress() + "掉线");    }    /**     * 该channel发生异常时调用的方法,     *     * @param ctx     * @param cause     */    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {        Channel incoming = ctx.channel();        System.out.println("客户端:" + incoming.remoteAddress() + "异常");        // 当出现异常就关闭连接        cause.printStackTrace();        ctx.close();    }}
    ------------------分割线---------------

接下来的是客户端代码:

public class Client {    public static void main(String[] args) throws Exception{        new Client("localhost", 16666).run();    }    private final String host;    private final int port;    public Client(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 ClientInitializer());            //同步连接到服务端            Channel channel = bootstrap.connect(host, port).sync().channel();            //读取控制台的信息。            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));            while(true){                //channel向服务端发送讯息。                channel.writeAndFlush(in.readLine() + "\r\n");            }        } catch (Exception e) {            e.printStackTrace();        } finally {            group.shutdownGracefully();        }    }}
public class ClientInitializer extends ChannelInitializer<SocketChannel> {    @Override    public void initChannel(SocketChannel ch) throws Exception {        ChannelPipeline pipeline = ch.pipeline();        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()));        pipeline.addLast("decoder", new StringDecoder());        pipeline.addLast("encoder", new StringEncoder());        pipeline.addLast("handler", new ClientHandler());    }}
public class ClientHandler extends  SimpleChannelInboundHandler<String> {    @Override    protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {        System.out.println(s);    }}
         一个Netty的基本通讯demo就完成了。
0 0