vlambda博客
学习文章列表

Netty的Socket编程详解-搭建服务端与客户端并进行数据传输

场景

Netty在IDEA中搭建HelloWorld服务端并对Netty执行流程与重要组件进行介绍:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108592418

上面对于搭建Netty的HelloWorld已经实现,下面介绍怎样使用Netty进行Socket编程,并搭建服务端与客户端进行通信传输数据。

注:

实现

Socket服务端搭建

在src下新建包,包下新建Socket类作为服务端,并新建main方法


  
    
    
  

  1. package com.badao.nettySocket;

  2.  

  3. import io.netty.bootstrap.ServerBootstrap;

  4. import io.netty.channel.ChannelFuture;

  5. import io.netty.channel.EventLoopGroup;

  6. import io.netty.channel.nio.NioEventLoopGroup;

  7. import io.netty.channel.socket.nio.NioServerSocketChannel;

  8.  

  9. public class SocketServer {

  10.     public static void main(String[] args) throws  Exception

  11.     {

  12.         EventLoopGroup bossGroup = new NioEventLoopGroup();

  13.         EventLoopGroup workerGroup = new NioEventLoopGroup();

  14.         try{

  15.             //服务端启动类-Netty提供的启动服务端的类

  16.             ServerBootstrap serverBootstrap = new ServerBootstrap();

  17.             //参数为两个事件组 前面的用来接收请求 后面的用来处理

  18.             //childHandler 设置请求到了之后进行处理的处理器

  19.             serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)

  20.                     .childHandler(new SocketServerInitializer());

  21.             //绑定端口

  22.             ChannelFuture channelFuture = serverBootstrap.bind(70).sync();

  23.             channelFuture.channel().closeFuture().sync();

  24.         }finally {

  25.             //关闭事件组

  26.             bossGroup.shutdownGracefully();

  27.             workerGroup.shutdownGracefully();

  28.         }

  29.     }

  30. }

同上面的博客的流程一样,在服务端中也需要一个Socket服务端的初始化器SocketServerInitializer()

所以新建类SocketServerInitializer作为服务端的初始化器。


  
    
    
  
  1. package com.badao.nettySocket;

  2.  

  3. import io.netty.channel.ChannelInitializer;

  4. import io.netty.channel.ChannelPipeline;

  5. import io.netty.channel.socket.SocketChannel;

  6. import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

  7. import io.netty.handler.codec.LengthFieldPrepender;

  8. import io.netty.handler.codec.string.StringDecoder;

  9. import io.netty.handler.codec.string.StringEncoder;

  10. import io.netty.util.CharsetUtil;

  11.  

  12. public class SocketServerInitializer extends ChannelInitializer<SocketChannel> {

  13.     @Override

  14.     protected void initChannel(SocketChannel ch) throws Exception {

  15.         ChannelPipeline pipeline = ch.pipeline();

  16.         pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));

  17.         pipeline.addLast(new LengthFieldPrepender(4));

  18.         pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));

  19.         pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));

  20.         pipeline.addLast(new SocketServerHandler());

  21.     }

  22. }

在初始化器中只需要将其继承ChannelInitializer并且泛型的类型为SocketChannnel。

然后需要重写其initChannel方法,对通道进行初始化。

在方法中添加一些Netty自带的处理器,主要是编码解码的格式设置。

然后最后再添加自定义的处理器对请求进行处理。

所以再新建一个自定义的服务端处理类SockerServerHandler


  
    
    
  
  1. package com.badao.nettySocket;

  2.  

  3. import io.netty.channel.ChannelHandlerContext;

  4. import io.netty.channel.SimpleChannelInboundHandler;

  5.  

  6. public class SocketServerHandler extends SimpleChannelInboundHandler<String> {

  7.     @Override

  8.     protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

  9.         System.out.println("服务端收到来自"+ctx.channel().remoteAddress()+"的"+msg);

  10.     }

  11.  

  12.     @Override

  13.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

  14.         cause.printStackTrace();

  15.         ctx.close();

  16.     }

  17. }

在处理器中需要继承SimpleChannelInboundHandler,泛型的类型就是传输数据的类型String

然后重写其chanelRead0方法,此方法就是具体的处理的方法。

在方法中接受客户端的数据并向客户端发送数据。

