Netty 应用:Socket服务器
Socket服务器
Netty作为Socket服务器,需要编写服务端和客户端,服务器端和客户端收发消息通过自定义的Handler.channelRead0方法来交互,客户端连接上服务器后,需要在active时向服务器发送一条消息来触发服务器的行为。
服务端实现
/** * Created by fubin on 2019/7/11. */ public class SocketServer { public static void main(String[] args) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new SocketServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class SocketServerInitializer extends ChannelInitializer<SocketChannel>{ protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline channelPipeline = socketChannel.pipeline(); channelPipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4)); channelPipeline.addLast(new LengthFieldPrepender(4)); channelPipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new SocketServerHandler()); } } class SocketServerHandler extends SimpleChannelInboundHandler<String>{ protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { //远程地址 System.out.println(ctx.channel().remoteAddress()+","+msg); //处理完业务,把结果返回给客户端 ctx.channel().writeAndFlush("from server:"+ UUID.randomUUID()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); //出现异常,关闭连接 ctx.close(); } }
客户端实现
/** * Created by fubin on 2019/7/11. */ public class SocketClient { public static void main(String[] args) { EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try{ Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new SocketClientInitializer()); ChannelFuture future = bootstrap.connect("localhost",8899).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { eventLoopGroup.shutdownGracefully(); } } } class SocketClientInitializer extends ChannelInitializer<SocketChannel>{ protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline channelPipeline = ch.pipeline(); channelPipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4)); channelPipeline.addLast(new LengthFieldPrepender(4)); channelPipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new SocketClientHandler()); } } class SocketClientHandler extends SimpleChannelInboundHandler<String>{ protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println(ctx.channel().remoteAddress()); System.out.println("client output:"+msg); ctx.channel().writeAndFlush("from client:"+ LocalDateTime.now()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); ctx.close(); } //在连接激活时,客户端向服务器发送一条消息 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.channel().writeAndFlush("hello world socket"); } }
Socket实现的聊天程序消息群发
netty实现多客户端消息群发的关键 : ChannelGroup
服务端实现
/** * Created by fubin on 2019/7/11. * 聊天程序,消息群发 */ public class ChatServer { public static void main(String[] args) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChatServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class ChatServerInitializer extends ChannelInitializer<SocketChannel> { protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline channelPipeline = socketChannel.pipeline(); //分隔符编解码器 channelPipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter())); channelPipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new ChatServerHandler()); } } class ChatServerHandler extends SimpleChannelInboundHandler<String> { //保存channel对象 private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { Channel channel = ctx.channel(); //广播消息 channels.forEach(ch ->{ if(channel != ch){ ch.writeAndFlush(channel.remoteAddress() + "发送的消息:"+ msg + "\n"); }else{ ch.writeAndFlush(" 【自己】" + msg + "\n"); } }); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); //出现异常,关闭连接 ctx.close(); } /** * 连接建立,告诉其他客户端,xxx已经上线 * @param ctx * @throws Exception */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //广播 channels.writeAndFlush("【服务器】 - " + channel.remoteAddress() + "加入\n"); channels.add(channel); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //广播 channels.writeAndFlush("【服务器】 - " + channel.remoteAddress() + "离开\n"); //此代码可不要,netty自动移除 //channels.remove(channel); //验证 System.out.println("channelGroup size :"+channels.size()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); System.out.println("【服务器】- "+ channel.remoteAddress() + "下线\n"); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); System.out.println("【服务器】- "+ channel.remoteAddress() + "上线\n"); } }
客户端实现
/** * Created by fubin on 2019/7/11. */ public class ChatClient { public static void main(String[] args) { EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try{ Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new ChatClientInitializer()); Channel channel = bootstrap.connect("localhost",8899).sync().channel(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //循环监听客户端输入 for(;;){ channel.writeAndFlush(br.readLine()+"\r\n"); } } catch (InterruptedException | IOException e) { e.printStackTrace(); } finally { eventLoopGroup.shutdownGracefully(); } } } class ChatClientInitializer extends ChannelInitializer<SocketChannel>{ protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline channelPipeline = ch.pipeline(); channelPipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter())); channelPipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); channelPipeline.addLast(new ChatClientHandler()); } } class ChatClientHandler extends SimpleChannelInboundHandler<String>{ protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println(msg); } }
应用内的推送的思考: 服务器端和客户端维持长连接推送给app,如果客户端断线,服务端需要把消息保存在数据库或者mongodb中,当客户端重连后,服务端再再重新发送。