• Netty,Tcp,socket的java框架,netty学习


    最新更新,报文发送,机器终端(车)与服务端

    先学习一下基本内容,以下是基于基本内容

    互相转换:byte(字节,字节是数字单位,他的数组是十进制内容),bit(二进制内容,不用操心这部分),十六进制0x(0-9-a-f),String(字符的数组,引用类型)

    框架与语言:socket(tcp),java,netty

    java代码

    终端发给服务端,16进制字符串 转换 10进制的字节数组(数字数组)。通过outStream流发送。

    服务端下发给终端,与上句原理一样。

    1. String sourceStrs="AB AA";//AA是170,AB是171//有多个用空格隔开
    2. //因为bytes[]不能追加,所以用字节流写入,写入后在转换
    3. ByteArrayOutputStream output = new ByteArrayOutputStream();
    4. String[] sourceStrArr = sourceStrs.split(" ");
    5. for (int i = 0; i < sourceStrArr.length; i++) {
    6. String sourceStr = sourceStrArr[i];
    7. String byteNum = Tools.trans16t10(sourceStr);//将16进制字符串转换 得到字节
    8. output.write(Integer.parseInt(byteNum));//将单个字节追加
    9. }
    10. byte[] sendBytes = output.toByteArray();//将字节流转换为字节数组
    11. //写一个字节数组过去
    12. outputStream.write(sendBytes);

    服务端解析终端,二进制转换为byte[]数组,数组转为16进制。就能解析出16进制内容

    我用的netty,他的事件部分,decode执行的部分

    //创建一个字节数组
    
    1. byte[] bufs2=new byte[in.readableBytes()];
    2. //将接收的字节存放到字节数组中
    3. in.readBytes(bufs2);//in是ByteBuf in重写的内容,终端传来的数据
    4. //将字节数组,他转成16进制的内容,这样就能和协议匹配了
    5. String jiqiStr = TuLiTcpTools.bytes2hex(bufs2);

    方法补充:

    trans16t10

     public static String trans16t10(String str){
    
            String myStr[] = { "a", "b", "c", "d", "e", "f" };
            int result = 0;
            int n = 1;
            for (int i = str.length() - 1; i >= 0; i--) {
                String param = str.substring(i, i + 1);
                for (int j = 0; j < myStr.length; j++) {
                    if (param.equalsIgnoreCase(myStr[j])) {
                        param = "1" + String.valueOf(j);
                    }
                }
                result += Integer.parseInt(param) * n;
                n *= 16;
            }
    //        System.out.println(result);
    //        System.out.println(Integer.parseInt(str, 16));
            return String.valueOf(result);
        }
    bytes2hex,10进制转16进制
    public static String bytes2hex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        String tmp;
        sb.append("[");
        for (byte b : bytes) {
            // 将每个字节与0xFF进行与运算,然后转化为10进制,然后借助于Integer再转化为16进制
            tmp = Integer.toHexString(0xFF & b);
            if (tmp.length() == 1) {
                tmp = "0" + tmp;//只有一位的前面补个0
            }
            sb.append(tmp).append(" ");//每个字节用空格断开
        }
        sb.delete(sb.length() - 1, sb.length());//删除最后一个字节后面对于的空格
        sb.append("]");
        return sb.toString();
    }

    netty程序

    一个netty程序,分3个内容,前2个必须要,第3个依据业务可选

    1.netty服务端的server,包含netty的配置和启动

    2.handler处理端,对客户端(终端)发来的数据进行处理

    3.编解码器,它其实是两部分,编码和解码,一般把他定义在handler之前

    一个完整的netty服务端(JAVA版)

    导入netty依赖

    1. <dependency>
    2. <groupId>io.nettygroupId>
    3. <artifactId>netty-allartifactId>
    4. <version>4.1.36.Finalversion>
    5. dependency>

    编写server

    1. package nettyServer;
    2. import io.netty.bootstrap.ServerBootstrap;
    3. import io.netty.channel.*;
    4. import io.netty.channel.nio.NioEventLoopGroup;
    5. import io.netty.channel.socket.SocketChannel;
    6. import io.netty.channel.socket.nio.NioServerSocketChannel;
    7. import io.netty.channel.socket.nio.NioSocketChannel;
    8. public class nServer {
    9. public static void main(String[] args) throws InterruptedException {
    10. //创建bossGroup,接受连接请求,用evnetLoopGroup接受
    11. //创建workGroup,执行工作,业务处理
    12. EventLoopGroup bossGroup = new NioEventLoopGroup();
    13. EventLoopGroup workGroup = new NioEventLoopGroup();
    14. //上面2个是线程组,loop循环,这2个都是无限循环
    15. //创建配置参数
    16. ServerBootstrap bootstrap = new ServerBootstrap();
    17. //使用链式编程,设置
    18. bootstrap.group(bossGroup,workGroup)//设置2个线程组
    19. .channel(NioServerSocketChannel.class)//指定服务通道为nio模型
    20. .option(ChannelOption.SO_BACKLOG,128)//设置线程得到的连接个数
    21. .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持长连接状态
    22. .childHandler(new ChannelInitializer() {
    23. @Override//给pipline设置处理器
    24. protected void initChannel(SocketChannel socketChannel) throws Exception {
    25. ChannelPipeline pipeline = socketChannel.pipeline();
    26. pipeline.addLast("decoder",new nDecode());
    27. pipeline.addLast(new nHandler());//增加处理器,handler
    28. }
    29. });//设置work的EventLoop对应管道设置处理器
    30. System.out.println("服务器准备完成");
    31. //绑定端口,并且同步处理,future对象
    32. ChannelFuture channelFuture = bootstrap.bind(12306).sync();
    33. //当监听到后,处理完,在关闭,没有监听到则不会调用
    34. channelFuture.channel().closeFuture().sync();
    35. }
    36. }

    编写handler

    注意:尽量用try catch,如果像我这样Exception,在springBoot中,异常状况下,客户端可能收不到数据,并且不会出现打印异常,让你错以为是卡死的情况。

    1. package nettyServer;
    2. import io.netty.buffer.ByteBuf;
    3. import io.netty.buffer.Unpooled;
    4. import io.netty.channel.ChannelHandlerContext;
    5. import io.netty.channel.ChannelInboundHandlerAdapter;
    6. import io.netty.util.CharsetUtil;
    7. //自定义一个handler,需要继承netty定义好的handler适配器,否则无效
    8. public class nHandler extends ChannelInboundHandlerAdapter{
    9. @Override
    10. //ChannelHandlerContext管道,通道,地址,他都能拿到
    11. //msg是客户端发送来的数据
    12. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    13. System.out.println("server ctx="+ctx);
    14. //将msg 转成一个ByteBuf
    15. //ByteBuf 是 Netty 提供的,注意不要使用nio的byteBuffer
    16. System.out.println(msg);
    17. ByteBuf buf = (ByteBuf) msg;
    18. System.out.println("客户端发送的消息是"+buf.toString(CharsetUtil.UTF_8));
    19. }
    20. @Override
    21. //读完了客户端的消息后,执行的内容
    22. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    23. //要写完,之后在flush,flush是发送(刷新,到通道)
    24. //对发送的数据进行编码
    25. ctx.channel().writeAndFlush(Unpooled.copiedBuffer("我收到了\r\n",CharsetUtil.UTF_8));
    26. }
    27. //处理异常,一般是关闭通道
    28. @Override
    29. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    30. ctx.close();
    31. }
    32. }

    编写解码器业务不需要可以删除

    注:可以复制重写的方法,但是不能直接复制,我的代码内容,我引入了自己业务中的工具类。

    1. package com.dt.tuli.springBoot_netty;
    2. import com.dt.tuli.tools.TuLiTcpTools;
    3. import io.netty.buffer.ByteBuf;
    4. import io.netty.channel.ChannelHandlerContext;
    5. import io.netty.handler.codec.ByteToMessageDecoder;
    6. import io.netty.util.CharsetUtil;
    7. import nettyServer.nServerAccept;
    8. import java.util.List;
    9. public class nDecodeSpringBoot extends ByteToMessageDecoder {
    10. //ctx上下文,比如地址ip,端口等,从这拿
    11. //in,客户端发来的数据
    12. //rs经过解码器,最后保留的数据,输出的数据
    13. @Override
    14. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List rs) {
    15. //创建一个字节数组
    16. byte[] bufs2=new byte[in.readableBytes()];
    17. //将接收的字节存放到字节数组中
    18. in.readBytes(bufs2);
    19. System.out.println("刚接受来的数据---------");//10进制
    20. for (int i = 0; i < bufs2.length; i++) {
    21. System.out.print(bufs2[i]);
    22. }
    23. System.out.println();
    24. System.out.println("----------------");
    25. //读取成字符串,打印下
    26. // byte bufs[]=in.toString(CharsetUtil.UTF_8).getBytes();
    27. // System.out.println("终端gps发过来的内容---------");
    28. // System.out.println(in.toString(CharsetUtil.UTF_8));
    29. //将他转成16进制的内容
    30. String jiqiStr = TuLiTcpTools.bytes2hex(bufs2);
    31. System.out.println("转成16进制的数据"+jiqiStr);
    32. try {
    33. //解析后存入rs中
    34. int size = TuLiTcpTools.getSize(jiqiStr);
    35. System.out.println("数据的长度是"+size);
    36. nServerAccept nServerAccept = TuLiTcpTools.parseData(jiqiStr, size);//封装成功
    37. rs.add(nServerAccept);
    38. }catch (Exception e){
    39. e.printStackTrace();
    40. }
    41. System.out.println("------------------------解码结束");
    42. }
    43. /* public static void main(String[] args) {
    44. ByteBuf buf= ByteBufAllocator.DEFAULT.buffer();
    45. StringBuilder sb=new StringBuilder();
    46. for(int i=0;i<5;i++){
    47. sb.append(123);
    48. }
    49. buf.writeBytes(sb.toString().getBytes());
    50. System.out.println(buf.readableBytes());
    51. System.out.println(buf.toString(CharsetUtil.UTF_8));//给我的字节二进制
    52. System.out.println(buf.capacity());
    53. byte bufs[] = new byte[buf.readableBytes()];
    54. bufs=buf.toString(CharsetUtil.UTF_8).getBytes();
    55. System.out.println(bufs);
    56. String jiqiStr = TuLiTcpTools.bytes2hex(bufs);
    57. System.out.println(jiqiStr);
    58. }*/
    59. }
    60. 测试客户端编写

      :删除了其中的业务敏感数据,所以与打印结果有所不同

      1. import java.io.*;
      2. import java.net.Socket;
      3. public class SocketClient {
      4. public static void main(String[] args) throws InterruptedException {
      5. try {
      6. // 和服务器创建连接
      7. Socket socket = new Socket("localhost",12306);
      8. // 要发送给服务器的信息
      9. OutputStream os = socket.getOutputStream();
      10. PrintWriter pw = new PrintWriter(os);
      11. // byte[] bytes = new byte[97 98 99 10 01 01 10 21 03 49];
      12. // pw.write(0xAA);
      13. //
      14. //
      15. //
      16. // pw.flush();
      17. os.write("xxxxx".getBytes());
      18. // 找到原因了,我代码里面写了个循环
      19. // 从服务器接收的信息
      20. InputStream is = socket.getInputStream();
      21. BufferedReader br = new BufferedReader(new InputStreamReader(is));
      22. String info = null;
      23. while((info = br.readLine())!=null){
      24. System.out.println("我是客户端,服务器返回信息:"+info);
      25. }
      26. socket.shutdownOutput();
      27. br.close();
      28. is.close();
      29. os.close();
      30. pw.close();
      31. socket.close();
      32. } catch (Exception e) {
      33. e.printStackTrace();
      34. }
      35. }
      36. }

      结果

      客户端

      服务端


      netty普通server转springBoot+netty

      在springBoot中集成netty的目的是,可以通过springBoot的接口,向netty下发指令

      上一个案例,是利用netty本身的server,而集成到springBoot中,需要新开一个线程运行netty服务,并且在启动springBoot的同时,启动netty。

      新开一个线程,需要修改netty的server服务

      启动springBoot的同时,启动netty,需要新写一个springBoot的config类

      修改netty的server  (nServerSpringBoot)

      package com.dt.tuli.springBoot_netty;
      
      import io.netty.bootstrap.ServerBootstrap;
      import io.netty.channel.*;
      import io.netty.channel.nio.NioEventLoopGroup;
      import io.netty.channel.socket.SocketChannel;
      import io.netty.channel.socket.nio.NioServerSocketChannel;
      import nettyServer.nDecode;
      
      
      public class nServer4SpringBoot {
          public static void bind(int port) throws InterruptedException {
      
              Thread thread = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      //创建bossGroup,接受连接请求,用evnetLoopGroup接受
                      //创建workGroup,执行工作,业务处理
                      EventLoopGroup bossGroup = new NioEventLoopGroup();
                      EventLoopGroup workGroup = new NioEventLoopGroup();
                      //上面2个是线程组,loop循环,这2个都是无限循环
                      try {
                          //创建配置参数
                          ServerBootstrap bootstrap = new ServerBootstrap();
      
                          //使用链式编程,设置
                          bootstrap.group(bossGroup, workGroup)//设置2个线程组
                                  .channel(NioServerSocketChannel.class)//指定服务通道为nio模型
                                  .option(ChannelOption.SO_BACKLOG, 128)//设置线程得到的连接个数
                                  .childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持长连接状态
                                  .childHandler(new ChannelInitializer() {
                                      @Override//给pipline设置处理器
                                      protected void initChannel(SocketChannel socketChannel) throws Exception {
                                          ChannelPipeline pipeline = socketChannel.pipeline();
                                          pipeline.addLast(new nDecode());
                                          pipeline.addLast(new nHandler4SpringBoot());//增加处理器,handler
                                      }
                                  });//设置work的EventLoop对应管道设置处理器
                          System.out.println("服务器准备完成");
      
                          //绑定端口,并且同步处理,future对象
                          ChannelFuture channelFuture = bootstrap.bind(port).sync();
                          //当监听到后,处理完,在关闭,没有监听到则不会调用
                          channelFuture.channel().closeFuture().sync();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } finally {
                          bossGroup.shutdownGracefully();
                      }
      
                  }
              });
      
              thread.start();
          }
      }

      新建springBoot配置类

      1. package com.dt.tuli.springBoot_netty;
      2. import org.springframework.boot.ApplicationArguments;
      3. import org.springframework.boot.ApplicationRunner;
      4. import org.springframework.stereotype.Component;
      5. @Component
      6. public class nConfig4SpringBoot implements ApplicationRunner {
      7. @Override
      8. public void run(ApplicationArguments args) throws Exception {
      9. nServer4SpringBoot.bind(12306);
      10. }
      11. }

      此时启动springBoot,则会自动启动netty


      springBoot,服务端如何主动下发指令,给机器

      1.对每台机器,建立映射,存储套接字

      2.服务端通过套接字发送给机器

      我们业务,车端会通过tcp协议,不断的往服务端的某个端口发送数据-登录数据,此时服务端可以根据厂家协议,解析登录数据,获取到登录数据的信息,比如,车端发送的手机卡号,设备编号。

      此时就可以存储设备编号,和socket连接,在netty中存储的是channel通道。

      下次要主动下发的时候,从map中取出对应的编号的socket连接,进行发送。

      建立存储套接字map

      1. package com.dt.tuli.springBoot_netty;
      2. import io.netty.channel.Channel;
      3. import io.netty.channel.ChannelHandlerContext;
      4. import java.util.Map;
      5. import java.util.concurrent.ConcurrentHashMap;
      6. //用来存储客户端和服务端建立的管道
      7. public enum TCPCache {
      8. INSTANCE;
      9. private Map clientInfoContext = new ConcurrentHashMap<>();
      10. private Map serverInfoContext = new ConcurrentHashMap<>();
      11. public Map getClientInfoContext() {
      12. return clientInfoContext;
      13. }
      14. public Map getServerInfoContext() {
      15. return serverInfoContext;
      16. }
      17. }

      将socket套接字存入,套接字Map中(在hadnler的某个事件中,为了测试我放在了

      channelReadComplete方法部分。实际业务是放在了channelRead部分,解码成功后校验完毕,进行存储。

      其中test是唯一编号

      TCPCache.INSTANCE.getClientInfoContext().computeIfAbsent("test",no -> ctx);//no是别名

      创建springBoot类

      1. @PostMapping("/send")
      2. @ResponseBody
      3. //服务端主动推送测试
      4. public R TestSendMessage() {
      5. Map clientInfoContext = TCPCache.INSTANCE.getClientInfoContext();
      6. System.out.println("进来接口了");
      7. System.out.println(clientInfoContext.keySet());
      8. if(clientInfoContext.get("test")==null){
      9. return failed("终端,还没有和服务端建立tcp连接");
      10. }
      11. // Unpooled.copiedBuffer()
      12. try {
      13. ChannelHandlerContext ctx = clientInfoContext.get("test");
      14. ctx.channel().writeAndFlush(Unpooled.copiedBuffer("123\n".getBytes())).sync();
      15. // ctx.writeAndFlush("123\n");
      16. } catch (Exception e) {
      17. e.printStackTrace();
      18. }
      19. return ok("推送完成");
      20. }

      测试中: 我的客户端测试是用\n区分是不是下一段的。所以通过请求写数据要\n。(实际开发,我是以包长度进行获取的)另外启动服务端后,要启动客户端才可以主动发送数据,因为客户端在启动时候,我的代码中向服务端发送了消息,建立了连接。收到数据后,服务端会存储这个连接,之后springBoot才能主动推送数据。

      结果:

       

      客户端:

       

       


      杂谈,如果消息不想要的话,或者不符合你的规则就丢掉,比如,我发送的开头必须有aa aa,结果他发了个a1 aa,这就不符合规则,你想要丢掉,就在decode解码的时候,进行return;这样消息就不会发送给处理者,而是直接到结束者阶段。

      修改decode代码

      jiqiStr是我发过来的数据,而我截取了他的开头,看看符不符合规则

      String substring = jiqiStr.substring(0, 8);//随便取的前面的数,看看开头有没有aa,没有则说明不是这个协议的报头,直接丢弃
      if (!substring.contains("aa aa")){
          //如果没有,则直接丢弃,不会走对数据的处理方法,而直接走处理完数据后的方法
          return;
      }//下面的语句都不会被执行

      ........

      rs.add(nServerAccept);//List  rs ,这句话不会被执行,这句话不被执行的话,处理端就不会被调用 
       
      

      测试,含aa aa的正确规则,返回结果

      含a1 aa的错误规则,返回结果

      服务端的handler代码


      由于业务需要,一个服务,要同时与多辆车连接,并且能够通过接口向车下发指令。车上报数据,给服务端,服务端入时序数据库。

      由于一个车辆需要和服务连接,多个车辆将消耗多个线程,而线程又是由cpu产生,线程之间的传递是通过网络,这里的硬件要求就是,高cpu核数线程数,以及宽带网速高和稳定

      netty比较复杂,他是封装了nio,然后在封装了netty,然后在改进了roactor。所以框架比较复杂。学起来需要时间。建议不要速成,速成后,你没法改成你自己想要的业务。

      044_尚硅谷_Netty入门-服务端1_哔哩哔哩_bilibili

      解码器,解协议:

      Netty自定义编-解码器解决TCP通讯粘包拆包的问题 - william_zhao - 博客园

      netty是什么?


      netty是一个nio框架,解决了socket的单线程效率低,采用了nio的优势,多线程,但是又屏蔽了nio的复杂性。

      但是nio肯定是略低于bio的速度的。bio会一直阻塞,来了就收发,nio会缓冲,如果没有任务,可以先去做其他的事,然后再切回来。

      ps:科普,bio是传统框架,同步进行会阻塞,效率不高,nio不会阻塞。

      NIO全称 java non-blocking IO。

      NIO三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)

      深入了解:BIO和NIO的区别_你喜欢炸酱面么的博客-CSDN博客_bio nio

      netty的架构原理


      采用nio的模式,原来bio一个socket对应一个线程,变成了多个socket对应一个线程

      select

       原理是,用户提交读写后,会交给1个线程进行注册,所有的用户都交给这1个线程注册,注册后,这个线程就会对注册的事件,进行监控。一旦有消息发送来了,他就监控到了,然后发到缓冲区,这时候,会回调出一个新线程来处理,处理完,新线程又回去做其他的事情。

      buffer缓冲区

      数据只能从channel中读到buffer中,或者把数据从buffer写入channel中

      线程模型

      选择让哪个线程进行解码,这将很影响性能

      线程模型1: 事件驱动,轮询查询

      轮询就是不断的判断是否存在,存在则处理

      事件驱动模型,就是把任务发到队列,另1个线程拉取队列内容,分发到不同的子线程中去执行任务,如下:

       reactor模型

      netty模型

      模块组件

      这个比较重要,设计到代码

      模块组件

      【Bootstrap、ServerBootstrap】

      Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。

      【Future、ChannelFuture】

      正如前面介绍,在 Netty 中所有的 IO 操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 Future 和 ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。

      【Channel】

      Netty 网络通信的组件,能够用于执行网络 I/O 操作。Channel 为用户提供:

      1)当前网络连接的通道的状态(例如是否打开?是否已连接?)

      2)网络连接的配置参数 (例如接收缓冲区大小)

      3)提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。

      4)调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。

      5)支持关联 I/O 操作与对应的处理程序。不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应。

      下面是一些常用的 Channel 类型:

      1. NioSocketChannel,异步的客户端 TCP Socket 连接。
      2. NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
      3. NioDatagramChannel,异步的 UDP 连接。
      4. NioSctpChannel,异步的客户端 Sctp 连接。
      5. NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。

      【Selector】

      Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。

      【NioEventLoop】

      NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。两种任务的执行时间比由变量 ioRatio 控制,默认为 50,则表示允许非 IO 任务执行的时间与 IO 任务的执行时间相等。

      【NioEventLoopGroup】

      NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。

      【ChannelHandler】

      ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。ChannelHandler 本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类:
      ChannelInboundHandler 用于处理入站 I/O 事件。
      ChannelOutboundHandler 用于处理出站 I/O 操作。
      或者使用以下适配器类:
      ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。
      ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。
      ChannelDuplexHandler 用于处理入站和出站事件。

      【ChannelHandlerContext】

      保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。

      【ChannelPipline】

      保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。

      下图引用 Netty 的 Javadoc 4.1 中 ChannelPipeline 的说明,描述了 ChannelPipeline 中 ChannelHandler 通常如何处理 I/O 事件。I/O 事件由 ChannelInboundHandler 或 ChannelOutboundHandler 处理,并通过调用 ChannelHandlerContext 中定义的事件传播方法。

      例如:ChannelHandlerContext.fireChannelRead(Object)和 ChannelOutboundInvoker.write(Object)转发到其最近的处理程序。

      入站事件由自下而上方向的入站处理程序处理,如图左侧所示。入站 Handler 处理程序通常处理由图底部的 I/O 线程生成的入站数据。通常通过实际输入操作(例如 SocketChannel.read(ByteBuffer))从远程读取入站数据。出站事件由上下方向处理,如图右侧所示。出站 Handler 处理程序通常会生成或转换出站传输,例如 write 请求。I/O 线程通常执行实际的输出操作,例如 SocketChannel.write(ByteBuffer)。在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应,它们的组成关系如下:

      一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。

      入站事件和出站事件在一个双向链表中,入站事件会从链表 head 往后传递到最后一个入站的 handler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

      没有时间了,没想到netty学习还是比较复杂的,由于工作期限的要求,我现在开始使用步骤,大家感兴趣,可以看下方的阅读

      原理参考:

      Netty:原理架构解析_区块链之美的博客-CSDN博客_netty原理详解

      Netty核心原理_myself study log的博客-CSDN博客_netty原理

      netty的使用


          我采用的是java编写

          依赖导入

      1. <dependency>
      2. <groupId>io.nettygroupId>
      3. <artifactId>netty-allartifactId>
      4. <version>4.1.36.Finalversion>
      5. dependency>

          netty服务端创建

          服务端要创建的有

          监听类,负责读取消息

         连接类,负责处理消息

         启动类,负责启动

         公共代码,负责保存,客户端于服务端的连接关系

        监听类:

              

       链接类:

        启动类:

         公共代码类:

      参考:

      一个简单的Netty demo_颜翎的博客-CSDN博客_netty的demo

      下面这个文章,可以完成,服务端,向客户端发送命令。

      Netty--TCP--实例_IT利刃出鞘的博客-CSDN博客_netty tcp实例

      springBoot和netty案例:

      Spring Boot + Netty + WebSocket 实现消息推送 (qq.com)

      踩坑:

      netty客户端能收到netty服务端的,socket客户端能收到socket服务端的,netty服务端能收到socket客户端的,socket客户端【收不到】netty的,一直卡着

      原因是因为,netty不知道你消息发送完了,所以会一直卡着,这句话的意思是告诉他,我发送完了。

      socket(客户端)与netty(服务端)交互。在客户端发送完消息后加上

      socket.shutdownOutput();

      但是这不是最终解法,最终的问题是netty身上,如果是这样,那每次客户端都需要重新和netty建立连接。开销大,而且会丢包。

      最终的解法是设定netty,让netty回复客户端。底层tcp不知道,你到底有没有发完,你关闭后,可能他以为是发完了。这种情况需要使用消息解析器去判断

      消息解析器,每次发完一段话,给他一个结束标志,他就知道,是不是这段消息发送完了。

      最佳方案,居然是 在服务端的回复后面加"\n" 

    61. 相关阅读:
      Springboot学生选课系统的设计与实现毕业设计源码291510
      简易轮播图和打地鼠
      MYSQL高可用架构之MHA实战三 mha+keepalive
      组长给组员派活,把组长自己的需求和要改的bug派给组员,合理吗?
      haproxy工具,负载均衡配置,反向代理配置,动静分离,高可用等等
      力扣2296.设计一个文本编辑器
      专业扫盲, 熬夜整理56个JavaScript高级的手写知识点【史上最全】
      002数据安全传输-多端协议传输平台:配置Oracle数据库-19c及导入数据信息
      Maven依赖的作用域你到底用对了没有
      网络 - OkHttp
    62. 原文地址:https://blog.csdn.net/qq_38403590/article/details/126697995