当前位置: 首页 > news >正文

做网站平台应该注意哪些白嫖永久服务器

做网站平台应该注意哪些,白嫖永久服务器,wordpress隐藏菜单,做一个app软件大概需要多少钱QEventLoop屏蔽了底层消息循环实现细节,向上提供了与平台无关的消息/事件循环。 本文拟对Windows系统下QEventLoop的实现原理予以分析。 注1:限于研究水平,分析难免不当,欢迎批评指正。 注2:文章内容会不定期更新。 …

QEventLoop屏蔽了底层消息循环实现细节,向上提供了与平台无关的消息/事件循环。

本文拟对Windows系统下QEventLoop的实现原理予以分析。

注1:限于研究水平,分析难免不当,欢迎批评指正。

注2:文章内容会不定期更新。

一、研究素材:Win32应用程序框架

在Win32应用程序中,wWinMain是整个程序的入口点,整个代码段主要包括窗口类注册、创建窗口、消息循环等。

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR    lpCmdLine,_In_ int       nCmdShow)
{UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine);// TODO: Place code here.// Initialize global stringsLoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// Perform application initialization:if (!InitInstance (hInstance, nCmdShow)){return FALSE;}HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));MSG msg;// Main message loop:while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}return (int) msg.wParam;
}

MyRegisterClass用于注册创库类,可以指定窗口样式、窗口过程函数等。

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEXW wcex;wcex.cbSize = sizeof(WNDCLASSEX);wcex.style          = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc    = WndProc;wcex.cbClsExtra     = 0;wcex.cbWndExtra     = 0;wcex.hInstance      = hInstance;wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);wcex.lpszClassName  = szWindowClass;wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));return RegisterClassExW(&wcex);
}

完成窗口类注册之后,可以依据窗口类创建窗口实例,

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst = hInstance; // Store instance handle in our global variableHWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}

当完成窗口类注册之后,便可以依据窗口类名称来创建窗口。在Windows系统下,消息队列用于管理线程内窗口相关的消息,同一线程内的窗口对象共享同一各消息队列。

The Message Loop

For each thread that creates a window, the operating system creates a queue for window messages. This queue holds messages for all the windows that are created on that thread. 

    MSG msg;// Main message loop:while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}

当消息被投递到窗口时,便会调用对应的窗口过程函数,

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:{int wmId = LOWORD(wParam);// Parse the menu selections:switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc = BeginPaint(hWnd, &ps);// TODO: Add any drawing code that uses hdc here...EndPaint(hWnd, &ps);}break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}

二、QEventLoop实现原理

QEventLoop实际上是通过QAbstractEventDispatcher子类来屏蔽了底层窗口系统的消息循环。

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{Q_D(QEventLoop);if (!d->threadData->hasEventDispatcher())return false;return d->threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}

从中可以看出,QAbstractEventDispatcher是线程级别的存在,每个线程有唯一的QAbstractEventDispatcher实现,线程内的所有QEventLoop共享同一个QAbstractEventDispatcher实现。

Ref. from QAbstractEventDispatcher 

An event dispatcher receives events from the window system and other sources. It then sends them to the QCoreApplication or QApplication instance for processing and delivery. QAbstractEventDispatcher provides fine-grained control over event delivery.

2.1 QEventDispatcherWin32的创建

实际上,在Windows系统下,当创建QCoreApplication时,便会创建主线程相关的QEventDispatcherWin32,而QEventDispatcherWin32正是QAbstractEventDispatcher的Windows系统实现。

void QCoreApplicationPrivate::init()
{// ...
#ifndef QT_NO_QOBJECT// use the event dispatcher created by the app programmer (if any)Q_ASSERT(!eventDispatcher);eventDispatcher = threadData->eventDispatcher.loadRelaxed();// otherwise we create oneif (!eventDispatcher)createEventDispatcher();Q_ASSERT(eventDispatcher);if (!eventDispatcher->parent()) {eventDispatcher->moveToThread(threadData->thread.loadAcquire());eventDispatcher->setParent(q);}threadData->eventDispatcher = eventDispatcher;eventDispatcherReady();
#endif// ...
}
void QCoreApplicationPrivate::createEventDispatcher()
{Q_Q(QCoreApplication);QThreadData *data = QThreadData::current();Q_ASSERT(!data->hasEventDispatcher());eventDispatcher = data->createEventDispatcher();eventDispatcher->setParent(q);
}
QAbstractEventDispatcher *QThreadData::createEventDispatcher()
{QAbstractEventDispatcher *ed = QThreadPrivate::createEventDispatcher(this);eventDispatcher.storeRelease(ed);ed->startingUp();return ed;
}
QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data)
{Q_UNUSED(data);
#ifndef Q_OS_WINRTreturn new QEventDispatcherWin32;
#elsereturn new QEventDispatcherWinRT;
#endif
}

