全球信息:【Netty源码分析】03 客户端接入流程
2023-03-29 01:23:35 来源:腾讯云

Netty服务端启动完成,这时候客户端连接就可以接入进来了,下面我们就来分析下客户端连接接入的流程。

之前分析过NioEventLoop线程启动方法是startThread(),由于这个方法里面的逻辑比较复杂,并没有展开,这一节就是从这个方法开始分析。


【资料图】

startThread

private void startThread() {    if (state == ST_NOT_STARTED) {        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {            try {                doStartThread();            } catch (Throwable cause) {                STATE_UPDATER.set(this, ST_NOT_STARTED);                PlatformDependent.throwException(cause);            }        }    }}

这个方法主要主要完成2件事:

利用casNioEventLoop的状态由ST_NOT_STARTED修改成ST_STARTED,即表示NioEventLoop线程启动;执行doStartThread()方法;

doStartThread()方法看着比较复杂,核心逻辑如下,向线程池执行器executor提交一个任务,而这个线程池执行器类型是ThreadPerTaskExecutor,即每次执行任务都会创建一个新线程,而且这个任务是无限循环的:事件轮询selector.select()、事件处理processSelectedKeys()和任务队列处理runAllTasks(),这样NioEventLoop就和具体的Thread线程进行了关联:

