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

cocos2d-x-触屏(Touch)事件详解

1.首先来了解一下相关的几个类、处理触屏事件时操作和执行的流程

CCTouch:它封装了触摸点,可以通过locationInView函数返回一个CCPoint。

CCTouchDelegate:它是触摸事件委托,就是系统捕捉到触摸事件后交由它或者它的子类处理,所以我们在处理触屏事件时,必须得继承它。它封装了下面这些处理触屏事件的函数:

virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent);
virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent);
virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent);
virtual void ccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent);virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);

ccTouchesCancelled和ccTouchCancelled函数很少用,在接到系统中断通知,需要取消触摸事件的时候才会调用此方法。

如:应用长时间无响应、当前view从window上移除、触摸的时候来电话了等。

CCTargetedTouchDelegateCCStandardTouchDelegate是CCTouchDelegate的子类,类结构图如下:



CCTouchDispatcher:实现触摸事件分发,它封装了下面这两个函数,

可以把CCStandardTouchDelegate和CCTargetedTouchDelegate添加到分发列表中:

void addStandardDelegate(CCTouchDelegate *pDelegate, int nPriority);
void addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches);

CCTouchHandler:封装了CCTouchDelegate和其对应的优先级,优先级越高,分发的时候越容易获得事件处理权,CCStandardTouchHandlerCCTargetedTouchHandler是它的子类。

下面分析一下触屏事件处理和执行流程:
用户自定义类继承CCTouchDelegate,重写触屏事件处理函数和registerWithTouchDispatcher函数,在init或者onEnter函数中调用registerWithTouchDispatcher函数,如:

void GameLayer::registerWithTouchDispatcher()
{cocos2d::CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true);
}

把相应的CCTouchDelegate添加到CCTouchDispatcher的分发列表中。addTargetedDelegate函数会创建CCTouchDelegate对应

的CCTouchHandler对象并添加到CCArray m_pTargetedHandlers中,看源码:

void CCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches)
{   CCTouchHandler *pHandler = CCTargetedTouchHandler::handlerWithDelegate(pDelegate, nPriority, bSwallowsTouches);if (! m_bLocked){forceAddHandler(pHandler, m_pTargetedHandlers);}else{/**....*/}
}void CCTouchDispatcher::forceAddHandler(CCTouchHandler *pHandler, CCMutableArray *pArray)
{unsigned int u = 0;CCMutableArray::CCMutableArrayIterator iter;for (iter = pArray->begin(); iter != pArray->end(); ++iter){CCTouchHandler *h = *iter;if (h){if (h->getPriority() < pHandler->getPriority()){++u;}if (h->getDelegate() == pHandler->getDelegate()){CCAssert(0, "");return;}}}pArray->insertObjectAtIndex(pHandler, u);
}

注意forceAddHandler()函数中,pHandler是被添加的对象:pHandler->getPriority()的值越小u的值就越小,

因此插入到目标容器中的位置也就越靠前,说明优先级的值越小优先级反而越高,

也就能先响应事件(CCMenu的默认值是-128)

前面事件分发时就是从m_pTargetedHandlers中取出CCXXXTouchHandler,然后调用handler对应的delegate的:

pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);执行的是CCTouchDispatcher的touches函数.

