Netty介绍(2)————多人聊天室实现Dome

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25673113/article/details/79489613

git地址:https://github.com/lkj41110/netty_dome

服务端

/**
 * 多人聊天例子服务器
 * @author lkj41110
 * @version time:2017年1月16日 下午9:54:55
 */
public class ServerMain {

   private int port;

   public ServerMain(int port) {
      this.port = port;
   }

   public static void main(String[] args) {
      new ServerMain(2000).run();
   }

   public void run() {
      EventLoopGroup acceptor = new NioEventLoopGroup();
      EventLoopGroup worker = new NioEventLoopGroup();
      ServerBootstrap bootstrap = new ServerBootstrap();
      bootstrap.option(ChannelOption.SO_BACKLOG, 1024);

      bootstrap.group(acceptor, worker);//设置循环线程组,前者用于处理客户端连接事件,后者用于处理网络IO
       bootstrap.channel(NioServerSocketChannel.class);//用于构造socketchannel工厂
       bootstrap.childHandler(new ServerIniterHandler());//为处理accept客户端的channel中的pipeline添加自定义处理函数
        try {
         // 服务器绑定端口监听
         Channel channel = bootstrap.bind(port).sync().channel();
         System.out.println("server strart running in port:" + port);

         // 监听服务器关闭监听
         channel.closeFuture().sync();
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         // 退出
         acceptor.shutdownGracefully();
         worker.shutdownGracefully();
      }
   }
}

服务端启动的时候先要创建一个ServerBootstrap 实例。然后需要绑定内置的传输模式。netty内置了NIO,Epoll,OIO,JVM内部传输的传输模式,分别对应了NioEventLoopGroup , EpollEventLoopGroup , OioEventLoopGroup , LocalEventLoopGroup四个模式。一般都使用NIO,所以我们使用NioEventLoopGroup。

EventLoopGroup acceptor = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
//都是使用了NIO。
bootstrap.group(acceptor, worker);
bootstrap.channel(NioServerSocketChannel.class);

从上面的代码我们看到了bootstrap绑定了两个NioEventLoopGroup(可以看做线程池),这种是Reactor主从线程模型,用来接受和处理新的连接,并且将Channel的类型指定为NioServer- SocketChannel 。在此之后,你将本地地址设置为一个具有选定端口的 InetSocket- Address 。服务器将绑定到这个地址以监听新的连接请求。



public class ServerIniterHandler extends  ChannelInitializer<SocketChannel> {

   @Override
   protected void initChannel(SocketChannel arg0) throws Exception {
      ChannelPipeline pipeline = arg0.pipeline();
      pipeline.addLast("decode",new StringDecoder());
      pipeline.addLast("encode",new StringEncoder());
      pipeline.addLast("chat",new ChatServerHandler());

   }
}

ServerIniterHandler里主要是绑定ChannelPipeline链,里面我们设置了StringDecoder,StringEncoder帮助我们接收到的信息未为String类型,并设置了ChatServerHandler业务处理类,服务端的业务处理逻辑主要由ChatServerHandler实现。


/**
 * 服务器主要的业务逻辑
 */
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {

    //保存所有活动的用户
    public static final ChannelGroup group = new DefaultChannelGroup(
            GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext arg0, String arg1)
            throws Exception {
        Channel channel = arg0.channel();
        //当有用户发送消息的时候,对其他用户发送信息
        for (Channel ch : group) {
            if (ch == channel) {
                ch.writeAndFlush("[you]:" + arg1 + "\n");
            } else {
                ch.writeAndFlush(
                        "[" + channel.remoteAddress() + "]: " + arg1 + "\n");
            }
        }
        System.out.println("[" + channel.remoteAddress() + "]: " + arg1 + "\n");
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        for (Channel ch : group) {
            ch.writeAndFlush(
                    "[" + channel.remoteAddress() + "] " + "is comming");
        }
        group.add(channel);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        for (Channel ch : group) {
            ch.writeAndFlush(
                    "[" + channel.remoteAddress() + "] " + "is comming");
        }
        group.remove(channel);
    }

    //在建立链接时发送信息
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("[" + channel.remoteAddress() + "] " + "online");
        ctx.writeAndFlush("[server]: welcome");
    }

    //退出链接
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("[" + channel.remoteAddress() + "] " + "offline");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.out.println(
                "[" + ctx.channel().remoteAddress() + "]" + "exit the room");
        ctx.close().sync();
    }

}