private void doStartThread() {    assert thread == null;    //executor线程执行器,类型是:ThreadPerTaskExecutor,即每次执行任务都会创建一个新线程    executor.execute(new Runnable() {        @Override        public void run() {            //将executor线程执行器创建的线程:FastThreadLocalThread保存到EventLoop的全局变量中,相当于thread和EventLoop的绑定            thread = Thread.currentThread();            if (interrupted) {                thread.interrupt();            }            boolean success = false;            updateLastExecutionTime();            try {                //然后调用EventLoop中的run方法进行启动                SingleThreadEventExecutor.this.run();                success = true;            } catch (Throwable t) {                logger.warn("Unexpected exception from an event executor: ", t);            }        }    });}

该方法大致完成2件事:

thread = Thread.currentThread();:将executor线程池分配的线程保存起来,这样就完成了NioEventLoopThread线程的关联;SingleThreadEventExecutor.this.run():具体实现在NioEventLoop.run()方法,所以,startThread()核心就是分配一个线程运行NioEventLoop.run()方法。
protected void run() {    for (;;) {        try {            try {                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {                case SelectStrategy.CONTINUE:// 默认实现下,不存在这个情况                    continue;                case SelectStrategy.BUSY_WAIT:                case SelectStrategy.SELECT:                    //selector.select轮询io事件                    select(wakenUp.getAndSet(false));                    if (wakenUp.get()) {                        selector.wakeup();                    }                default:                }            } catch (IOException e) {                rebuildSelector0();                handleLoopException(e);                continue;            }            cancelledKeys = 0;            needsToSelectAgain = false;            final int ioRatio = this.ioRatio;            if (ioRatio == 100) {               try {                    // 处理 Channel 感兴趣的就绪 IO 事件                    processSelectedKeys();                } finally {                    // 运行所有普通任务和定时任务,不限制时间                    runAllTasks();                }            } else {                final long ioStartTime = System.nanoTime();                try {                    // 处理IO事件                    processSelectedKeys();                } finally {                    // 运行所有普通任务和定时任务,限制时间                    final long ioTime = System.nanoTime() - ioStartTime;                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);                }            }        } catch (Throwable t) {            handleLoopException(t);        }        //  EventLoop 优雅关闭        try {            if (isShuttingDown()) {                closeAll();                if (confirmShutdown()) {                    return;                }            }        } catch (Throwable t) {            handleLoopException(t);        }    }}

该方法主要完成三件事:

select(wakenUp.getAndSet(false)):主要执行selector.select()方法进行事件轮询processSelectedKeys():如果轮询到事件,会在这里进行处理runAllTasks():处理任务队列和定时任务队列中的任务

下面我们就分别来分析下这三个方法。

select

private void select(boolean oldWakenUp) throws IOException {    Selector selector = this.selector;    try {        int selectCnt = 0;//计数器置0        long currentTimeNanos = System.nanoTime();        /**         * selectDeadLineNanos是select()方法运行的截止时间         *         * currentTimeNanos:可以看成当前时间         * delayNanos(currentTimeNanos):获取间隔时间,这里分为两种情况:         * 1、netty里面定时任务队列scheduledTaskQueue是按照延迟时间从小到大进行排序,如果定时任务队列中有任务,         * 则只需要获取到第一个任务的启动时间 - 当前时间 = select()方法可以运行的时间间隔,即:select()方法要在第一个定时任务执行之前退出,这样才能去执行定时任务         * 2、如果定时任务队列没有任务,则delayNanos(currentTimeNanos)返回1秒对应的时间间隔         *         */        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);        for (;;) {            //计算超时时间            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;            /**             * timeoutMillis <= 0表示当前已经超时了,不能继续向下执行select()方法了,需要立即退出select方法,在退出前还有个判断:selectCnt == 0             * selectCnt == 0表示第一次进入循环,则执行下Selector.selectNow()检出准备好的网络IO事件,该方法不会阻塞,             */            if (timeoutMillis <= 0) {                if (selectCnt == 0) {                    selector.selectNow();                    selectCnt = 1;                }                break;            }            /**             * 如果没有超时,但是通过hasTasks()判断到taskQueue任务队列中有需要执行的任务,这时也需要退出select()方法             * 1、利用cas将wakeUp值由false变成true,wakeUp=true表示线程处于唤醒状态,可以执行任务,进入select()方法前会把wakeUp设置成false             * 表示线程处于select()方法阻塞中,不能处理任务队列中的任务,这时只要处理Selector.select()             * 2、退出前执行一次:selector.selectNow()             */            if (hasTasks() && wakenUp.compareAndSet(false, true)) {                //有任务,进行一次非阻塞式的select                selector.selectNow();                selectCnt = 1;                break;            }            //调用select方法,阻塞时间为上面算出的最近一个将要超时的定时任务时间            /**             * 未超时,任务队列中也没有需要执行的任务,这时就可以放心的执行Selector.select()方法了,这里带上之前计算出的超时时间             * 如果之前计算时存在定时任务,则保证在第一个定时任务启动前唤醒即可,没有定时任务则默认超时1秒             */            int selectedKeys = selector.select(timeoutMillis);            //轮询次数+1            selectCnt ++;            /**             * 发生如下几种情况,select()方法都需要退出:             * 1、selectedKeys != 0:表示轮询到IO事件             * 2、oldWakenUp:这个是入参,值为false,是在select()方法中控制是否需要退出,默认是没有使用到的,没有意义             * 3、wakenUp.get():进入select()方法之前,wakeUp被设置成false,如果这里为true,表示已有外部线程对线程进行唤醒操作,             *      一般就是addTask()添加新任务时会触发唤醒,然后及时去执行taskQueue中的任务             * 4、hasTasks() || hasScheduledTasks():判断任务队列和定时任务队列是否有任务需要执行             */            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {                break;            }            //线程中断响应:如果线程被中断,计数器置1,break退出for循环,则退出select()检测            if (Thread.interrupted()) {                if (logger.isDebugEnabled()) {                    logger.debug("Selector.select() returned prematurely because " +                            "Thread.currentThread().interrupt() was called. Use " +                            "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");                }                selectCnt = 1;                break;            }            /**             * 正常情况下:time >= currentTimeNanos + TimeUnit.MILLISECONDS.toNanos(timeoutMillis)             * 但是,jdk nio中存在一个bug,selector.select(timeoutMillis)在没有IO事件触发时并不会等待超时而是立即返回,造成空轮询             *             * 下面就是Netty解决空轮询问题             * 1、if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos)             *      表示selector.select(timeoutMillis)经过超时后才被唤醒,属于正常情况,把selectCnt重置成1             * 2、如果不是,表示可能发生空轮询selectCnt不会被重置成1,for循环一次selectCnt就会被累加1次;             * 3、等到 selectCnt > 门限值,默认是512,可以通过io.netty.selectorAutoRebuildThreshold参数设置,             *      则判断真正发生了nio空循环bug,则重建Selector替换掉当前这个出问题的Selector             */            long time = System.nanoTime();            //判断执行了一次阻塞式select后,当前时间和开始时间是否大于超时时间。(大于是很正常的,小于的话,说明没有执行发生了空轮询)            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {                selectCnt = 1;             } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {                selector = selectRebuildSelector(selectCnt);                selectCnt = 1;                break;            }            currentTimeNanos = time;        }    } catch (CancelledKeyException e) {        if (logger.isDebugEnabled()) {            logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",                        selector, e);        }    }}

select()方法代码看着很复杂,其核心思想理解再来分析就比较简单的。select()主要是用来执行selector.select()IO事件进行轮询,作为server,这里就是轮询OP_ACCEPT事件,看是否有客户端接入进来。但是NioEventLoop是单线程处理模式,不可能让线程一直处理selector.select(),还有轮询到的事件以及任务队列中任务等等都需要使用这个线程进行处理,所以,上面一大堆代码都是用来判断什么时候退出select()方法的,总结下退出逻辑主要分为如下几种情况:

在执行selector.select()方法之前,计算出一个超时时间,超时时间默认是1秒,如果定时任务队列有任务,则取出第一个任务(按顺序存放),保证在该定时任务执行之前退出select()方法即可;如果超时就退出,退出前判断是否是第一次进入for循环,如果是在退出之前调用一次无阻塞的selector.selectNow()轮询下判断任务队列taskQueue中是否有任务,如果有则将wakenUp利用cas设置成true,执行下无阻塞的selector.selectNow()轮询后退出select()方法如果上面情况都不存在,开始执行阻塞selector.select(timeoutMillis)轮询,并将之前计算的超时时间带上;selector.select(timeoutMillis)执行完成后,继续判断是否需要退出select()方法,发生如下任一情况则要退出:轮询到IO事件,则需要退出select()方法去处理事件外部线程对对线程执行过唤醒操作,比如addTask()等操作需要唤醒线程执行队列任务,才能及时去执行taskQueue中的任务:进入select()方法之前,wakeUp被设置成false,如果这里为true,表示已有外部线程对线程进行唤醒操作任务队列taskQueue或定时任务队列scheduledTaskQueue中有需要处理的任务,这时需要退出select()方法,转去执行任务

processSelectedKeys

private void processSelectedKeys() {    //selectedKeys != null表示已对Selector进行优化过,替换掉Selector内部的selectedKeys,正常情况下进入这个流程    if (selectedKeys != null) {        processSelectedKeysOptimized();    } else {        processSelectedKeysPlain(selector.selectedKeys());    } }

processSelectedKeys()主要是对selector.select()方法轮询到的事件进行处理,作为server,如果轮询到OP_ACCEPT,就表示有客户端接入进来了,那我们就跟踪下这个方法,看接入进来的客户端处理流程。

Netty是对Selector进行了优化,将selectedKeysSet实现替换成了数组实现,提升性能,所以,这里一般走的是processSelectedKeysOptimized()这个流程:

private void processSelectedKeysOptimized() {    for (int i = 0; i < selectedKeys.size; ++i) {        final SelectionKey k = selectedKeys.keys[i];        selectedKeys.keys[i] = null;        //k.attachment()获取到的就是NioServerSocketChannel        final Object a = k.attachment();        if (a instanceof AbstractNioChannel) {//一般是走这个分支流程            processSelectedKey(k, (AbstractNioChannel) a);        } else {            @SuppressWarnings("unchecked")            NioTask task = (NioTask) a;            processSelectedKey(k, task);        }        if (needsToSelectAgain) {            selectedKeys.reset(i + 1);            selectAgain();            i = -1;        }    }}

这里关键一点是Object a = k.attachment();,之前分析过向selector注册时把NioServerSocketChannel作为attachment添加进去,所以,这里取出来的就是NioServerSocketChannel对象。processSelectedKey()方法通过if判断事件类型进行处理,server端这里肯定是OP_ACCEPT

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { unsafe.read();}

具体的处理逻辑交由Unsafe对象进行处理:

public void read() {    assert eventLoop().inEventLoop();    final ChannelConfig config = config();    final ChannelPipeline pipeline = pipeline();    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();    allocHandle.reset(config);    boolean closed = false;    Throwable exception = null;    try {        try {            do {                //doReadMessages()读出来一个客户端连接的Channel                int localRead = doReadMessages(readBuf);                if (localRead == 0) {                    break;                }                if (localRead < 0) {                    closed = true;                    break;                }                allocHandle.incMessagesRead(localRead);            } while (allocHandle.continueReading());        } catch (Throwable t) {            exception = t;        }        int size = readBuf.size();        for (int i = 0; i < size; i ++) {            readPending = false;            pipeline.fireChannelRead(readBuf.get(i));        }        readBuf.clear();        allocHandle.readComplete();        pipeline.fireChannelReadComplete();        if (exception != null) {            closed = closeOnReadError(exception);            pipeline.fireExceptionCaught(exception);        }        if (closed) {            inputShutdown = true;            if (isOpen()) {                close(voidPromise());            }        }    } finally {        if (!readPending && !config.isAutoRead()) {            removeReadOp();        }    }}

这个类主要完成2件事:

doReadMessages(readBuf):调用serverSocketChannel.accept()接收到客户端连接socketChannel,并封装成Netty中类型:NioSocketChannel,然后放入到readBuf集合中;pipeline.fireChannelRead(readBuf.get(i));:将读入的客户端连接作为参数,即NioSocketChannel对象,通过pipeline触发channelRead事件进行handler间传播,注意这里的pipelineNioServerSocketChannel中的,即server端的。最终会进入到ServerBootstrapAcceptor#channelRead方法中进行处理。
public void channelRead(ChannelHandlerContext ctx, Object msg) {    final Channel child = (Channel) msg;    child.pipeline().addLast(childHandler);    setChannelOptions(child, childOptions, logger);    for (Entry, Object> e: childAttrs) {        child.attr((AttributeKey) e.getKey()).set(e.getValue());    }    try {        //childGroup.register(child):会给客户端连接进来的Channel从线程池中获取一个EventLoop绑定给Channel        childGroup.register(child).addListener(new ChannelFutureListener() {            @Override            public void operationComplete(ChannelFuture future) throws Exception {                if (!future.isSuccess()) {                    forceClose(child, future.cause());                }            }        });    } catch (Throwable t) {        forceClose(child, t);    }}

这个方法主要完成3件事:

child.pipeline().addLast(childHandler):向NioSocketChannel中添加ServerBootstrap.childHandler(new TestServerInitializer()),后面通过触发handlerAdded()时回调initChannel()实现向pipeline添加handler;设置optionattr信息;childGroup.register(child):将客户端连接NioSocketChannel注册到NioEventLoop实例上,基本和之前分析NioServerSocketChannel注册逻辑一致,这个过程中会触发三个事件:handlerAddedchannelRegisteredchannelActive,之前NioServerSocketChannel注册时只能触发前两个,绑定端口后才能触发第三个事件,客户端连接不存在端口绑定问题,所以这里会直接触发channelActive。和NioServerSocketChannel一样,真正向selector注册感兴趣事件就是在channelActive触发这里:
public void channelActive(ChannelHandlerContext ctx) {    //触发channelActive事件传播    ctx.fireChannelActive();    //向selector注册真正关注的事件    readIfIsAutoRead();}

channelActive和之前分析NioServerSocketChannel的处理逻辑一致,就不再分析。

总结

分析到这里,基本搞清楚了客户端接入的处理流程,现在再次总结下:

NioServerSocketChannel绑定的NioEventLoop不停轮询OP_ACCEPT,触发后通过调用java api获取到ServerSocket,然后包装成NioSocketChannel;然后触发channelRead事件传播,然后会进入server pipeline中非常重要的一个handlerServerBootstrapAcceptor,连接处理器专门处理客户端连接;在ServerBootstrapAcceptor#channelRead()方法中,完成NioSocketChannel的设置:optionattrhandler添加等;最重要的是将channel注册到NioEventLoop上,注册过程中会触发三种事件:handlerAddedchannelRegisteredchannelActive,和之前分析server channel注册过程一样,最终在channelActive这里向selector注册真正感兴趣IO事件,整个流程全部完成。

全球信息:【Netty源码分析】03 客户端接入流程

2023-03-29

王者荣耀:1号重磅皮肤即将返场!投票或将开启,最火限定皮肤非它莫属! 时讯

2023-03-28

【防弹少年团闵玧其SUGA】《其遇》06_即时焦点

2023-03-28

科陆电子(002121.SZ):拟投建“宜春科陆储能基地一期扩建项目”-热讯

2023-03-28

最新快讯!山东海化(000822.SZ):拟以硫酸钾厂现有固定资产和土地使用权出资,与裕滨新材料设骊潍新材料

2023-03-28

青岛市职业学校学籍管理系统登录入口_青岛市职业学校学籍管理系统 最新消息

2023-03-28

小米10怎么打开导航键?小米10导航键声音怎么关?

2023-03-28

今日热门!威神v(肖德俊)成为海外人气歌手

2023-03-28

2023天津东丽区一窗通办试点派出所在哪里? 全球视讯

2023-03-28

全球播报:重大进展!茂名城光世纪城最新规划效果图出炉

2023-03-28

魅族20系列大杯曝光:120Hz E6直屏、12GB 512GB组合 天天报资讯

2023-03-28

【科技前沿】北大研究揭示优化农业生产布局可减少资源消耗

2023-03-28

吃顿饭弄丢14万元现金,幸亏好心老板拾金不昧交由警方归还|全球滚动

2023-03-28

柱间史_关于柱间史简述

2023-03-28

全球观焦点:勤奋好学的成语是什么_勤奋好学的成语

2023-03-28

伯牙鼓琴选自(六年级伯牙鼓琴原文及翻译) 全球速讯

2023-03-28

魔兽世界怀旧银色黎明声望有什么用_魔兽世界银色黎明声望怎么刷 快消息

2023-03-28

18康美MTN001:本息兑付风险提示

2023-03-27

房屋知识科普: 怎样从房产证看商住还是住宅 环球看点

2023-03-27

今天公布的数据显示,英国零售商报告了七个月来的首次积极销售预期,为近期经济复苏的其他迹象增添了砝码。目前,英国的通胀率维持在10%左右,加上不断上升的利率和增税的前景,消费者正在受到沉重打击。CBI首席经济学家MartinSartorius表示

2023-03-27

血本无归!国米或遭强制转让!引援再生变数,免签或是唯一出路 世界热议

2023-03-27

为博“老铁们”关注,男子网络晒酒驾、飙车视频!

2023-03-27

贵州沿河:弘扬红色文化 做活特色经济 助推绿色发展

2023-03-27

【新视野】2023大连灵活就业人员社保缴费标准及基数标准一览

2023-03-27

速读:长线疲软,走势平600880股票淡

2023-03-27

天天热议:中俄珲春口岸人员往来和边境贸易实现稳步增长

2023-03-27

内蒙古电解铝产业尚具发展前景

2023-03-27

富春股份:AIGC技术将推动游戏行业新一轮发展变革

2023-03-27

秋浦河石台段入选全国“最美家乡河”_世界热资讯

2023-03-27

大葛和什么煲汤比较好吃 什么和 大葛煲汤比较好吃

2023-03-27

老雷斯的故事动画片(老雷斯的故事的介绍) 消息

2023-03-26

世界微动态丨鲜活饮品IPO:台湾黄国晃夫妇控制87.1%股份,曾被罚款12万元新台币

2023-03-26

女子偷闺蜜百万奢侈品用高仿替代 具体是什么情况?

2023-03-26

竹子作文借物喻人_竹子作文|天天快报

2023-03-26

意大利面最简单的做法

2023-03-26

公安县:打造水上运动新品牌-观点

2023-03-26

车suv是什么意思_车suv是代表的意思

2023-03-26

吴川市气象台发布暴雨橙色预警【II级/严重】【2023-03-26】|天天快看点

2023-03-26

怎样选不锈钢蒸锅?

2023-03-26

半球电热水壶价格_半球电热水壶|世界快播报

2023-03-26

猫耳fm_猫儿

2023-03-25

【环球时快讯】积分换手机的软件是什么东西_6pn换积分是什么网址

2023-03-25

中华鲎怎么读 焦点报道

2023-03-25

微软收购动视暴雪不会实质性损害游戏机市场竞争

2023-03-25

最新快讯!逃脱本色1-11(逃脱本色1 13)

2023-03-25

滚动:黄奇帆:中国的人口优势越来越转换为超大规模的市场优势

2023-03-25

《国宝故事之风铃奇缘》上演 博物馆里体验舞台剧

2023-03-25

如何评价荧屏上李连杰的太极拳 快资讯

2023-03-25

今年郑州社科工作重点敲定

2023-03-25

哥伦比亚今年以来已缴获56吨可卡因

2023-03-25

凝练前瞻性未来技术方向 高校增设“四新”专业

2023-03-24

环球要闻:金溢科技:3月23日公司高管罗瑞发减持公司股份合计6.43万股

2023-03-24

立春时间2020 是几月几日 天天速讯

2023-03-24

直击广州下冰雹 这是什么情况?到底是怎么回事?_天天要闻

2023-03-24

如何把png转化为jpg

2023-03-24

美环境、速整改、见成效,门头沟公路分局奏响创城“进行曲” 全球滚动

2023-03-24

内瓦车展正式亮相-关注

2023-03-24

怎样在抖音上发作品(抖音如何删除自己的作品)|速读

2023-03-24

世界观点:怎么看自己的ip地址

2023-03-24

dna分子是什么结构_dna分子结构的介绍

2023-03-24

文博体育公园提升改造工程将于4月开工建设|微速讯

2023-03-24

《牛魔王之魔王再临》定档3月26日 樊少皇携西游IP回归 再掀高燃神魔大战

2023-03-24

荆门“检学研一体化”民事执行监督和行政违法监督业务研究小组联合开展研讨、培训活动

2023-03-24

中国酸菜鱼预制菜行业:技术升级与创新成为酸菜鱼预制菜品质升级的重要突破口|天天看点

2023-03-24

精彩看点:两千关口已难挡黄金攻势 多头疾呼:金价很快要挑战前高!

2023-03-24

世界百事通!德国著作权法 第2版

2023-03-24

沈腾参演过的电影以及电视剧有哪些-快资讯

2023-03-23

贵州茅台股票行业分析(中行个人网上银行)-环球微动态

2023-03-23

长安家族存在感不高的“大哥” CS95PLUS是什么实力?|今头条

2023-03-23

环球热消息:郑永年:成功的民主基于对现实情况的尊重,“进口”的民主多以失败告终

2023-03-23

自己追尾可以报保险吗

2023-03-23

李栋旭裴秀智分手了吗

2023-03-23

银行,你能不能有点担当,能不能痛痛快快把钱赔了-世界报道

2023-03-23

今日白银回收价格查询(2023年3月23日)_全球聚看点

2023-03-23

签订3项创业孵化基地共建 2023年鲁甘人力资源服务协同发展对接活动硕果累累 世界热议

2023-03-23

环球短讯!做好防汛工作!番禺区严阵以待应对今年第一轮强降水

2023-03-23

动物研究后骨关节炎的潜在缓解进入临床试验

2023-03-23

最新!沈阳地铁4号线有重要进展!-看热讯

2023-03-23

【天天热闻】2022年放假安排时间表学生暑假

2023-03-23

Buha:湖人曾试训&有意签下白莱昂纳德 现在补强内线的选择不多

2023-03-23

中航西飞(000768):3月22日北向资金减持50.24万股

2023-03-23

2023青岛市玉兰花期及观赏地 世界实时

2023-03-22

2020教师资格证保教知识与能力考题_2020教师 即时

2023-03-22

巴士驾驶员2008,公交车司机旁边有门吗

2023-03-22

环球视讯!长方脸适合的发型图_长方脸适合什么发型男

2023-03-22

匈外长警告:北约国家不要与俄直接冲突|环球新消息

2023-03-22

中兴通讯:合计8050万元认购众投湛卢二期基金

2023-03-22

离职了公积金怎么提取出来?公积金可以全部提取出来吗?

2023-03-22

《易经》:所有“水逆”都是一时的,读懂这4句话,助你扭转困局 当前信息

2023-03-22

来新化!集五张特色名片,游神韵上梅古镇|环球微资讯

2023-03-22

快报:北京市西城区发布沙尘暴黄色预警

2023-03-22

断玉修复_断玉

2023-03-22

居然之家装修报价明细_居然之家装修

2023-03-21

天地板和地天板的区别是什么 微信转账显示待入账是什么意思-全球微动态

2023-03-21

大行评级|大摩:料周大福3月同店销售录正增长予目标价17港元 全球头条

2023-03-21

3月21日指南针涨7.43%,易方达科技创新混合基金重仓该股_天天讯息

2023-03-21

抢抓早春生产建设黄金期 南宁五象新区重大项目建设再提速_每日报道

2023-03-21

刷卡机可以刷京东白条吗 天天速讯

2023-03-21

卫宁健康:3月20日融资买入2424.74万元,融资融券余额8.22亿元

2023-03-21

浙江永康国土空间规划草案公示 目标建设世界五金之都

2023-03-21