//
// dispatch events
//
void CCTouchDispatcher::touches(CCSet *pTouches, CCEvent *pEvent, unsigned int uIndex)
{CCAssert(uIndex >= 0 && uIndex < 4, "");CCSet *pMutableTouches;m_bLocked = true;// optimization to prevent a mutable copy when it is not necessaryunsigned int uTargetedHandlersCount = m_pTargetedHandlers->count();unsigned int uStandardHandlersCount = m_pStandardHandlers->count();bool bNeedsMutableSet = (uTargetedHandlersCount && uStandardHandlersCount);pMutableTouches = (bNeedsMutableSet ? pTouches->mutableCopy() : pTouches);struct ccTouchHandlerHelperData sHelper = m_sHandlerHelperData[uIndex];//// process the target handlers 1st//if (uTargetedHandlersCount > 0){CCTouch *pTouch;CCSetIterator setIter;for (setIter = pTouches->begin(); setIter != pTouches->end(); ++setIter){pTouch = (CCTouch *)(*setIter);CCTargetedTouchHandler *pHandler = NULL;CCObject* pObj = NULL;CCARRAY_FOREACH(m_pTargetedHandlers, pObj){pHandler = (CCTargetedTouchHandler *)(pObj);if (! pHandler){break;}bool bClaimed = false;if (uIndex == CCTOUCHBEGAN){bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);if (bClaimed){pHandler->getClaimedTouches()->addObject(pTouch);}} elseif (pHandler->getClaimedTouches()->containsObject(pTouch)){// moved ended canceledbClaimed = true;switch (sHelper.m_type){case CCTOUCHMOVED:pHandler->getDelegate()->ccTouchMoved(pTouch, pEvent);break;case CCTOUCHENDED:pHandler->getDelegate()->ccTouchEnded(pTouch, pEvent);pHandler->getClaimedTouches()->removeObject(pTouch);break;case CCTOUCHCANCELLED:pHandler->getDelegate()->ccTouchCancelled(pTouch, pEvent);pHandler->getClaimedTouches()->removeObject(pTouch);break;}}if (bClaimed && pHandler->isSwallowsTouches()){if (bNeedsMutableSet){pMutableTouches->removeObject(pTouch);}break;}}}}//// process standard handlers 2nd//if (uStandardHandlersCount > 0 && pMutableTouches->count() > 0){CCStandardTouchHandler *pHandler = NULL;CCObject* pObj = NULL;CCARRAY_FOREACH(m_pStandardHandlers, pObj){pHandler = (CCStandardTouchHandler*)(pObj);if (! pHandler){break;}switch (sHelper.m_type){case CCTOUCHBEGAN:pHandler->getDelegate()->ccTouchesBegan(pMutableTouches, pEvent);break;case CCTOUCHMOVED:pHandler->getDelegate()->ccTouchesMoved(pMutableTouches, pEvent);break;case CCTOUCHENDED:pHandler->getDelegate()->ccTouchesEnded(pMutableTouches, pEvent);break;case CCTOUCHCANCELLED:pHandler->getDelegate()->ccTouchesCancelled(pMutableTouches, pEvent);break;}}}if (bNeedsMutableSet){pMutableTouches->release();}//// Optimization. To prevent a [handlers copy] which is expensive// the add/removes/quit is done after the iterations//m_bLocked = false;if (m_bToRemove){m_bToRemove = false;for (unsigned int i = 0; i < m_pHandlersToRemove->num; ++i){forceRemoveDelegate((CCTouchDelegate*)m_pHandlersToRemove->arr[i]);}ccCArrayRemoveAllValues(m_pHandlersToRemove);}if (m_bToAdd){m_bToAdd = false;CCTouchHandler* pHandler = NULL;CCObject* pObj = NULL;CCARRAY_FOREACH(m_pHandlersToAdd, pObj){pHandler = (CCTouchHandler*)pObj;if (! pHandler){break;}if (dynamic_cast<CCTargetedTouchHandler*>(pHandler) != NULL){                forceAddHandler(pHandler, m_pTargetedHandlers);}else{forceAddHandler(pHandler, m_pStandardHandlers);}}m_pHandlersToAdd->removeAllObjects();    }if (m_bToQuit){m_bToQuit = false;forceRemoveAllDelegates();}
}

该函数首先会先处理targeted 再处理standard,所以CCTargetedTouchDelegate比

CCStandardTouchDelegate优先级高。

那什么时候触发执行touches函数呢?CCTouchDispatcher继承了EGLTouchDelegate类,EGLTouchDelegate类源码:

class CC_DLL EGLTouchDelegate
{
public:virtual void touchesBegan(CCSet* touches, CCEvent* pEvent) = 0;virtual void touchesMoved(CCSet* touches, CCEvent* pEvent) = 0;virtual void touchesEnded(CCSet* touches, CCEvent* pEvent) = 0;virtual void touchesCancelled(CCSet* touches, CCEvent* pEvent) = 0;virtual ~EGLTouchDelegate() {}
};

CCTouchDispatcher中实现了这四个函数,正是在这四个函数中调用了touches函数:

void CCTouchDispatcher::touchesBegan(CCSet *touches, CCEvent *pEvent)
{if (m_bDispatchEvents){this->touches(touches, pEvent, CCTOUCHBEGAN);}
}
/**其他三个方法类似 **/

这几个触屏处理函数是由具体平台底层调用的,在AppDelegate.cpp中有这段代码:

CCDirector *pDirector = CCDirector::sharedDirector();pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());

继续跟进setOpenGLView函数,发现了这段代码:

CCTouchDispatcher *pTouchDispatcher = CCTouchDispatcher::sharedDispatcher();
m_pobOpenGLView->setTouchDelegate(pTouchDispatcher);
pTouchDispatcher->setDispatchEvents(true);

调用了具体平台下的CCEGLView类中的setTouchDelegate函数。由于是在windows平台下,

所以CCEGLView此时对应CCEGLView_win32.h文件的CCEGLView类,对应的setTouchDelegate函数为:

void    setTouchDelegate(EGLTouchDelegate * pDelegate);

系统最终通过CCEGLView类的WindowProc函数处理鼠标在Windows窗口的DOWN、MOVE、UP事件,

通过pDelegate分别调用touchesBegan、touchesMoved、touchesEnded函数。

LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{BOOL bProcessed = FALSE;switch (message){case WM_LBUTTONDOWN:
#if(_MSC_VER >= 1600)// Don't process message generated by Windows Touchif (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;
#endif /* #if(_MSC_VER >= 1600) */if (m_pDelegate && MK_LBUTTON == wParam){POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};CCPoint pt(point.x, point.y);pt.x /= m_fFrameZoomFactor;pt.y /= m_fFrameZoomFactor;CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp)){m_bCaptured = true;SetCapture(m_hWnd);int id = 0;handleTouchesBegin(1, &id, &pt.x, &pt.y);}}break;case WM_MOUSEMOVE:
#if(_MSC_VER >= 1600)// Don't process message generated by Windows Touchif (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;
#endif /* #if(_MSC_VER >= 1600) */if (MK_LBUTTON == wParam && m_bCaptured){POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};CCPoint pt(point.x, point.y);int id = 0;pt.x /= m_fFrameZoomFactor;pt.y /= m_fFrameZoomFactor;handleTouchesMove(1, &id, &pt.x, &pt.y);}break;case WM_LBUTTONUP:
#if(_MSC_VER >= 1600)// Don't process message generated by Windows Touchif (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;
#endif /* #if(_MSC_VER >= 1600) */if (m_bCaptured){POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};CCPoint pt(point.x, point.y);int id = 0;pt.x /= m_fFrameZoomFactor;pt.y /= m_fFrameZoomFactor;handleTouchesEnd(1, &id, &pt.x, &pt.y);ReleaseCapture();m_bCaptured = false;}break;
#if(_MSC_VER >= 1600)case WM_TOUCH:{BOOL bHandled = FALSE;UINT cInputs = LOWORD(wParam);PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];if (pInputs){if (s_pfGetTouchInputInfoFunction((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))){for (UINT i=0; i < cInputs; i++){TOUCHINPUT ti = pInputs[i];POINT input;input.x = TOUCH_COORD_TO_PIXEL(ti.x);input.y = TOUCH_COORD_TO_PIXEL(ti.y);ScreenToClient(m_hWnd, &input);CCPoint pt(input.x, input.y);CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp)){pt.x /= m_fFrameZoomFactor;pt.y /= m_fFrameZoomFactor;if (ti.dwFlags & TOUCHEVENTF_DOWN)handleTouchesBegin(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y);else if (ti.dwFlags & TOUCHEVENTF_MOVE)handleTouchesMove(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y);else if (ti.dwFlags & TOUCHEVENTF_UP)handleTouchesEnd(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y);}}bHandled = TRUE;}delete [] pInputs;}if (bHandled){s_pfCloseTouchInputHandleFunction((HTOUCHINPUT)lParam);}}break;
#endif /* #if(_MSC_VER >= 1600) */case WM_SIZE:switch (wParam){case SIZE_RESTORED:CCApplication::sharedApplication()->applicationWillEnterForeground();break;case SIZE_MINIMIZED:CCApplication::sharedApplication()->applicationDidEnterBackground();break;}break;case WM_KEYDOWN:if (wParam == VK_F1 || wParam == VK_F2){CCDirector* pDirector = CCDirector::sharedDirector();if (GetKeyState(VK_LSHIFT) < 0 ||  GetKeyState(VK_RSHIFT) < 0 || GetKeyState(VK_SHIFT) < 0)pDirector->getKeypadDispatcher()->dispatchKeypadMSG(wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked);}else if (wParam == VK_ESCAPE){CCDirector::sharedDirector()->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked);}if ( m_lpfnAccelerometerKeyHook!=NULL ){(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );}break;case WM_KEYUP:if ( m_lpfnAccelerometerKeyHook!=NULL ){(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );}break;case WM_CHAR:{if (wParam < 0x20){if (VK_BACK == wParam){CCIMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();}else if (VK_RETURN == wParam){CCIMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1);}else if (VK_TAB == wParam){// tab input}else if (VK_ESCAPE == wParam){// ESC input//CCDirector::sharedDirector()->end();}}else if (wParam < 128){// ascii charCCIMEDispatcher::sharedDispatcher()->dispatchInsertText((const char *)&wParam, 1);}else{char szUtf8[8] = {0};int nLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&wParam, 1, szUtf8, sizeof(szUtf8), NULL, NULL);CCIMEDispatcher::sharedDispatcher()->dispatchInsertText(szUtf8, nLen);}if ( m_lpfnAccelerometerKeyHook!=NULL ){(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );}}break;case WM_PAINT:PAINTSTRUCT ps;BeginPaint(m_hWnd, &ps);EndPaint(m_hWnd, &ps);break;case WM_CLOSE:CCDirector::sharedDirector()->end();break;case WM_DESTROY:destroyGL();PostQuitMessage(0);break;default:if (m_wndproc){m_wndproc(message, wParam, lParam, &bProcessed);if (bProcessed) break;}return DefWindowProc(m_hWnd, message, wParam, lParam);}if (m_wndproc && !bProcessed){m_wndproc(message, wParam, lParam, &bProcessed);}return 0;
}

现在应该明白了触屏操作相关函数的执行过程了,在其他平台下应该类似。

2.实现触屏事件处理

知道了原理之后,实现起来就很简单了:定义一个CCTouchDelegate(或者其子类CCTargetedTouchDelegate/CCStandardTouchDelegate),

然后重写那几个处理函数(began、move、end),并把定义好的CCTouchDelegate添加到分发列表中,在onExit函数中实现从分发列表中删除。

在平常的开发中,一般有两种方式:(1)继承CCLayer,在层中处理触屏函数。

(2)继承CCSprite和CCTouchDelegate(或者其子类)

上面两种方式,从原理上来说是一样的。

1. 下面是采用继承CCLayer的方式处理触屏事件。

(1)CCStandardTouchDelegate

添加CCStandardTouchDelegate是非常简单的,只需要重写触屏处理函数和调用setTouchEnabled(true)。主要代码如下:

//init函数中
this->setIsTouchEnabled(true);void GameLayer::ccTouchesBegan(CCSet* pTouches,CCEvent* pEvent)
{CCSetIterator it = pTouches->begin();CCTouch* touch = (CCTouch*)(*it);/** .... **/
}

这里为什么没有把CCStandardTouchDelegate添加进分发列表和从分发列表删除的操作呢,因为 函数 setTouchEnabled已经帮我们做了,看源码:

void CCLayer::setIsTouchEnabled(bool enabled)
{if (m_bIsTouchEnabled != enabled){m_bIsTouchEnabled = enabled;if (m_bIsRunning){if (enabled){this->registerWithTouchDispatcher();}else{// have problems?CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);}}}
}void CCLayer::registerWithTouchDispatcher()
{/** .... **/CCTouchDispatcher::sharedDispatcher()->addStandardDelegate(this,0);
}void CCLayer::onExit()
{if( m_bIsTouchEnabled ){CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);unregisterScriptTouchHandler();}CCNode::onExit();
}

(2) CCTargetedTouchDelegate
直接看cocos2d-x中的CCMenu(菜单)类,它是继承CCLayer的。部分源码如下:

class CC_DLL CCMenu : public CCLayer, public CCRGBAProtocol{/** .... */virtual void registerWithTouchDispatcher();/**@brief For phone event handle functions*/virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event);virtual void ccTouchEnded(CCTouch* touch, CCEvent* event);virtual void ccTouchCancelled(CCTouch *touch, CCEvent* event);virtual void ccTouchMoved(CCTouch* touch, CCEvent* event);/**@since v0.99.5override onExit*/virtual void onExit();/** .... */};
}//Menu - Events,在CCLayer的onEnter中被调用void CCMenu::registerWithTouchDispatcher(){CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, kCCMenuTouchPriority, true);}bool CCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event){/** .... */}void CCMenu::onExit(){/** .... */CCLayer::onExit();}

2.下面实现继承CCSprite的方式
定义一个Ball类继承CCSprite和CCTargetedTouchDelegate。源码如下:

class Ball : public CCSprite, public CCTargetedTouchDelegate
{
public:Ball(void);virtual ~Ball(void);virtual void onEnter();virtual void onExit();virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event);virtual void ccTouchMoved(CCTouch* touch, CCEvent* event);virtual void ccTouchEnded(CCTouch* touch, CCEvent* event);
/** .... */};void Ball::onEnter()
{CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true);CCSprite::onEnter();
}void Ball::onExit()
{CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);CCSprite::onExit();
}bool Ball::ccTouchBegan(CCTouch* touch, CCEvent* event)
{CCPoint touchPoint = touch->locationInView( touch->view() );touchPoint = CCDirector::sharedDirector()->convertToGL( touchPoint );    
/** .... */return true;
}