2.2 QEventDispatcherWin32的实现

由前面的分析可知,在Windows系统下,QEventLoop实际上是使用QEventDispatcherWin32来完成消息循环。那QEventDispatcherWin32又是如何实现对Windows消息循环的封装呢?

在QEventDispatcherWin32中,注册了一个"QEventDispatcherWin32_Internal_Widget"窗口类,而对应的窗口过程函数主要是提供了对定时器、套接字等消息处理。

QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext(): atom(0), className(0)
{// make sure that multiple Qt's can coexist in the same processconst QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")+ QString::number(quintptr(qt_internal_proc));className = new wchar_t[qClassName.size() + 1];qClassName.toWCharArray(className);className[qClassName.size()] = 0;WNDCLASS wc;wc.style = 0;wc.lpfnWndProc = qt_internal_proc;wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hInstance = GetModuleHandle(0);wc.hIcon = 0;wc.hCursor = 0;wc.hbrBackground = 0;wc.lpszMenuName = NULL;wc.lpszClassName = className;atom = RegisterClass(&wc);if (!atom) {qErrnoWarning("%ls RegisterClass() failed", qUtf16Printable(qClassName));delete [] className;className = 0;}
}

同时,依据该窗口类,创建了一个内部窗口,

void QEventDispatcherWin32::createInternalHwnd()
{Q_D(QEventDispatcherWin32);if (d->internalHwnd)return;d->internalHwnd = qt_create_internal_window(this);// setup GetMessage hook needed to drive our posted eventsd->getMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC) qt_GetMessageHook, NULL, GetCurrentThreadId());if (Q_UNLIKELY(!d->getMessageHook)) {int errorCode = GetLastError();qFatal("Qt: INTERNAL ERROR: failed to install GetMessage hook: %d, %ls",errorCode, qUtf16Printable(qt_error_string(errorCode)));}// start all normal timersfor (int i = 0; i < d->timerVec.count(); ++i)d->registerTimer(d->timerVec.at(i));
}
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();if (!ctx->atom)return 0;HWND wnd = CreateWindow(ctx->className,    // classnamectx->className,    // window name0,                 // style0, 0, 0, 0,        // geometryHWND_MESSAGE,            // parent0,                 // menu handleGetModuleHandle(0),     // application0);                // windows creation data.if (!wnd) {qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");return 0;}#ifdef GWLP_USERDATASetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher));
#elseSetWindowLong(wnd, GWL_USERDATA, reinterpret_cast<LONG>(eventDispatcher));
#endifreturn wnd;
}

