Netty
服务端启动完成,这时候客户端连接就可以接入进来了,下面我们就来分析下客户端连接接入的流程。
之前分析过NioEventLoop
线程启动方法是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件事:
利用cas
将NioEventLoop
的状态由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
线程池分配的线程保存起来,这样就完成了NioEventLoop
和Thread
线程的关联;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()
:处理任务队列和定时任务队列中的任务下面我们就分别来分析下这三个方法。
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()
方法,转去执行任务private void processSelectedKeys() { //selectedKeys != null表示已对Selector进行优化过,替换掉Selector内部的selectedKeys,正常情况下进入这个流程 if (selectedKeys != null) { processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); } }
processSelectedKeys()
主要是对selector.select()
方法轮询到的事件进行处理,作为server
,如果轮询到OP_ACCEPT
,就表示有客户端接入进来了,那我们就跟踪下这个方法,看接入进来的客户端处理流程。
Netty
是对Selector
进行了优化,将selectedKeys
由Set
实现替换成了数组实现,提升性能,所以,这里一般走的是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
间传播,注意这里的pipeline
是NioServerSocketChannel
中的,即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
这个方法主要完成3件事:
child.pipeline().addLast(childHandler)
:向NioSocketChannel
中添加ServerBootstrap.childHandler(new TestServerInitializer())
,后面通过触发handlerAdded()
时回调initChannel()
实现向pipeline添加handler
;设置option
和attr
信息;childGroup.register(child)
:将客户端连接NioSocketChannel
注册到NioEventLoop
实例上,基本和之前分析NioServerSocketChannel
注册逻辑一致,这个过程中会触发三个事件:handlerAdded
、channelRegistered
和channelActive
,之前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
中非常重要的一个handler
:ServerBootstrapAcceptor
,连接处理器专门处理客户端连接;在ServerBootstrapAcceptor#channelRead()
方法中,完成NioSocketChannel
的设置:option
、attr
、handler
添加等;最重要的是将channel
注册到NioEventLoop
上,注册过程中会触发三种事件:handlerAdded
、channelRegistered
和channelActive
,和之前分析server channel
注册过程一样,最终在channelActive
这里向selector
注册真正感兴趣IO事件
,整个流程全部完成。 关键词:
最新推荐
Netty服务端启动完成,这时候客户端连接就可以接入进来了,下面我们就来分析下客户端连接接入的流程。
3月27日,中国汽车工业协会整理的国家统计局数据显示,今年前2个月,汽车制造业营收12847 3亿元,同比...
3月28日,据美国国家公路交通安全管理局披露,克莱斯勒召回部分2020-2023款Jeep牧马人车型,共计5 8万...
3月28日,中国汽车流通协会汽车市场研究分会(乘用车市场信息联席会)联合广州威尔森信息科技有限公司发...
3月28日,有消息称,今年2月,宁德时代计划投资73 4亿欧元的第二座欧洲工厂,因遭当地居民反对陷入停滞...
据摩登天空官方微博,2023北京草莓音乐节即将回归,定档五一假期,将于4月29日-5月1日与乐迷见面,更多...
1、塞翁失马,焉知非福的意思是:比喻虽然一时受到损失,也许反而因此能得到好处。2、形容人的心态,一...
成都地铁18号线快线时刻表后续,快线时刻表仍会根据客流变化进行调整,敬请关注,一切以官方信息为准。...
1、◎译名恐怖大师系列:印记 鬼伎回忆录◎片名MastersOfHorrorImprint◎年代2
一直以来,很多人都觉得,游戏只是一种娱乐方式,但却忽视了游戏拥有许多实实在在的硬核技术。
鞍钢股份(00347)相对活跃,尾盘升约5%。22日,富达再度进场增持鞍钢股份H股,持仓占比由5 22%升至6 35...
农行信用卡积分当钱花想要用农行信用卡积分抵现,需要在手机上下载一个本来生活APP,注册账号进行登录,...
3月28日,智能新能源卡车造车新势力公司DeepWay宣布,完成7 7亿元A+轮股权融资,目前DeepWay已累积融资超过12亿元。
民生信用卡5000直提5万民生信用卡额度是比较容易提的,不过从5000直提到5万,额度翻了10倍,虽说是可以...
信用卡没还会影响花呗使用吗?信用卡没还不一定会影响花呗使用的,毕竟花呗开通后不会去查征信的,有额度...
征信信用卡审批有影响征信上的信用卡审批记录,属于硬查询记录,和贷款审批、担保资格审查一样,都会影...
信用卡冻结积分可以兑换礼品吗?其实,对信用卡冻结积分是否能用,每个银行都没有明确的答复,但是根据大...
超期还款天数怎么计算?超期还款天数是不包括还款日当天,而是从第二天开始计算的。打个比方,招行信用卡...
(记者杨毅)针对28日大连地铁五号线“列车车门自动打开”一事,中铁大连地铁五号线公司发布消息称,系单...
怎么点外卖更省钱很多朋友经常在美团、饿了么点外卖,虽说开通会员可以领优惠券,但是有数量限制,不能...
继文心一言发布、微软全线接入GPT-4后,大佬们又有新动作了。3月21日晚上11点,GTC2023开幕,英伟达CEO...
混合基金有哪些优点混合基金是指同时投资于股票、债券和货币市场等工具,没有明确的投资方向的基金。债...
IOPV有什么用简单来说,ETF引入IOPV指标,是较为准确的反映了ETF份额中的实时净值,也提高了交易的透明...
基金的换手率对基金有什么影响基金的换手率是我们在了解一只基金的时候比较重要的参考指标,所谓换手率...
基金放几年合适基金放几年,会不会赚钱,其实可以根据基金的类型来考虑:如果是货币基金的话,其实随便...
为什么大家都说基金定投是懒人理财法呢?因为基金定投有以下的优点:1、基金定投相当于每个月强迫储蓄,...
行业指数基金怎么选我们做基金投资,如果是入门级别的选手的话,我们建议可以直接从沪深300和中证500指...
混合基金与股票基金哪个风险大混合型基金又称混合基金,是指以股票、债券等为投资对象的基金。混合型基...
股票怎么有效控制回撤设置止盈止损止盈止损在短线操作中尤为重要,稍不注意账户就会出现大幅回撤,我们...
3月27日,FAENZA法恩莎受邀参与2021-2022住宅产业年会暨第六届CBDA住宅产业(红鼎)创新大赛发布仪式,...
填权股如何操作如果是高价股除权,那么不要在除权后马上买进,因为这样的股票往往有高估的风险。如果是...
量比怎么计算与换手率、委比、委差等百分率指标不同的是,量比通常是一个倍数,倍数的大小反映短期内成...
为进一步激发学生的阅读兴趣,提升学生阅读素养和人文素养,近日,西安经开区多所学校开展了精彩的“书...
仙人指路形态买卖点与仓位控制当判断股票出现仙人指路形态后,买点有三种,卖点无法预判只能根据盘面信...
主力出货的信号散户想要在主力出货之前逃顶,就必须提前观察到主力出货的信号,一旦主力有所动作就可以...
重大资产重组是利好还是利空公司发生重大资产重组,需公告停牌并报请证监会和交易所批准。如果被收购方...
散户怎么寻找黄金坑形态的买点?1 激进的买点是大阴线破位下跌时带长下影线,可以预判买入少量仓位。2...
市盈率种类市盈率是指股票的市场价格与每股盈利的比值,又叫本益比。它反映的是股东买入股票的成本与持...
回购的股票如何处理1 直接注销,以减少市场流通股本的数量,这样可以提高每股盈利,从而是市盈率降低...
融资融券的原理是什么两融业务的原理说白了,就是有些投资者觉得自己资金太少但又想多赚钱,刚好证券公...
配资炒股风险大不大与不配资的人相比较,风险肯定是变大了,但同时对于需要资金来扩大投资以获取更高收...
地球知识局微信公众号:地球知识局据说地球人民都关注分享我局了(⊙v⊙)二里头遗址新发现作者:赵海涛制...
IT之家3月28日消息,据北京科技大学消息,我国人工智能领域著名科学家、人工智能学科的主要奠基人、中国...
外交部近日组织来自欧洲、非洲、亚洲的26个国家和地区的驻华使节代表团赴重庆参访,智能制造第一站便走...
℃摄氏度符号怎么打01利用搜狗输入法打摄氏度符号02点击最右边的工具箱按钮-表情符号-符号大全。03出现...
1、剁手是指网上购物,不知不觉间花费大量金钱,回头一看账单懊恼不已。2、自嘲要剁手。本文就为大家分...
如何批量修改图片尺寸?01直切主题,软件是免安装的绿色小工具,直接双击运行exe程序就能使用了。如下图...
上古卷轴5原版捏脸攻略01选择了个预设脸,然后选了个还可以的眼睛和一个嘴巴,鼻子,然后随便捏了一下。...
怎样快速修改图片上的文字和数字01首先在我们的电脑桌面上找到美图秀秀并点击它,如下图所示。02然后点...
如何用WiFi万能钥匙使电脑连上WiFi01若自己的手机还未能连上WiFi,那么可以下载一个WiFi万能钥匙,打开...
选海信·你放心| 海信地产品质节 你可以一直相信海信
"18华润置地MTN002B4"将于4月4日兑付 债券余额为35亿元
环球播报:马斯克又嘲讽比尔盖茨:到今天,他对AI的理解依然有限
广西举办第11届节地生态安葬活动 2356名逝者骨灰与花草为伴
10万以内的新能源电动车有哪些?新能源汽车北方冬天可以用吗?
环球时讯:理想汽车组织动刀:OKR升级为PBC,绩效考核周期延长至半年
新能源新车磨合期注意事项有哪些?新能源车年检新规2023年新规定看这里
广西举办第十一届节地生态安葬活动 2356名逝者骨灰与鲜花为伴-环球微动态
个人房屋转租租合同怎么写?个人房屋转租租合同范本一览-焦点热讯
《我的思念是圆的》 教案该如何设计?《我的思念是圆的》 教案设计汇总 世界实时
八年级上册历史教学计划有哪些?八年级上册历史教学计划详情介绍
环球今热点:初二新学期的学习计划怎么写?初二新学期的学习计划范文一览
【独家】劳动法加班小时工资怎么计算?劳动法加班小时工资计算方法是什么?
当前速递!小学教师的年度工作怎么总结?小学教师的年度工作总结3篇
要闻:《心理学与生活》这本书你读过吗?心理学与生活读后感(精选10篇)
安全气囊灯亮了还可以继续开吗?江铃驭胜S350发动机是哪产的?
重实效 强实干 抓落实丨百色首趟铝产品集装箱直达班列驶向广东:报资讯
联系我们:55 16 53 8@qq.com
关于我们| 联系方式| 版权声明| 供稿服务| 友情链接
华讯网 版权所有,未经书面授权禁止使用
Copyright©2008-2020 By www.saibeinews.com All Rights Reserved