注意: virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)的返回值对触屏消息是有影响的。
如果返回false,表示不处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法,而交由后面接收触屏消息的对象处理;

如果返回true,表示会处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法。

请看CCTouchDispatcher.cpp的touches函数部分源码,它是用来分发事件的:

                bool bClaimed = false;if (uIndex == CCTOUCHBEGAN){bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);if (bClaimed){pHandler->getClaimedTouches()->addObject(pTouch);}} elseif (pHandler->getClaimedTouches()->containsObject(pTouch)){// moved ended canceledbClaimed = true;switch (sHelper.m_type){case CCTOUCHMOVED:pHandler->getDelegate()->ccTouchMoved(pTouch, pEvent);break;case CCTOUCHENDED:pHandler->getDelegate()->ccTouchEnded(pTouch, pEvent);pHandler->getClaimedTouches()->removeObject(pTouch);break;case CCTOUCHCANCELLED:pHandler->getDelegate()->ccTouchCancelled(pTouch, pEvent);pHandler->getClaimedTouches()->removeObject(pTouch);break;}}if (bClaimed && pHandler->isSwallowsTouches()){if (bNeedsMutableSet){pMutableTouches->removeObject(pTouch);}break;}


如果返回true,并且addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches),

bSwallowsTouches为true,则表示消耗掉此触屏消息,后面需要接收触屏消息的对象就接收不到触屏消息了

