当前位置:   article > 正文

Spring Boot使用Netty实现客户端与服务器通信_springboot netty回复客户端

springboot netty回复客户端

一、服务端

1、添加Maven依赖

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.1.3.RELEASE</version>
  5. </parent>
  6. <dependencies>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-web</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>io.netty</groupId>
  13. <artifactId>netty-all</artifactId>
  14. <version>5.0.0.Alpha2</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>com.alibaba</groupId>
  18. <artifactId>fastjson</artifactId>
  19. <version>1.2.59</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.projectlombok</groupId>
  23. <artifactId>lombok</artifactId>
  24. <optional>true</optional>
  25. </dependency>
  26. </dependencies>

2、application.yml

  1. server:
  2. port: 8001

3、启动类

  1. @SpringBootApplication
  2. public class NettyServerApplication implements CommandLineRunner {
  3. @Autowired
  4. private NettyServerBootStrap serverBootStrap;
  5. public static void main(String[] args) {
  6. SpringApplication.run(NettyServerApplication.class, args);
  7. }
  8. @Override
  9. public void run(String... args) throws Exception {
  10. serverBootStrap.start();
  11. }
  12. }

4、NettyServerBootStrap

  1. @Component
  2. @Slf4j
  3. public class NettyServerBootStrap {
  4. @Autowired
  5. private NettyServerHandler nettyServerHandler;
  6. public void start() throws InterruptedException {
  7. EventLoopGroup boss = new NioEventLoopGroup();
  8. EventLoopGroup worker = new NioEventLoopGroup();
  9. ServerBootstrap bootstrap = new ServerBootstrap();
  10. try {
  11. bootstrap.group(boss, worker)
  12. .channel(NioServerSocketChannel.class)
  13. .option(ChannelOption.SO_BACKLOG, 128)
  14. // 使消息立即发出去,不用等待到一定的数据量才发出去
  15. .option(ChannelOption.TCP_NODELAY, true)
  16. // 保持长连接状态
  17. .childOption(ChannelOption.SO_KEEPALIVE, true)
  18. .childHandler(new ChannelInitializer<SocketChannel>() {
  19. @Override
  20. protected void initChannel(SocketChannel socketChannel) throws Exception {
  21. ChannelPipeline p = socketChannel.pipeline();
  22. p.addLast(new StringDecoder(CharsetUtil.UTF_8));
  23. p.addLast(new StringEncoder(CharsetUtil.UTF_8));
  24. p.addLast(nettyServerHandler);
  25. }
  26. });
  27. // 绑定端口,同步等待成功
  28. ChannelFuture f = bootstrap.bind(5678).sync();
  29. if (f.isSuccess()) {
  30. log.info("Netty Start successful");
  31. } else {
  32. log.error("Netty Start failed");
  33. }
  34. // 等待服务监听端口关闭
  35. f.channel().closeFuture().sync();
  36. } finally {
  37. // 退出,释放线程资源
  38. worker.shutdownGracefully();
  39. boss.shutdownGracefully();
  40. }
  41. }
  42. }