在QEventDispatcherWin32::processEvents中,会不断的调用PeekMessage检查消息队列,然后调用TranslateMessage与DispatchMessage进行消息转发。

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{Q_D(QEventDispatcherWin32);if (!d->internalHwnd) {createInternalHwnd();wakeUp(); // trigger a call to sendPostedEvents()}d->interrupt.storeRelaxed(false);emit awake();// To prevent livelocks, send posted events once per iteration.// QCoreApplication::sendPostedEvents() takes care about recursions.sendPostedEvents();bool canWait;bool retVal = false;do {DWORD waitRet = 0;DWORD nCount = 0;HANDLE *pHandles = nullptr;if (d->winEventNotifierActivatedEvent) {nCount = 1;pHandles = &d->winEventNotifierActivatedEvent;}QVarLengthArray<MSG> processedTimers;while (!d->interrupt.loadRelaxed()) {MSG msg;bool haveMessage;if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {// process queued user input eventshaveMessage = true;msg = d->queuedUserInputEvents.takeFirst();} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {// process queued socket eventshaveMessage = true;msg = d->queuedSocketEvents.takeFirst();} else {haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);if (haveMessage) {if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)&& isUserInputMessage(msg.message)) {// queue user input events for later processingd->queuedUserInputEvents.append(msg);continue;}if ((flags & QEventLoop::ExcludeSocketNotifiers)&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {// queue socket events for later processingd->queuedSocketEvents.append(msg);continue;}}}if (!haveMessage) {// no message - check for signalled objectswaitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {// a new message has arrived, process itcontinue;}}if (haveMessage) {if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {// Set result to 'true', if the message was sent by wakeUp().if (msg.wParam == WMWP_QT_FROMWAKEUP)retVal = true;continue;}if (msg.message == WM_TIMER) {// avoid live-lock by keeping track of the timers we've already sentbool found = false;for (int i = 0; !found && i < processedTimers.count(); ++i) {const MSG processed = processedTimers.constData()[i];found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);}if (found)continue;processedTimers.append(msg);} else if (msg.message == WM_QUIT) {if (QCoreApplication::instance())QCoreApplication::instance()->quit();return false;}if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}} else if (waitRet - WAIT_OBJECT_0 < nCount) {activateEventNotifiers();} else {// nothing todo so breakbreak;}retVal = true;}// still nothing - wait for message or signalled objectscanWait = (!retVal&& !d->interrupt.loadRelaxed()&& (flags & QEventLoop::WaitForMoreEvents));if (canWait) {emit aboutToBlock();waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);emit awake();if (waitRet - WAIT_OBJECT_0 < nCount) {activateEventNotifiers();retVal = true;}}} while (canWait);return retVal;
}

2.3 小结

依据上述对QEventLoop的实现分析,大体上可有以下结论:

1. QEventLoop内部实际上是通过QAbstractEventDispatcher子类来完成了消息(事件)分发,这实际上就是GoF's Bridge Pattern。

2. QAbstractEventDispatcher定义了事件分发的接口,而针对具体窗口系统的实现则有QEventDispatcherWin32、QEventDispatcherWinRT、QEventDispatcherUNIX、QEventDispatcherGlib等负责。

3. 每个线程都有唯一的同一事件分发器,线程内的窗体共用同一事件分发器。

4.  在Windows系统下,QEventLoop::processEvents函数大体等价于PeekMessage/TranslateMessage/DispatchMessage。

三、Qt中的事件路由

3.1 消息转换

在Qt中,当窗体接受到底层窗口系统消息之后,又是如何将这些平台相关的消息转换成Qt事件呢?

Ref. from QEvent 

Qt's main event loop (QCoreApplication::exec()) fetches native window system events from the event queue, translates them into QEvents, and sends the translated events to QObjects.

In general, events come from the underlying window system (spontaneous() returns true), but it is also possible to manually send events using QCoreApplication::sendEvent() and QCoreApplication::postEvent() (spontaneous() returns false).

实际上,对于QGuiApplication,会基于QPA (Qt Platform Abstraction)来创建QPlatformIntegration,进而创建特定的QAbstractEventDispatcher。

Ref. from Qt Platform Abstraction 

The Qt Platform Abstraction (QPA) is the platform abstraction layer for Qt 5 and replaces Qt for Embedded Linux and the platform ports from Qt 4.

QPA plugins are implemented by subclassing various QPlatform* classes. There are several root classes, such as QPlatformIntegration and QPlatformWindow for window system integration and QPlatformTheme for deeper platform theming and integration. QStyle is not a part of QPA.

void QGuiApplicationPrivate::createEventDispatcher()
{Q_ASSERT(!eventDispatcher);if (platform_integration == 0)createPlatformIntegration();// The platform integration should not mess with the event dispatcherQ_ASSERT(!eventDispatcher);eventDispatcher = platform_integration->createEventDispatcher();
}

对于Windows系统,QGuiApplication会通过加载qwindows.dll插件来创建QWindowsIntegration对象。

QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const
{return new QWindowsGuiEventDispatcher;
}

也就是说,在Windows系统下,QGuiApplication实际上是通过QWindowsGuiEventDispatcher来实现对底层消息的调度。

void QWindowsGuiEventDispatcher::sendPostedEvents()
{QEventDispatcherWin32::sendPostedEvents();QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}
bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{int nevents = 0;while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {QWindowSystemInterfacePrivate::WindowSystemEvent *event = nullptr;if (QWindowSystemInterfacePrivate::platformFiltersEvents) {event = QWindowSystemInterfacePrivate::getWindowSystemEvent();} else {event = flags & QEventLoop::ExcludeUserInputEvents ?QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :QWindowSystemInterfacePrivate::getWindowSystemEvent();}if (!event)break;if (QWindowSystemInterfacePrivate::eventHandler) {if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))nevents++;} else {nevents++;QGuiApplicationPrivate::processWindowSystemEvent(event);}// Record the accepted state for the processed event// (excluding flush events). This state can then be// returned by flushWindowSystemEvents().if (event->type != QWindowSystemInterfacePrivate::FlushEvents)QWindowSystemInterfacePrivate::eventAccepted.storeRelaxed(event->eventAccepted);delete event;}return (nevents > 0);
}

对于来自底层窗口系统的鼠标消息,可以看到QWindowsGuiEventDispatcher最终是通过调用QGuiApplicationPrivate::processMouseEvent,

void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{Q_TRACE_SCOPE(QGuiApplicationPrivate_processWindowSystemEvent, e->type);switch(e->type) {case QWindowSystemInterfacePrivate::Mouse:QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));break;// ...default:qWarning() << "Unknown user input event type:" << e->type;break;}
}

而正是在这个函数中,实现了底层窗口消息到Qt事件的转换。