把该触摸对象CCTouch从数组pMutableTouches中移除了,并且跳出当前for循环,而CCStandardTouchHandler需要从

pMutableTouches取出触摸对象进行处理的,这样后面的CCTargetedTouchHandler和CCStandardTouchHandler就都处理不了。














http://www.xdnf.cn/news/800929.html

相关文章:

  • css代码添加背景图片常用代码
  • Maemo系统介绍
  • ASP.NET概述
  • 华表Cell应用 - 在IE中使用华表Cell插件 | #华表Cell #报表
  • 2012年1月凯立德地图普高清全分辨率懒人包P1750-D5616-2721J09(完美破解,已上路实测,永久下载地址)...
  • 从生日透视你的性格与优缺点
  • 算法整理五——分治
  • 英语九百句 English900(含录音下载)
  • C++ Json解析库CJsonObject的详细使用(跨平台无须编译成库)
  • 教你找电影
  • Microsoft Visual Studio 2010(vs2010) 中文版安装
  • 磊科linux无线网卡驱动安装步骤,怎么安装磊科nw336无线网卡驱动
  • 一星期总结:U盘量产与USB-CDROM制作及修改晨枫U盘维护V2.0完全攻略
  • WIFI_植入JS【转】
  • C语言编译器(C语言编程软件)完全攻略(第二十三部分:Turbo C 2.0下载地址和安装教程(图解))
  • 磊科全功能路由器上网行为管理配置指南 -- 路由器
  • 帮你找到99%的电子书,这46个免费电子书网站,你还不知道吗?
  • 说说QQ校友与校内网的优势
  • 【科普】黑客,骇客,红客,蓝客,它们有什么区别?
  • Linux系统镜像下载(centOS-7)教程
  • 【discuzx2】forum_index.php文件的分析
  • RATIONAL ROSE 2007详细安装教程(图文版)
  • 春晚小宫女唐奕霖 网友封为最美的年轻董事长
  • 上海前端求职招聘工作交流qq群
  • SnakeYAML序列化反序列化其经典反序列化漏洞利用链讲解(非常详细!!!)
  • 宏基4752g linux驱动下载,宏基4752g显卡驱动
  • 100多个优秀的互联网编程学习平台整理。
  • 2021-08-26小白笔记
  • Linux二进制ELF程序查找symbol过程分析
  • 终端里面常用的转义字符串