5、NettyServerHandler

  1. @Component
  2. @ChannelHandler.Sharable
  3. @Slf4j
  4. public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
  5. /**
  6. * @Description 客户端断开连接时执行,将客户端信息从Map中移除
  7. * @param ctx
  8. * @Date 2019/8/28 14:22
  9. * @Author wuyong
  10. * @return
  11. **/
  12. @Override
  13. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  14. log.info("客户端断开连接:{}", getClientIp(ctx.channel()));
  15. NettyChannelMap.remove((SocketChannel) ctx.channel());
  16. }
  17. /**
  18. * @Description 客户端连接时执行,将客户端信息保存到Map中
  19. * @param ctx
  20. * @Date 2019/8/28 14:22
  21. * @Author wuyong
  22. * @return
  23. **/
  24. @Override
  25. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  26. log.info("有新的客户端连接:{}", getClientIp(ctx.channel()));
  27. String clientIp = getClientIp(ctx.channel());
  28. NettyClient client = new NettyClient((SocketChannel) ctx.channel(), getClientIp(ctx.channel()));
  29. NettyChannelMap.add(clientIp, client);
  30. }
  31. /**
  32. * @Description 收到消息时执行,根据消息类型做不同的处理
  33. * @param ctx
  34. * @param msg
  35. * @Date 2019/8/28 14:33
  36. * @Author wuyong
  37. * @return
  38. **/
  39. @Override
  40. public void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
  41. log.info("收到客户端消息:" + msg);
  42. // 这个消息一般是结构化的数据,比如JSON字符串,解析这个JSON字符串,做相应的逻辑处理
  43. JSONObject msgObj = JSON.parseObject(msg);
  44. String msgType = msgObj.getString("msgType");
  45. switch (msgType) {
  46. // 回复客户端请求
  47. case "req":
  48. doReply(ctx);
  49. break;
  50. default:
  51. break;
  52. }
  53. }
  54. /**
  55. * @description: TODO
  56. * @param ctx
  57. * @param cause
  58. * @Author: wuyong
  59. * @Date: 2019/08/30 13:41:51
  60. * @return: void
  61. */
  62. @Override
  63. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  64. log.info("抛出异常执行,包括客户端断开连接时,会抛出IO异常");
  65. }
  66. /**
  67. * @description: 当收到客户端的消息后,进行处理
  68. * @param ctx
  69. * @Author: wuyong
  70. * @Date: 2019/08/30 14:10:59
  71. * @return: void
  72. */
  73. private void doReply(ChannelHandlerContext ctx) {
  74. String reply = "{\"msgType\":\"reply\",\"data\":\"回复的数据\"}";
  75. ctx.channel().writeAndFlush(reply);
  76. }
  77. /**
  78. * @Description 获取客户端IP
  79. * @param channel
  80. * @Date 2019/8/28 14:32
  81. * @Author wuyong
  82. * @return
  83. **/
  84. private String getClientIp(Channel channel) {
  85. InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
  86. String clientIP = inetSocketAddress.getAddress().getHostAddress();
  87. return clientIP;
  88. }
  89. /**
  90. * @Description 当有新的客户端连接的时候,用于保存客户端信息
  91. * @Date 2019/8/28 14:20
  92. * @Author wuyong
  93. * @return
  94. **/
  95. public static class NettyChannelMap {
  96. public static Map<String, NettyClient> map = new ConcurrentHashMap<>();
  97. public static void add(String clientId, NettyClient client) {
  98. map.put(clientId, client);
  99. }
  100. public static NettyClient get(String clientId) {
  101. return map.get(clientId);
  102. }
  103. public static void remove(SocketChannel socketChannel) {
  104. for (Map.Entry entry : map.entrySet()) {
  105. if (((NettyClient) entry.getValue()).getChannel() == socketChannel) {
  106. map.remove(entry.getKey());
  107. }
  108. }
  109. }
  110. }
  111. /**
  112. * @Description 封装客户端的信息
  113. * @Date 2019/8/28 14:21
  114. * @Author wuyong
  115. * @return
  116. **/
  117. @Data
  118. public static class NettyClient {
  119. /**客户端与服务器的连接*/
  120. private SocketChannel channel;
  121. /**ip地址*/
  122. private String clientIp;
  123. // ......
  124. public NettyClient(SocketChannel channel, String clientIp) {
  125. this.channel = channel;
  126. this.clientIp = clientIp;
  127. }
  128. }
  129. }

至此,一个简单的Netty服务端就完成了。接下来写一个Controller,用于获取当前在线的客户端列表:

6、NettyServerController

  1. @RestController
  2. @RequestMapping("/server")
  3. public class NettyServerController {
  4. @GetMapping("/clientList")
  5. public Map<String, NettyServerHandler.NettyClient> clientList() {
  6. return NettyServerHandler.NettyChannelMap.map;
  7. }
  8. }

项目结构如下:

二、客户端

客户端添加的依赖、配置文件以及启动类和服务端类似。

