1 netty聊天室过程遇到的bug与解决方法

chapter 1-3

1.同步和异步

  • 同步请求,A调用B,B的处理是同步的,在处理完之前他不会通知A,只有处理完之后才会明确的通知A。
  • 异步请求,A调用B,B的处理是异步的,B在接到请求后先告诉A我已经接到请求了,然后异步去处理,处理完之后通过回调等方式再通知A。
  • 所以说,同步和异步最大的区别就是被调用方的执行方式和返回时机。同步指的是被调用方做完事情之后再返回,异步指的是被调用方先返回,然后再做事情,做完之后再想办法通知调用方。

2.阻塞和非阻塞

  • 阻塞请求,A调用B,A一直等着B的返回,别的事情什么也不干。
  • 非阻塞请求,A调用B,A不用一直等着B的返回,先去忙别的事情了。
  • 所以说,阻塞和非阻塞最大的区别就是在被调用方返回结果之前的这段时间内,调用方是否一直等待。阻塞指的是调用方一直等待别的事情什么都不做。非阻塞指的是调用方先去忙别的事情。

3. 阻塞、非阻塞和同步、异步的区别

  • 阻塞、非阻塞说的是调用者,同步、异步说的是被调用者。

4. Java中的三种IO模型

  • 在Java语言中,一共提供了三种IO模型,分别是阻塞IO(BIO)、非阻塞IO(NIO)、异步IO(AIO)。
    这里面的BIO和NIO都是同步的IO模型,即同步阻塞IO和同步非阻塞IO,异步IO指的是异步非阻塞IO。
    • BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
    • NIO (New I/O):同时支持阻塞与非阻塞模式,但主要是使用同步非阻塞IO。
    • AIO (Asynchronous I/O):异步非阻塞I/O模型。

  • 问题
  1. BIO,NIO,AIO的区别是什么
  2. 什么是同步阻塞BIO:发起请求,一直阻塞,处理完成
  3. 什么是同步非阻塞NIO:selector主动轮训channel,处理请求,处理完成
  4. 什么是一步非阻塞AIO:发起请求,通知回调

Reactor线程模型

  1. 单线程模型:所有的IO操作都由一个NIO线程处理
  2. 多线程模型:有一组NIO线程处理IO操作
  3. 主从线程模型(推荐):一组线程池接受请求,一组线程池处理请求

关键代码

1.程序入口类HelloNetty

1.定义一对线程组

    //主线程组 -- 不做事情
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //从线程组 -- 做事情
        EventLoopGroup workerGroup = new NioEventLoopGroup();

2.定义ServerBootstrap启动类

ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)   //设置主从线程组
                    .channel(NioServerSocketChannel.class)  //设置nio的双向通道
                    .childHandler(new HelloServerInitializer()); //子处理器,用于处理workergroup
//启动server,并且设置8088为启动的端口号,同时启动方式为同步
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
//监听关闭的channel,设置位同步方式
channelFuture.channel().closeFuture().sync();

2.定义初始化器HelloServerInitializer

1.初始化HelloServerInitializer, 继承ChannelInitializer, 改写initChannel函数

@Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //通过socketChannel去获得对应的管道
        ChannelPipeline pipeline = socketChannel.pipeline();


        //通过管道添加handler
        //HttpServerCodec是由netty自己提供的助手类,也可以理解为拦截器
        //当请求到服务端,我们需要做解码,相应到客户端做编码
        pipeline.addLast("HttpServerCodedec", new HttpServerCodec());


        //添加一个自定义的Handler
        pipeline.addLast("customHandler", new CustomHandler());
    }

3.编写子处理器CustomHandler

1.子处理器, 继承SimpleChannelInboundHandler,改写channelRead0方法

 @Override
    protected void channelRead0(ChannelHandlerContext ctx,
                                HttpObject msg) throws Exception {
        //获取channel
        Channel channel = ctx.channel();
        if(msg instanceof HttpRequest){
            //显示客户端的远程地址
            System.out.println(channel.remoteAddress());
            //定义发送的数据消息
            ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8);
            //构建一个http response
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    content);
            //为相应添加数据类型和长度
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
            // 把响应刷到客户端
            ctx.writeAndFlush(response);
        }
    }

Chapter4

1.程序入口类WSServer

  • 与上节一样,设置主从线程类组
  • 配置serverBootstrap
  • serverBootstrap绑定端口,得到ChannelFuture对象

2.初始化器WSServerInitialzer

  • 继承ChannelInitializer,改写initChannel方法
  • 得到pipeline,不停添加我们需要的解码器HttpServerCodec、ChunkedWriteHandler、聚合器HttpObjectAggregator(用于支持http协议)
  • pipeline 继续添加 WebSocketServerProtocolHandler(用于指定给客户端连接访问的路由 /ws),自定义的ChatHandler来处理自己消息

3.子线程处理器ChatHandler 继承SimpleChannelInboundHandler

  • 得到客户端组ChannelGroup
  • 重写channelRead0方法
    • 获取客户端传输过来的消息 content
    • 给客户端推送消息 clients.writeAndFlush
  • 重写handlerAdded函数,设置打开连接时的要做的事情可以写在这个函数里
    • clients.add(ctx.channel());
  • 重写handlerRemoved函数,(断开连接)
    • clients.remove(ctx.channel());

Chapter 5