然后再重写exceptionCaught方法,用来捕获异常并输出,一旦出现异常则关闭连接。

Socket客户端搭建

与搭建Socket服务端一致,搭建Socket客户端也是同样的流程。

新建SocketClient作为客户端类并新增main方法


  
    
    
  
  1. package com.badao.nettySocket;

  2.  

  3. import io.netty.bootstrap.Bootstrap;

  4. import io.netty.channel.ChannelFuture;

  5. import io.netty.channel.EventLoopGroup;

  6. import io.netty.channel.nio.NioEventLoopGroup;

  7. import io.netty.channel.socket.nio.NioSocketChannel;

  8.  

  9. public class SocketClient {

  10.     public static void main(String[] args) throws  Exception {

  11.         EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

  12.         try {

  13.  

  14.             Bootstrap bootstrap = new Bootstrap();

  15.  

  16.             bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)

  17.                     .handler(new SocketClientInitializer());

  18.             //绑定端口

  19.             ChannelFuture channelFuture = bootstrap.connect("localhost", 70);

  20.             channelFuture.channel().closeFuture().sync();

  21.         } finally {

  22.             //关闭事件组

  23.             eventLoopGroup.shutdownGracefully();

  24.  

  25.         }

  26.     }

  27. }

在main方法中连接上面服务端绑定的70端口,注意这里的启动类使用的是Bootstrap,并且

客户端使用的是handler方法,注意与服务端的不同。

然后在客户端中也使用了客户端的初始化器,所以新建SocketClientInitializer


  
    
    
  
  1. package com.badao.nettySocket;

  2.  

  3. import io.netty.channel.ChannelInitializer;

  4. import io.netty.channel.ChannelPipeline;

  5. import io.netty.channel.socket.SocketChannel;

  6. import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

  7. import io.netty.handler.codec.LengthFieldPrepender;

  8. import io.netty.handler.codec.string.StringDecoder;

  9. import io.netty.handler.codec.string.StringEncoder;

  10. import io.netty.util.CharsetUtil;

  11.  

  12. public class SocketClientInitializer extends ChannelInitializer<SocketChannel> {

  13.     @Override

  14.     protected void initChannel(SocketChannel ch) throws Exception {

  15.         ChannelPipeline pipeline = ch.pipeline();

  16.         pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));

  17.         pipeline.addLast(new LengthFieldPrepender(4));

  18.         pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));

  19.         pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));

  20.         pipeline.addLast(new SocketClientHandler());

  21.     }

  22. }

所添加的处理器与服务端的一致,同样添加了一个自定义的处理器SocketClientHandler

所以新建类SocketClientHandler


  
    
    
  
  1. package com.badao.nettySocket;

  2.  

  3. import io.netty.channel.ChannelHandlerContext;

  4. import io.netty.channel.SimpleChannelInboundHandler;

  5.  

  6. public class SocketClientHandler extends SimpleChannelInboundHandler<String> {

  7.     @Override

  8.     protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

  9.         System.out.println("客户端收到来自"+ctx.channel().remoteAddress()+"的"+msg);

  10.     }

  11.  

  12.     @Override

  13.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

  14.         cause.printStackTrace();

  15.         ctx.close();

  16.     }

  17.  

  18.  

  19. }

至此服务端和客户端已经搭建完成。

依次运行SocketServer的main方法和SocketClient的main方法,如果没有报错则是搭建成功。

但是此时虽然客户端和服务端已经建立连接但是没有任何的交互传输数据和输出。

这里因为客户端或者服务端并没有发出请求。

所以在客户端的处理器SocketClientHandler中

重写channelActive方法,此方法会在通道激活即建立连接后执行


  
    
    
  
  1.     @Override

  2.     public void channelActive(ChannelHandlerContext ctx) throws Exception {

  3.         ctx.writeAndFlush("客户端发来消息");

  4.     }

在此方法中向服务端发送字符串消息。

那么服务端的处理器中的channelRead0方法就会激活并执行,然后将数据输出并向客户端发送数据。

那么客户端的处理器中的channelRead0也会被激活并执行,那么客户端会输出收到服务端的数据并向服务端发送数据。

所以客户端和服务端一直发送数据。

依次运行服务端和客户端的main方法,查看输出