void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e)
{// ...QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source);ev.setTimestamp(e->timestamp);if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {// a modal window is blocking this window, don't allow mouse events throughreturn;}if (doubleClick && (ev.type() == QEvent::MouseButtonPress)) {// QtBUG-25831, used to suppress delivery in qwidgetwindow.cppsetMouseEventFlags(&ev, ev.flags() | Qt::MouseEventCreatedDoubleClick);}QGuiApplication::sendSpontaneousEvent(window, &ev);// ...
}

以上代码分析,也可通过在Qt应用程序设置断点,观察函数堆栈调用来进一步佐证。

Message from the underlying window system

3.2 事件处理

当底层窗口消息转换成Qt事件之后,最终会由QCoreApplication进行事件路由。

bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{// no events are delivered after ~QCoreApplication() has startedif (QCoreApplicationPrivate::is_app_closing)return true;return doNotify(receiver, event);
}static bool doNotify(QObject *receiver, QEvent *event)
{if (receiver == 0) {                        // serious errorqWarning("QCoreApplication::notify: Unexpected null receiver");return true;}#ifndef QT_NO_DEBUGQCoreApplicationPrivate::checkReceiverThread(receiver);
#endifreturn receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{// Note: when adjusting the tracepoints in here// consider adjusting QApplicationPrivate::notify_helper too.Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());bool consumed = false;bool filtered = false;Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);// send to all application event filters (only does anything in the main thread)if (QCoreApplication::self&& receiver->d_func()->threadData->thread.loadAcquire() == mainThread()&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {filtered = true;return filtered;}// send to all receiver event filtersif (sendThroughObjectEventFilters(receiver, event)) {filtered = true;return filtered;}// deliver the eventconsumed = receiver->event(event);return consumed;
}

从中可以看出,Qt事件首先分别让应用程序过滤器、对象过滤器进行处理,最后再交由虚函数QObject::event(QEvent *e)进行处理。

对于QWidget,QWidget::event(QEvent *e)实际上就是通过调用mousePressEvent(QMouseEvent *event) 、mouseReleaseEvent(QMouseEvent *event)等虚函数来处理各种具体的事件。

3.3 小结

基于上述分析,可有以下结论:

1. Qt基于QPA实现了跨窗口系统。QPA实际上是一种插件系统。

2. 在Windows系统下,QGuiApplication创建QWindowsIntegration。

3. QCoreApplication使用QEventDispatcherWin32进行消息调度;

4. QWindowsIntegration使用QWindowsGuiEventDispatcher进行消息调度。

5. QEvent先有系统级的filter处理,再有对象级的filter处理,最后交由目标对象处理。

四、扩展:Qt插件系统

4.1 动态插件

4.2 静态插件

参考文献

Erich Gamma. Design Patterns:elements of reusable object-oriented software. Addison Wesley, 1994.

Joseph Ingeno. Handbook of Software Architecture.  

参考资料

QEventLoop

Get Started with Win32 and C++  

QAbstractEventDispatcher

QThread  

QCoreApplication

http://www.khdw.cn/news/34925.html

相关文章:

  • 如何进入一个网站开发人员工具优化网站内容
  • 国开行网站毕业申请怎么做搜索引擎网站大全
  • 武威网站建设优化抖音营销推广方案
  • 网站导航栏按钮企业网站推广渠道
  • 做艺术教育的网站深圳网络营销推广方案
  • 皖icp阜阳网站建设618网络营销策划方案
  • 用别的域名给网站做竞价seo排名技术软件
  • 商城开发分销系统seo博客优化
  • 上海浦东网站建设搜狗收录入口
  • 做钓鱼网站的公司营销渠道的概念
  • 江门那里做公司网站好seo建站公司推荐
  • 原创小说网站建设源码丽水网站seo
  • 莆田网站建设电话网络营销推广公司简介
  • 建设网站什么软件比较好电商产品推广方案
  • 顺德最新疫情seo排名赚靠谱吗
  • 可以做t恤的网站全球搜索
  • 一品威客网是真的吗北京seo多少钱
  • 网站登录注册怎么做seo公司品牌哪家好
  • 哈尔滨网站改版帮我搜一下长沙做网络销售
  • 广州的做淘宝女鞋货源下载数据包的网站网址是多少?小熊猫seo博客
  • 我想看b站直播开元棋牌专业北京网站建设公司
  • 北京培训机构杭州seo排名公司
  • 广州做外贸网站多少钱站长统计app网站
  • 济南设计网站的公司黄冈网站建设收费
  • 极速时时彩网站建设站长网站查询工具
  • 网站如何做微信支付宝支付公司网站费用
  • 南京网站制作目前推广平台都有哪些
  • 网页网站制作培训班郑州网站推广培训
  • 运城网站建设设计价格b2b平台排名
  • wordpress页面链接404错误网站优化关键词排名