1 mui.plusReady

            mui.plusReady(function(){
            //获取当前的webview对象
            var indexWebview = plus.webview.currentWebview()
            //想当前的主页webview追加子页的4张webview对象
            for(var i = 0; i < muxinArray.length; i++){
                var muxinPage = plus.webview.create(muxinArray[i].pageUrl, 
                    muxinArray[i].pageId,
                    muxinStyle)
                //隐藏webview窗口
                muxinPage.hide();
                //追加每一个子页面到当前主页面
                indexWebview.append(muxinPage);
            }
            plus.webview.show(muxinArray[0].pageId);

2 批量绑定tap事件,展示不同的页面

mui(".mui-bar-tab").on("tap", "a",function(){
                //获取当前a标签属性
                var tabindex = this.getAttribute("tabindex")
                //显示点击tab选项所对应的页面
                plus.webview.show(muxinArray[tabindex].pageId, "fade-in")
                for(var i = 0; i < muxinArray.length; i++){
                    if(i != tabindex){
                        plus.webview.hide(muxinArray[i].pageId, "fade-out")
                    }
                }

3 导入外部css

    <link rel="stylesheet" href="fonticon/iconfont.css" />

4 设置状态栏

mui.plusReady(function(){
              plus.navigator.setStatusBarStyle("light")
              plus.navigator.setStatusBarBackground("#2AC845")
          })

5 引入外部icon 直接在class标签里加入icon类名

https://dev.dcloud.net.cn/mui/ui/#icon
https://www.iconfont.cn/

<nav class="mui-bar mui-bar-tab">
        <a class="mui-tab-item mui-active" tabindex="0">
            <span class="mui-icon mui-icon-chat"></span>
            <span class="mui-tab-label">wichat</span>
        </a>

Chapter 6

0 重建mvn

Netty 源码编译,io.netty.util.collection包不存在解决方法
解决办法
cd common
mvn clean compile -Dcheckstyle.skip=true

1 因为有一些my batis错误

重新选择了项目之后解决

2 maven 阿里云镜像

<mirrors>
    <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <mirrorOf>central</mirrorOf>        
    </mirror>
  </mirrors>

来源: <a href="https://developer.aliyun.com/article/78124">https://developer.aliyun.com/article/78124</a>

3 从WSServer开始,

  1. 程序入口
@Componentpublic class NettyBooter implements ApplicationListener<ContextRefreshedEvent> {
    @Override    public void onApplicationEvent(ContextRefreshedEvent event) {
        if(event.getApplicationContext().getParent() == null){
            try{
                WSServer.getInstance().start();            }catch (Exception e){
                e.printStackTrace();            }
        }
    }
}
  1. 得到单例对象,启动
public static WSServer getInstance(){
    return SigletionWSServer.instance;}
public void start(){
    this.future = serverBootstrap.bind(8088);    System.err.println("nety websocker server 启动完毕");}
  1. 初始化器
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
    @Override    protected void initChannel(SocketChannel socketChannel) throws Exception {
    .....
}
  1. 处理消息的handler
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);    @Override    protected void channelRead0(ChannelHandlerContext ctx,                                TextWebSocketFrame msg) throws Exception {
        ...
}

chapt7

1 前后端联调过程中无法交互

  • 前端为Hbuilder上运行的安卓端应用
  • 当通过js代码调用时调用后端进行数据库插入时发现一直超时,没有反应
  • 但是通过电脑端的Chrome浏览器,直接通过url发送get请求时是正常的。(这里url无法模拟Post请求)
  • 于是决定开始排查原因,我通过在UserController 代码中函数入口的第一行编写打印语句,发现没有打印。说明程序根本就没有进入我的Controller代码
  • 于是类似的将注解从POST请求改成GET请求,然后通过在浏览器上通过url的方式发送get请求,这时是可以进入UserController 的函数的,是可以正常打印的
    • 分析1:POST请求没有发送到,但GET请求有发送到。说明我们的代码应该是没有问题的。url: http://192.168.149.1:8080/u/registOrLogin
    • 分析2:GET和POST请求的区别就是一个是在手机上,一个是在后端代码存放的服务器上进行,当时用的ip地址
    • 结论: 手机端与后端代码服务器不在同一个局域网中,
    • 解决办法:将手机连接上wifi,并使用服务器的ipv6地址。http://10.23.23.33:8080/u/registOrLogin

2 前后端联调的过程中前端无法根据缓存进行重定向

  • plus.storage.setItem("userInfo", userInfoStr);
  • 当登录成功之后,应该会保存后端返回的Users对象,前端会做一个判断,如果缓存中有User对象,直接进入主页,而不是进入登录页面
    • 分析1: 可能是因为小米手机的权限问题,无法保存这个Users对象到缓存中
    • 分析2:也可能是因为前端没有获取到这个Users对象,于是采用console.log先打印一遍success:function(data)中的data,即console.log(JSON.stringify(data));{"status":200,"msg":"OK","data":null,"ok":null} at login.html:131。发现 前端确实没有获取后端的Users对象返回值,于是开始后端代码的Controller层去打印这个Service层返回的User对象。当让为了看起来直观一点是有重写Users实体类的toString方法的。Users{id='210521AG0DGMRH28', username='wid', password='ICy5YqxZB1uWSwcVLSNLcA==', faceImage='', faceImageBig='', nickname='wid', qrcode='', cid='57760fcc6495fd6c52d71c75df89ccba'}。发现Service层的返回值没有问题。于是在Controller层往下找,发现Contr层这个登录注册方法的返回值里没有穿进去一个Users的实体类,
    • 解决方法:将实体类对象作为参数传进去。 return IMoocJSONResult.ok(); --> return IMoocJSONResult.ok(userVO);

3还有一些比较奇怪的报错,

js里面console.log("str" + str);对的
console.log("str" ,str);错的

chapter8

1 前后端联调的过程中一直读不到用户的头像


然后去后端找,后端通过toString 打印确实找不到sendFaceImage,然后去看了自己的sql代码,发现sender.face_image as sendFaceImage, 写错为sender.face_image as senderFaceImage了
改正只有即可解决

posted @ 2021-07-11 10:26  weidalin  阅读(451)  评论(0)    收藏  举报