1、NettyClientBootStrap

  1. @Component
  2. @Slf4j
  3. public class NettyClientBootStrap {
  4. private static final String HOST = "localhost";
  5. private static final int PORT = 5678;
  6. private static SocketChannel socketChannel = null;
  7. public void start() throws InterruptedException {
  8. EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
  9. Bootstrap bootstrap = new Bootstrap();
  10. bootstrap.channel(NioSocketChannel.class);
  11. bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
  12. bootstrap.group(eventLoopGroup);
  13. bootstrap.remoteAddress(HOST, PORT);
  14. bootstrap.handler(new ChannelInitializer<SocketChannel>() {
  15. @Override
  16. protected void initChannel(SocketChannel socketChannel) throws Exception {
  17. socketChannel.pipeline().addLast(new IdleStateHandler(20, 10, 0));
  18. socketChannel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
  19. socketChannel.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
  20. socketChannel.pipeline().addLast(new NettyClientHandler());
  21. }
  22. });
  23. ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
  24. if (future.isSuccess()) {
  25. socketChannel = (SocketChannel) future.channel();
  26. log.info("connect server success");
  27. }
  28. }
  29. public static SocketChannel getSocketChannel() {
  30. return socketChannel;
  31. }
  32. }

2、NettyClientHandler

  1. @Component
  2. @Slf4j
  3. @ChannelHandler.Sharable
  4. public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
  5. @Override
  6. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  7. log.info("断开连接执行");
  8. }
  9. @Override
  10. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  11. log.info("连接成功执行");
  12. }
  13. @Override
  14. protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
  15. log.info("收到消息执行:" + msg);
  16. }
  17. @Override
  18. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  19. log.info("抛出异常执行");
  20. }
  21. }

3、编写一个Controller,用于向服务器发送消息:

  1. @RestController
  2. @RequestMapping("/client")
  3. public class NettyClientController {
  4. /**
  5. * @description: 模拟向服务器发送消息
  6. * @param
  7. * @Author: wuyong
  8. * @Date: 2019/08/30 14:10:09
  9. * @return: java.lang.String
  10. */
  11. @RequestMapping("/req")
  12. public String req() {
  13. String msg = "{\"msgType\":\"req\",\"clientId\":\"请求数据\"}";
  14. NettyClientBootStrap.getSocketChannel().writeAndFlush(msg);
  15. return "success";
  16. }
  17. }

客户端的结构如下:

三、测试

首先启动服务端:

然后启动客户端,启动成功后可以看到如下输出:

然后查看服务端的控制台:

在浏览器中访问http://localhost:8001/server/clientList,可以看到确实注册成功了:

接下来使用客户端向服务器发消息,在浏览器中执行http://localhost:8002/client/req,在服务端的控制台输出如下信息:

客户端的控制台输出了如下信息:

接下来断开客户端的连接,在服务端的控制台输出了如下信息:

查看客户端列表,也为空了:

四、使用Socket连接服务器

也可以直接使用Socket连接Netty服务器,编写一个简单的Demo如下:

  1. public class Client {
  2. public static void main(String[] args) throws IOException, InterruptedException {
  3. Socket socket = new Socket("localhost", 5678);
  4. // 向服务器发消息
  5. OutputStream outputStream = socket.getOutputStream();
  6. String msg = "{\"msgType\":\"req\",\"clientId\":\"请求数据\"}";
  7. outputStream.write(msg.getBytes(CharsetUtil.UTF_8));
  8. outputStream.flush();
  9. Thread.sleep(1000);
  10. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  11. char[] ch = new char[65536];
  12. int len = -1;
  13. while ((len = br.read(ch)) != -1) {
  14. String result = new String(ch, 0, len);
  15. System.out.println("服务器返回数据:" + result);
  16. }
  17. }
  18. }

运行main方法,可以看到控制台输出:

服务器返回数据:{"msgType":"reply","data":"回复的数据"}

五、粘包/拆包

正常情况下,一条消息是一个整体,一次接收到的消息只会是一条。但是实际情况下,收到的消息可能是多条消息粘在一起,或者一条消息被拆分成了多条。这就是所谓的粘包/拆包。产生粘包和拆包问题的主要原因是,操作系统在发送TCP数据的时候,底层会有一个缓冲区,例如1024个字节大小,如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题;如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。

