實現場景: 聊天
服務端,客戶端A,客戶端B,客戶端C。當客戶端發送消息給服務端后,服務端在將這條消息廣播個所有客戶端戶端A,客戶端B,客戶端C。
需求1: 客戶端上線后,會通知所有客戶端上線。
如客戶端A先建立連接,不需要通知。
當客戶端B與服務端建立連接,服務端告訴A,客戶端B上線。
A和B建立連接后,客戶端C和服務端建立連接。服務端廣播一條信息給A和B。
需求2: A、B、C都已經建立連接,當A發送一條給服務端,服務端廣播這條消息給所有客戶端,客戶端A會提示這條消息是自己發送的。
一、服務端程序的編寫
1、MyChartServer 類
public class MyChartServer { public static void main(String[] args) throws Exception{ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class) .childHandler(new MyChatServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
2、MyChatServerInitializer 類
public class MyChatServerInitializer extends ChannelInitializer<SocketChannel>{ protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter())); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new MyChartServerHandle()); } }
3、MyChartServerHandle 類
public class MyChartServerHandle extends SimpleChannelInboundHandler<String>{ //用於保存所有Channel對象 private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { //channel為當前客戶端發給服務端的channel Channel channel = ctx.channel(); channelGroup.forEach(ch -> { if(channel != ch){ ch.writeAndFlush(channel.remoteAddress() + " 發送的消息:" + msg + "\n"); } else{ ch.writeAndFlush("[自己]" + msg + " \n"); } }); } //表示連接建立 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { //chanel可以理解成Connection Channel channel = ctx.channel(); //廣播消息給所有的客戶端 channelGroup.writeAndFlush("[服務器] - " + channel.remoteAddress() + " 加入\n"); channelGroup.add(channel); } //表示連接斷掉了 @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //廣播消息給所有的客戶端 channelGroup.writeAndFlush("[服務器] - " + channel.remoteAddress() + " 離開\n"); //下面這行代碼Netty會自動調用 //channelGroup.remove(channel); } //表示連接時活動狀態 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //廣播消息給所有的客戶端 CommonUtil.println( channel.remoteAddress() + " 上線 \n"); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //廣播消息給所有的客戶端 CommonUtil.println( channel.remoteAddress() + " 下線 \n"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace();; ctx.close(); } }
二、客戶端程序編寫
1、MyChatClient類
public class MyChatClient { public static void main(String[] args) throws Exception{ EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class) .handler(new MyChatClientInitializer()); //channel表示與服務端的Connection Channel channel = bootstrap.connect("localhost",8899).sync().channel(); //不斷讀取客戶端輸入的內容 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); for(;;){ channel.writeAndFlush(br.readLine() + "\r\n"); } }finally { eventLoopGroup.shutdownGracefully(); } } }
2、MyChatClientInitializer 類
public class MyChatClientInitializer extends ChannelInitializer<SocketChannel> { protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter())); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new MyChartClientHandle()); } }
3、MyChartClientHandle 類
public class MyChartClientHandle extends SimpleChannelInboundHandler<String> { // 對於客戶端來說,輸入來自控制台 protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { //僅僅打印來自服務端的信息 CommonUtil.println(msg); } }
三、測試
1、啟動MyChartServer,然后啟動MyChatClient
MyChartServer打印 59786 上線
2、再啟動一個客戶端
可以發現60635 上線,
並且第一個客戶端提示 60635 加入
3、再啟動一個客戶端
提示60966上線
第一個客戶端 提示60966 加入
第二個客戶端 提示60966 加入
四、測試2
1、客戶端A發送消息: 大家好,我是客戶端A
客戶端A接收到寫信息: [自己]大家好,我是客戶端A
客戶端B接收到信息
客戶端C接收到的信息
自此,實現了通過多個Socket實現的通信的過程