netty入门实现简单的echo程序

妖狐艹你老母 2024-02-19 18:38 108阅读 0赞

最近看以往在程序中编写的代码,发现有一个功能是使用socket通讯来实现的,而那个时候使用的是基于bio的阻塞io来实现的,最近在看netty,发现可以使用netty来使用nio的方式来实现,此博客记录一下netty学习的一个过程,此处实现一个简单的服务器和客户端之间的通讯。

实现要求:

1、服务器端监听9090端口

2、客户端连上服务端时,向服务端发送10条消息

3、使用netty自带的解码器解决tcp的粘包问题,避免接收到的数据是不完整的。(不了解tcp粘包的可以百度一下

实现:(代码的注释大致都写在了服务端这个类上)

一、引入netty的jar包

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.huan.netty</groupId>
  5. <artifactId>netty-study</artifactId>
  6. <version>0.0.1-SNAPSHOT</version>
  7. <packaging>jar</packaging>
  8. <name>netty-study</name>
  9. <url>http://maven.apache.org</url>
  10. <properties>
  11. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  12. </properties>
  13. <dependencies>
  14. <dependency>
  15. <groupId>junit</groupId>
  16. <artifactId>junit</artifactId>
  17. <version>4.10</version>
  18. <scope>test</scope>
  19. </dependency>
  20. <dependency>
  21. <groupId>io.netty</groupId>
  22. <artifactId>netty-all</artifactId>
  23. <version>4.1.6.Final</version>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.projectlombok</groupId>
  27. <artifactId>lombok</artifactId>
  28. <version>1.16.18</version>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.slf4j</groupId>
  32. <artifactId>slf4j-api</artifactId>
  33. <version>1.7.25</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>ch.qos.logback</groupId>
  37. <artifactId>logback-classic</artifactId>
  38. <version>1.1.7</version>
  39. </dependency>
  40. <dependency>
  41. <groupId>ch.qos.logback</groupId>
  42. <artifactId>logback-core</artifactId>
  43. <version>1.1.7</version>
  44. </dependency>
  45. <dependency>
  46. <groupId>ch.qos.logback</groupId>
  47. <artifactId>logback-access</artifactId>
  48. <version>1.1.7</version>
  49. </dependency>
  50. </dependencies>
  51. </project>

二、netty服务端的编写

  1. @Slf4j
  2. public class EchoServer {
  3. public static void main(String[] args) throws InterruptedException {
  4. /** 此线程组用于服务单接收客户端的连接 */
  5. EventLoopGroup boss = new NioEventLoopGroup(1);
  6. /** 此线程组用于处理SocketChannel的读写操作 */
  7. EventLoopGroup worker = new NioEventLoopGroup();
  8. /** netty启动服务端的辅助类 */
  9. ServerBootstrap bootstrap = new ServerBootstrap();
  10. bootstrap.group(boss, worker)//
  11. .channel(NioServerSocketChannel.class)// 对应的是ServerSocketChannel类
  12. .option(ChannelOption.SO_BACKLOG, 128)//
  13. .handler(new LoggingHandler(LogLevel.TRACE))//
  14. .childHandler(new ChannelInitializer<SocketChannel>() {
  15. @Override
  16. protected void initChannel(SocketChannel ch) throws Exception {
  17. ByteBuf delimiter = Unpooled.copiedBuffer("^^".getBytes(StandardCharsets.UTF_8));
  18. // 表示客户端的数据中只要出现了^^就表示是一个完整的包,maxFrameLength这个表示如果在这个多个字节中还没有出现则表示数据有异常情况,抛出异常
  19. ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
  20. // 将接收到的数据转换成String的类型
  21. ch.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
  22. // 接收到的数据由自己的EchoServerHandler类进行处理
  23. ch.pipeline().addLast(new EchoServerHandler());
  24. }
  25. });
  26. // 绑定端口,同步等待成功
  27. ChannelFuture future = bootstrap.bind(9090).sync();
  28. log.info("server start in port:[{}]", 9090);
  29. // 等待服务端链路关闭后,main线程退出
  30. future.channel().closeFuture().sync();
  31. // 关闭线程池资源
  32. boss.shutdownGracefully();
  33. worker.shutdownGracefully();
  34. }
  35. @Slf4j
  36. static class EchoServerHandler extends ChannelInboundHandlerAdapter {
  37. private int counter = 0;
  38. /**
  39. * 接收到数据的时候调用
  40. */
  41. @Override
  42. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  43. String body = (String) msg;
  44. log.info("this is: {} times receive cliemt msg: {}", ++counter, body);
  45. body += "^^";
  46. // 此处将数据写会到客户端,如果使用的是ctx.write方法这个只是将数据写入到缓冲区,需要再次调用ctx.flush方法
  47. ctx.writeAndFlush(Unpooled.copiedBuffer(body.getBytes(StandardCharsets.UTF_8)));
  48. }
  49. /** 当发生了异常时,次方法调用 */
  50. @Override
  51. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  52. log.error("error:", cause);
  53. ctx.close();
  54. }
  55. }
  56. }

三、netty客户端代码的编写

客户端在服务端链接建立成功后发送了10条数据给服务端

  1. @Slf4j
  2. public class EchoClient {
  3. public static void main(String[] args) throws InterruptedException {
  4. EventLoopGroup group = new NioEventLoopGroup();
  5. Bootstrap bootstrap = new Bootstrap();
  6. bootstrap.group(group)//
  7. .channel(NioSocketChannel.class)//
  8. .option(ChannelOption.TCP_NODELAY, true)//
  9. .handler(new ChannelInitializer<SocketChannel>() {
  10. @Override
  11. protected void initChannel(SocketChannel ch) throws Exception {
  12. ByteBuf delimiter = Unpooled.copiedBuffer("^^".getBytes(StandardCharsets.UTF_8));
  13. ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
  14. ch.pipeline().addLast(new StringDecoder());
  15. ch.pipeline().addLast(new EchoClientHandler());
  16. }
  17. });
  18. ChannelFuture future = bootstrap.connect("127.0.0.1", 9090).sync();
  19. log.info("client connect server.");
  20. future.channel().closeFuture().sync();
  21. group.shutdownGracefully();
  22. }
  23. @Slf4j
  24. static class EchoClientHandler extends ChannelInboundHandlerAdapter {
  25. private int counter;
  26. private static final String ECHO_REQ = "hello server.服务器好啊.^^";
  27. /**
  28. * 客户端和服务器端TCP链路建立成功后,此方法被调用
  29. */
  30. @Override
  31. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  32. for (int i = 0; i < 10; i++) {
  33. ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes(StandardCharsets.UTF_8)));
  34. }
  35. }
  36. /**
  37. * 接收到服务器端数据时调用
  38. */
  39. @Override
  40. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  41. log.info("this is :{} revice server msg:{}", ++counter, msg);
  42. }
  43. /**
  44. * 发生异常时调用
  45. */
  46. @Override
  47. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  48. log.error("client error:", cause);
  49. ctx.close();
  50. }
  51. }
  52. }

四、测试
aHR0cDovL2RsMi5pdGV5ZS5jb20vdXBsb2FkL2F0dGFjaG1lbnQvMDEyNy83NTcxLzQyODY0ZjBiLWIyMjctMzg0Ny04MzNkLTUwYWNmOTMzOWVhZS5wbmc
服务器端和客户端都获取到了正确的数据,到此一个简单的echo工程就已经写完了。

发表评论

表情:
评论列表 (有 0 条评论,108人围观)

还没有评论,来说两句吧...

相关阅读