对于粘包和拆包问题,常见的解决方案有四种(https://my.oschina.net/zhangxufeng/blog/3023794):

1、客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度(FixedLengthFrameDecoder解码器)。

2、客户端在每个包的末尾使用固定的分隔符,例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包(LineBasedFrameDecoder或DelimiterBasedFrameDecoder解码器)。

3、将消息分为消息头和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息(LengthFieldBasedFrameDecoder和LengthFieldPrepender)。

4、通过自定义协议进行粘包和拆包的处理(通过继承LengthFieldBasedFrameDecoderLengthFieldPrepender来实现粘包和拆包的处理)。

首先,模拟一下粘包和拆包的问题。

修改NettyClientController,连续向服务器发送20条消息:

  1. private void doReply(ChannelHandlerContext ctx) {
  2. String reply = "{\"msgType\":\"reply\",\"data\":\"回复的数据\"}";
  3. for (int i = 0; i < 20; i++) {
  4. ctx.channel().writeAndFlush(reply);
  5. }
  6. }

查看服务端的控制台,输出如下:

  1. 2019-08-30 15:35:52.330 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  2. 2019-08-30 15:35:52.331 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  3. 2019-08-30 15:35:52.331 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}
  4. 2019-08-30 15:35:52.331 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 抛出异常执行,包括客户端断开连接时,会抛出IO异常
  5. 2019-08-30 15:35:52.331 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}
  6. 2019-08-30 15:35:52.331 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 抛出异常执行,包括客户端断开连接时,会抛出IO异常
  7. 2019-08-30 15:35:52.331 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}
  8. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 抛出异常执行,包括客户端断开连接时,会抛出IO异常
  9. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}
  10. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 抛出异常执行,包括客户端断开连接时,会抛出IO异常
  11. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}
  12. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 抛出异常执行,包括客户端断开连接时,会抛出IO异常
  13. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}
  14. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 抛出异常执行,包括客户端断开连接时,会抛出IO异常
  15. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}{"msgType":"req","clientId":"请求数据"}
  16. 2019-08-30 15:35:52.332 INFO 5772 --- [ntLoopGroup-1-0] com.wuychn.server.NettyServerHandler : 抛出异常执行,包括客户端断开连接时,会抛出IO异常

可知第三条消息就发生了粘包的问题。我这里采用在末尾加特定的分隔符(\r\n)的方式来解决。

修改NettyServerBootstrap,添加一个LineBasedFrameDecoder解码器:

  1. @Component
  2. @Slf4j
  3. public class NettyServerBootStrap {
  4. @Autowired
  5. private NettyServerHandler nettyServerHandler;
  6. public void start() throws InterruptedException {
  7. EventLoopGroup boss = new NioEventLoopGroup();
  8. EventLoopGroup worker = new NioEventLoopGroup();
  9. ServerBootstrap bootstrap = new ServerBootstrap();
  10. try {
  11. bootstrap.group(boss, worker)
  12. .channel(NioServerSocketChannel.class)
  13. .option(ChannelOption.SO_BACKLOG, 128)
  14. // 使消息立即发出去,不用等待到一定的数据量才发出去
  15. .option(ChannelOption.TCP_NODELAY, true)
  16. // 保持长连接状态
  17. .childOption(ChannelOption.SO_KEEPALIVE, true)
  18. .childHandler(new ChannelInitializer<SocketChannel>() {
  19. @Override
  20. protected void initChannel(SocketChannel socketChannel) throws Exception {
  21. ChannelPipeline p = socketChannel.pipeline();
  22. p.addLast(new LineBasedFrameDecoder(Integer.MAX_VALUE));
  23. p.addLast(new StringDecoder(CharsetUtil.UTF_8));
  24. p.addLast(new StringEncoder(CharsetUtil.UTF_8));
  25. p.addLast(nettyServerHandler);
  26. }
  27. });
  28. // 绑定端口,同步等待成功
  29. ChannelFuture f = bootstrap.bind(5678).sync();
  30. if (f.isSuccess()) {
  31. log.info("Netty Start successful");
  32. } else {
  33. log.error("Netty Start failed");
  34. }
  35. // 等待服务监听端口关闭
  36. f.channel().closeFuture().sync();
  37. } finally {
  38. // 退出,释放线程资源
  39. worker.shutdownGracefully();
  40. boss.shutdownGracefully();
  41. }
  42. }
  43. }