客户端的代码也基本类似与服务端,最大的区别是客户端的启动代码是创建的Bootstrap示例,并且只设置了NioEventLoopGroup一个线程池来处理连接。


客户端

/**
 * 多人聊天客户端(在服务端开启后开启,可开多个)
 * @author lkj41110
 * @version time:2017年1月16日 下午9:55:41
 */
public class ClientMain {
   private String host;
   private int port;
   private boolean stop = false;

   public ClientMain(String host, int port) {
      this.host = host;
      this.port = port;
   }

   public static void main(String[] args) throws IOException {
      new ClientMain("127.0.0.1", 2000).run();
   }

   public void run() throws IOException {
       //设置一个worker线程,使用
      EventLoopGroup worker = new NioEventLoopGroup();
      Bootstrap bootstrap = new Bootstrap();
      bootstrap.group(worker);
      //指定所使用的 NIO 传输 Channel
      bootstrap.channel(NioSocketChannel.class);
      bootstrap.handler(new ClientIniterHandler());

      try {
          //使用指定的 端口设置套 接字地址
         Channel channel = bootstrap.connect(host, port).sync().channel();
         while (true) {
             //向服务端发送内容
            BufferedReader reader = new BufferedReader(
                  new InputStreamReader(System.in));
            String input = reader.readLine();
            if (input != null) {
               if ("quit".equals(input)) {
                  System.exit(1);
               }
               channel.writeAndFlush(input);
            }
         }
      } catch (InterruptedException e) {
         e.printStackTrace();
         System.exit(1);
      }
   }

}

public class ClientIniterHandler extends ChannelInitializer<SocketChannel> {

   @Override
   protected void initChannel(SocketChannel arg0) throws Exception {
      ChannelPipeline pipeline = arg0.pipeline();
      pipeline.addLast("stringD", new StringDecoder());
      pipeline.addLast("stringC", new StringEncoder());
      pipeline.addLast("http", new HttpClientCodec());
      pipeline.addLast("chat", new ChatClientHandler());
   }

}

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {

   @Override
   protected void channelRead0(ChannelHandlerContext arg0, String arg1)
         throws Exception {
       //客户端主要用来接收服务器发送的消息
      System.out.println(arg1);
   }

}
运行结果
//多个用户连接在线。服务端
server strart running in port:2000
[/127.0.0.1:60628] online
[/127.0.0.1:60628]: 123

[/127.0.0.1:60628]: 12312

[/127.0.0.1:60628]: 312312

[/127.0.0.1:60744] online
[/127.0.0.1:60744]: 123123

[/127.0.0.1:60744]: 1231231

[/127.0.0.1:60744] offline
[/127.0.0.1:60628]: 1232131

[/127.0.0.1:60628] offline

//客户端
[server]: welcome
123
[you]:123

12312
[you]:12312

312312
[you]:312312

[/127.0.0.1:60744] is comming
[/127.0.0.1:60744]: 123123

[/127.0.0.1:60744]: 1231231

[/127.0.0.1:60744] is comming
1232131
[you]:1232131

以上是一个简单的netty Dome。其中用到了netty重要的组件(以后会一一分析):

  • Reactor线程模型
  • Bootstrap or ServerBootstrap
  • EventLoop
  • EventLoopGroup
  • ChannelPipeline
  • Channel
  • Future or ChannelFuture
  • ChannelInitializer
  • ChannelHandler

猜你喜欢

转载自blog.csdn.net/qq_25673113/article/details/79489613
今日推荐