Netty的Socket程式設計詳解-搭建服務端與客戶端並進行資料傳輸
場景
Netty在IDEA中搭建HelloWorld服務端並對Netty執行流程與重要元件進行介紹:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108592418
上面對於搭建Netty的HelloWorld已經實現,下面介紹怎樣使用Netty進行Socket程式設計,並搭建服務端與客戶端進行通訊傳輸資料。
注:
部落格:
https://blog.csdn.net/badao_liumang_qizhi
關注公眾號
霸道的程式猿
獲取程式設計相關電子書、教程推送與免費下載。
實現
Socket服務端搭建
在src下新建包,包下新建Socket類作為服務端,並新建main方法
package com.badao.nettySocket; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public class SocketServer { publicstatic void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try{ //服務端啟動類-Netty提供的啟動服務端的類 ServerBootstrap serverBootstrap = new ServerBootstrap();//引數為兩個事件組 前面的用來接收請求 後面的用來處理 //childHandler 設定請求到了之後進行處理的處理器 serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class) .childHandler(new SocketServerInitializer()); //繫結埠 ChannelFuture channelFuture = serverBootstrap.bind(70).sync(); channelFuture.channel().closeFuture().sync(); }finally { //關閉事件組 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
同上面的部落格的流程一樣,在服務端中也需要一個Socket服務端的初始化器SocketServerInitializer()
所以新建類SocketServerInitializer作為服務端的初始化器。
package com.badao.nettySocket; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; public class SocketServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4)); pipeline.addLast(new LengthFieldPrepender(4)); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new SocketServerHandler()); } }
在初始化器中只需要將其繼承ChannelInitializer並且泛型的型別為SocketChannnel。
然後需要重寫其initChannel方法,對通道進行初始化。
在方法中新增一些Netty自帶的處理器,主要是編碼解碼的格式設定。
然後最後再新增自定義的處理器對請求進行處理。
所以再新建一個自定義的服務端處理類SockerServerHandler
package com.badao.nettySocket; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class SocketServerHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("服務端收到來自"+ctx.channel().remoteAddress()+"的"+msg); ctx.channel().writeAndFlush("服務端傳送的資料:(公眾號:霸道的程式猿)"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
在處理器中需要繼承SimpleChannelInboundHandler,泛型的型別就是傳輸資料的型別String
然後重寫其chanelRead0方法,此方法就是具體的處理的方法。
在方法中接受客戶端的資料並向客戶端傳送資料。
然後再重寫exceptionCaught方法,用來捕獲異常並輸出,一旦出現異常則關閉連線。
Socket客戶端搭建
與搭建Socket服務端一致,搭建Socket客戶端也是同樣的流程。
新建SocketClient作為客戶端類並新增main方法
package com.badao.nettySocket; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; public class SocketClient { 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 SocketClientInitializer()); //繫結埠 ChannelFuture channelFuture = bootstrap.connect("localhost", 70); channelFuture.channel().closeFuture().sync(); } finally { //關閉事件組 eventLoopGroup.shutdownGracefully(); } } }
在main方法中連線上面服務端繫結的70埠,注意這裡的啟動類使用的是Bootstrap,並且
客戶端使用的是handler方法,注意與服務端的不同。
然後在客戶端中也使用了客戶端的初始化器,所以新建SocketClientInitializer
package com.badao.nettySocket; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; public class SocketClientInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4)); pipeline.addLast(new LengthFieldPrepender(4)); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new SocketClientHandler()); } }
所新增的處理器與服務端的一致,同樣添加了一個自定義的處理器SocketClientHandler
所以新建類SocketClientHandler
package com.badao.nettySocket; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class SocketClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("客戶端收到來自"+ctx.channel().remoteAddress()+"的"+msg); ctx.channel().writeAndFlush("客戶端向服務端傳送的資料:(公眾號:霸道的程式猿)"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
至此服務端和客戶端已經搭建完成。
依次執行SocketServer的main方法和SocketClient的main方法,如果沒有報錯則是搭建成功。
但是此時雖然客戶端和服務端已經建立連線但是沒有任何的互動傳輸資料和輸出。
這裡因為客戶端或者服務端並沒有發出請求。
所以在客戶端的處理器SocketClientHandler中
重寫channelActive方法,此方法會在通道啟用即建立連線後執行
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush("客戶端發來訊息"); }
在此方法中向服務端傳送字串訊息。
那麼服務端的處理器中的channelRead0方法就會啟用並執行,然後將資料輸出並向客戶端傳送資料。
那麼客戶端的處理器中的channelRead0也會被啟用並執行,那麼客戶端會輸出收到服務端的資料並向服務端傳送資料。
所以客戶端和服務端一直髮送資料。
依次執行服務端和客戶端的main方法,檢視輸出