修改NettyClientController,在消息末尾增加\r\n:

  1. @RestController
  2. @RequestMapping("/client")
  3. public class NettyClientController {
  4. /**
  5. * @description: 模拟向服务器发送消息
  6. * @param
  7. * @Author: wuyong
  8. * @Date: 2019/08/30 14:10:09
  9. * @return: java.lang.String
  10. */
  11. @RequestMapping("/req")
  12. public String req() {
  13. String msg = "{\"msgType\":\"req\",\"clientId\":\"请求数据\"}\r\n";
  14. for (int i = 0; i < 20; i++) {
  15. NettyClientBootStrap.getSocketChannel().writeAndFlush(msg);
  16. }
  17. return "success";
  18. }
  19. }

再次测试,服务器的控制台输出如下:

  1. 2019-08-30 15:47:05.959 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  2. 2019-08-30 15:47:06.050 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  3. 2019-08-30 15:47:06.051 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  4. 2019-08-30 15:47:06.051 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  5. 2019-08-30 15:47:06.051 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  6. 2019-08-30 15:47:06.051 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  7. 2019-08-30 15:47:06.051 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  8. 2019-08-30 15:47:06.051 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  9. 2019-08-30 15:47:06.052 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  10. 2019-08-30 15:47:06.052 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  11. 2019-08-30 15:47:06.052 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  12. 2019-08-30 15:47:06.052 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  13. 2019-08-30 15:47:06.052 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  14. 2019-08-30 15:47:06.052 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  15. 2019-08-30 15:47:06.053 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  16. 2019-08-30 15:47:06.053 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  17. 2019-08-30 15:47:06.053 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  18. 2019-08-30 15:47:06.053 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  19. 2019-08-30 15:47:06.053 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}
  20. 2019-08-30 15:47:06.053 INFO 216 --- [ntLoopGroup-1-1] com.wuychn.server.NettyServerHandler : 收到客户端消息:{"msgType":"req","clientId":"请求数据"}

可见一切正常。

同样的道理,服务器发送给客户端的消息也会出现粘包/拆包的问题,客户端也可以加上LineBasedFrameDecoder解码器,然后服务器发送的消息使用\r\n结尾即可解决。

如果客户端使用的Socket,可以做如下处理:

  1. public class Client {
  2. public static void main(String[] args) throws IOException, InterruptedException {
  3. Socket socket = new Socket("localhost", 5678);
  4. // 向服务器发消息
  5. OutputStream outputStream = socket.getOutputStream();
  6. String msg = "{\"msgType\":\"req\",\"clientId\":\"请求数据\"}\r\n";
  7. outputStream.write(msg.getBytes(CharsetUtil.UTF_8));
  8. outputStream.flush();
  9. // 模拟收到服务端粘包消息:服务器收到客户端消息后,循环回复20条消息
  10. // 为了让小郭更明显,这里让客户端暂停1秒
  11. Thread.sleep(1000);
  12. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  13. char[] ch = new char[65536];
  14. int len = -1;
  15. while ((len = br.read(ch)) != -1) {
  16. String result = new String(ch, 0, len);
  17. System.out.println("原始消息:" + result); // 获取到的原始消息,是有粘包或者拆包的
  18. String[] results = result.split("\r\n"); // 按照\r\n拆分,这里没有处理拆包的问题
  19. for (String str : results) {
  20. System.out.println("接收到消息:" + str);
  21. }
  22. }
  23. }
  24. }

 

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/973875
推荐阅读
相关标签
  

闽ICP备14008679号