我這里只介紹采用ReadDirectoryChangesW對(duì)文件目錄實(shí)施監(jiān)控
創(chuàng)新互聯(lián)公司從2013年開(kāi)始,是專(zhuān)業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站設(shè)計(jì)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元潁州做網(wǎng)站,已為上家服務(wù),為潁州各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話(huà):028-86922220
關(guān)鍵代碼
CfgdsgDlg * dlg = (CfgdsgDlg*)lparam; HANDLE hDir; char notify[1024]; DWORD cbBytes,i; char AnsiChar[3]; wchar_t UnicodeChar[2]; CString path; FILE_NOTIFY_INFORMATION *pnotify=(FILE_NOTIFY_INFORMATION *)notify; FILE_NOTIFY_INFORMATION *tmp; GetCurrentDirectory(MAX_PATH,path.GetBuffer(MAX_PATH+1)); hDir = CreateFile( path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (hDir == INVALID_HANDLE_VALUE) { dlg->m_edit.ReplaceSel("hDir:INVALID_HANDLE_VALUE\r\n"); return 0; } while (TRUE) { if(ReadDirectoryChangesW(hDir, ¬ify, sizeof(notify), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME| FILE_NOTIFY_CHANGE_LAST_WRITE, &cbBytes, NULL, NULL)) { tmp = pnotify; switch(tmp->Action) { case FILE_ACTION_ADDED: dlg->m_edit.ReplaceSel("Directory/File added (添加文件)- \r\n"); break; case FILE_ACTION_REMOVED: dlg->m_edit.ReplaceSel("Directory/File removed (刪除文件)- \r\n"); break; case FILE_ACTION_MODIFIED: dlg->m_edit.ReplaceSel("Directory/File modified (修改文件內(nèi)容)- \r\n"); break; case FILE_ACTION_RENAMED_OLD_NAME: dlg->m_edit.ReplaceSel("Directory/File old name (修改文件名字)- \r\n"); break; case FILE_ACTION_RENAMED_NEW_NAME: dlg->m_edit.ReplaceSel("Directory/File new name - \r\n"); break; default: break; } } }
FILE_NOTIFY_INFORMATION //可以確定是那個(gè)文件進(jìn)行的修改
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;//動(dòng)作
DWORD FileNameLength;//文件名字的長(zhǎng)度
WCHAR FileName[1];//文件名字
} FILE_NOTIFY_INFORMATION,
*PFILE_NOTIFY_INFORMATION;
ReadDirectoryChangesW 返回類(lèi)型(見(jiàn)MSDN)
Value | Meaning |
---|---|
FILE_ACTION_ADDED |
The file was added to the directory. |
FILE_ACTION_REMOVED |
The file was removed from the directory. |
FILE_ACTION_MODIFIED |
The file was modified. This can be a change in the time stamp or attributes. |
FILE_ACTION_RENAMED_OLD_NAME |
The file was renamed and this is the old name. |
FILE_ACTION_RENAMED_NEW_NAME |
The file was renamed and this is the new name. |
效果如下:
不足的地方:
只能檢測(cè)到指定目錄和下一級(jí)目錄,超過(guò)目錄級(jí)數(shù),該函數(shù)檢測(cè)不到。
ReadDirectoryChangesW 監(jiān)控文件夾 (一個(gè)簡(jiǎn)單的監(jiān)控示例程序)
.h文件
// .h文件 #pragma once typedef void (*PFN_NotifyAction)(DWORD dwAction, LPWSTR szFile, DWORD dwLength); class CDirectoryWatch { public: CDirectoryWatch(void); virtual ~CDirectoryWatch(void); public: BOOL StartDirectoryWatch(LPCTSTR lpszDirectory, PFN_NotifyAction pFn_NotifyAction); BOOL StopDirectoryWatch(void); private: static UINT __cdecl ThreadProc(LPVOID lParam); static UINT __cdecl DirectoryWatch(LPVOID lParam); private: HANDLE m_hFile; CWinThread* m_pThread; TCHAR m_szDirectory[MAX_PATH]; };
.cpp文件
// .cpp文件 #include "StdAfx.h" #include "DirectoryWatch.h" #include <strsafe.h> typedef enum { MSG_STARTWATCH = (WM_USER + 0x11), MSG_STOPWATCH, MSG_EXITTHREAD }; #define MAX_BUFFER_SIZE (1024) typedef struct _tagWATCHPARAMETERS { _tagWATCHPARAMETERS() { hFile = INVALID_HANDLE_VALUE; hEvent = NULL; memset(&ol, 0, sizeof(OVERLAPPED)); pBuffer = NULL; dwBufferSize = 0; bExit = FALSE; pFn_NotifyAction = NULL; } HANDLE hFile; HANDLE hEvent; OVERLAPPED ol; BYTE* pBuffer; DWORD dwBufferSize; BOOL bExit; PFN_NotifyAction pFn_NotifyAction; }WATCH_PARAMETERS, *PWATCH_PARAMETERS; CDirectoryWatch::CDirectoryWatch() : m_hFile(INVALID_HANDLE_VALUE), m_pThread(NULL) { memset(m_szDirectory, 0, sizeof(m_szDirectory)); m_pThread = AfxBeginThread(ThreadProc, NULL, 0, CREATE_SUSPENDED, 0, NULL); if(NULL == m_pThread) { TRACE("Error Code : %d\n", GetLastError()); return ; } m_pThread->m_bAutoDelete = FALSE; m_pThread->ResumeThread(); } CDirectoryWatch::~CDirectoryWatch() { if(INVALID_HANDLE_VALUE != m_hFile) { CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; } if((NULL != m_pThread) && (NULL != m_pThread->m_hThread)) { m_pThread->PostThreadMessage(MSG_EXITTHREAD, 0, 0); WaitForSingleObject(m_pThread->m_hThread, INFINITE); delete m_pThread; m_pThread = NULL; } } BOOL CDirectoryWatch::StartDirectoryWatch(LPCTSTR lpszDirectory, PFN_NotifyAction pFn_NotifyAction) { if(NULL == m_pThread) { return FALSE; } if(NULL == lpszDirectory) { return FALSE; } if(NULL == pFn_NotifyAction) { return FALSE; } if(!PathFileExists(lpszDirectory)) { TRACE("Error Code : %d\n", GetLastError()); return FALSE; } if(!PathIsDirectory(lpszDirectory)) { TRACE("Error Code : %d\n", GetLastError()); return FALSE; } if(0 == _tcslen(m_szDirectory)) { StringCchPrintf(m_szDirectory, _countof(m_szDirectory), _T("%s"), lpszDirectory); } else if(CSTR_EQUAL != CompareStringOrdinal(m_szDirectory, -1, lpszDirectory, -1, TRUE)) { TRACE("Not Change Directory.\n"); return FALSE; } if(INVALID_HANDLE_VALUE == m_hFile) { m_hFile = CreateFile(lpszDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if(INVALID_HANDLE_VALUE == m_hFile) { TRACE("Error Code : %d\n", GetLastError()); return FALSE; } } return m_pThread->PostThreadMessage(MSG_STARTWATCH, (WPARAM)m_hFile, (LPARAM)pFn_NotifyAction); } BOOL CDirectoryWatch::StopDirectoryWatch() { if(NULL != m_pThread) { return m_pThread->PostThreadMessage(MSG_STOPWATCH, 0, 0); } return FALSE; } UINT __cdecl CDirectoryWatch::DirectoryWatch(LPVOID lParam) { WATCH_PARAMETERS* pParam = (WATCH_PARAMETERS*)lParam; if(NULL == pParam) { return 0; } HANDLE& hFile = pParam->hFile; BYTE* pBuffer = pParam->pBuffer; DWORD dwBufferSize = pParam->dwBufferSize; OVERLAPPED& ol = pParam->ol; HANDLE& hEvent = pParam->hEvent; BOOL& bExit = pParam->bExit; PFN_NotifyAction pFn_NotifyAction = pParam->pFn_NotifyAction; DWORD dwBytesReturn = 0; DWORD dwRet = WAIT_FAILED; DWORD dwOffSet = 0; TCHAR szFile[MAX_PATH] = {0}; while(TRUE) { if(WAIT_OBJECT_0 != WaitForSingleObject(hEvent, INFINITE)) { TRACE("Error Code : %d\n", GetLastError()); break; } if(bExit) { break; } if(!ReadDirectoryChangesW(hFile, pBuffer, dwBufferSize, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY, &dwBytesReturn, &ol, NULL)) { TRACE("Error Code : %d\n", GetLastError()); break; } if(!GetOverlappedResult(hFile, &ol, &dwBytesReturn, TRUE)) { TRACE("Error Code : %d\n", GetLastError()); break; } FILE_NOTIFY_INFORMATION* pFileNotify = (FILE_NOTIFY_INFORMATION*)pBuffer; do { if(pFn_NotifyAction && (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 0))) { pFn_NotifyAction(pFileNotify->Action, pFileNotify->FileName, (pFileNotify->FileNameLength) / sizeof(WCHAR)); } dwOffSet = pFileNotify->NextEntryOffset; pFileNotify = (FILE_NOTIFY_INFORMATION*)((BYTE*)pFileNotify + dwOffSet); } while (dwOffSet); } TRACE0("DirectoryWatch Thread Exit ... \n"); return 0; } UINT __cdecl CDirectoryWatch::ThreadProc(LPVOID lParam) { WATCH_PARAMETERS* pParam = new WATCH_PARAMETERS; if(NULL == pParam) { goto __CLEANUP__; } BYTE* pBuffer = new BYTE[MAX_BUFFER_SIZE]; if(NULL == pBuffer) { goto __CLEANUP__; } memset(pBuffer, 0, MAX_BUFFER_SIZE); pParam->pBuffer = pBuffer; pParam->dwBufferSize = MAX_BUFFER_SIZE; HANDLE hWatchEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if(NULL == hWatchEvent) { goto __CLEANUP__; } pParam->ol.hEvent = hWatchEvent; CWinThread* pThread = NULL; HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if(NULL == hEvent) { goto __CLEANUP__; } pParam->hEvent = hEvent; MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { switch(msg.message) { case MSG_STARTWATCH: { HANDLE hFile = (HANDLE)(msg.wParam); PFN_NotifyAction pFn_NotifyAction = (PFN_NotifyAction)(msg.lParam); if((INVALID_HANDLE_VALUE == hFile) && (NULL == pFn_NotifyAction)) { break; } if(NULL == pThread) { pParam->hFile = hFile; pParam->pFn_NotifyAction = pFn_NotifyAction; pThread = AfxBeginThread(DirectoryWatch, (LPVOID)pParam, 0, CREATE_SUSPENDED, NULL); if(NULL == pThread) { goto __CLEANUP__; } pThread->m_bAutoDelete = FALSE; pThread->ResumeThread(); } SetEvent(hEvent); } break; case MSG_STOPWATCH: { ResetEvent(hEvent); } break; case MSG_EXITTHREAD: { SetEvent(hEvent); pParam->bExit = FALSE; if((NULL != pThread) && (NULL != pThread->m_hThread)) { WaitForSingleObject(pThread->m_hThread, INFINITE); delete pThread; pThread = NULL; } goto __CLEANUP__; } default: break; } TranslateMessage(&msg); DispatchMessage(&msg); } __CLEANUP__: if(NULL != hWatchEvent) { CloseHandle(hWatchEvent); hWatchEvent = NULL; } if(NULL != pBuffer) { delete[] pBuffer; pBuffer = NULL; } if(NULL != pParam) { delete pParam; pParam = NULL; } TRACE0("ThreadProc Thread Exit ...\n"); return 0; }
測(cè)試代碼
// 測(cè)試代碼 #include "stdafx.h" #include "DirectoryWatch.h" void NotifyAction(DWORD dwAction, LPWSTR szFile, DWORD dwLength) { switch(dwAction) { case FILE_ACTION_ADDED: wprintf(L"FILE_ACTION_ADDED: \n\t"); break; case FILE_ACTION_REMOVED: wprintf(L"FILE_ACTION_REMOVED: \n\t"); break; case FILE_ACTION_MODIFIED: wprintf(L"FILE_ACTION_MODIFIED: \n\t"); break; case FILE_ACTION_RENAMED_OLD_NAME: wprintf(L"FILE_ACTION_RENAMED_OLD_NAME: \n\t"); break; case FILE_ACTION_RENAMED_NEW_NAME: wprintf(L"FILE_ACTION_RENAMED_NEW_NAME: \n\t"); break; default: break; } WCHAR szPath[MAX_PATH] = {0}; wmemcpy(szPath, szFile, min(dwLength, MAX_PATH)); wprintf(L"%s\n", szPath); } int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { CDirectoryWatch watch; wprintf(L"Start Directory Watch ...\n"); watch.StartDirectoryWatch(_T("F:\\11"), NotifyAction); Sleep(30 * 1000); watch.StopDirectoryWatch(); wprintf(L"Stop Directory Watch ...\n"); Sleep(10 * 1000); wprintf(L"Start Directory Watch ...\n"); watch.StartDirectoryWatch(_T("F:\\11"), NotifyAction); Sleep(30 * 1000); watch.StopDirectoryWatch(); wprintf(L"Stop Directory Watch ...\n"); Sleep(30 * 1000); wprintf(L"Process Exit ...\n"); return 0; }
效果如下圖所示:
使用ReadDirectoryChangesW API監(jiān)控文件系統(tǒng)的改變
在C++中若想要監(jiān)控檔案系統(tǒng)改變有很多方法,可以用FindFirstChangeNotification取得檔案變更、或是Hook底層的API等方法來(lái)實(shí)現(xiàn),這邊使用ReadDirectoryChangesW API來(lái)實(shí)現(xiàn),該API使用前必須先加入Kernel32.lib。
并加入Windows.h的標(biāo)頭檔
#include "Windows.h"
這些步驟做完后在程式中就可以看到ReadDirectoryChangesW API了,其函式原型如下:
BOOL WINAPI ReadDirectoryChangesW( __in HANDLE hDirectory, __out LPVOID lpBuffer, __in DWORD nBufferLength, __in BOOL bWatchSubtree, __in DWORD dwNotifyFilter, __out_opt LPDWORD lpBytesReturned, __inout_opt LPOVERLAPPED lpOverlapped, __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
該API必須帶入八個(gè)參數(shù),hDirectory帶入的是要監(jiān)控的目錄Handle、lpBuffer帶入的是用來(lái)回傳變動(dòng)資料的空間、nBufferLength是lpBuffer空間的大小、bWatchSubtree是指定是否偵測(cè)子目錄、dwNotifyFilter是指定監(jiān)控的目錄有哪些動(dòng)作時(shí)需要通知、lpBytesReturned是用來(lái)回傳變動(dòng)資料內(nèi)含的長(zhǎng)度、lpOverlapped可用來(lái)在非同步環(huán)境下使用重疊IO用、lpCompletionRoutine則是當(dāng)監(jiān)控完成或取消時(shí)所呼叫的回調(diào)函式。
其中dwNotifyFilter的值可設(shè)定的有FILE_NOTIFY_CHANGE_FILE_NAME、FILE_NOTIFY_CHANGE_DIR_NAME、FILE_NOTIFY_CHANGE_ATTRIBUTES、FILE_NOTIFY_CHANGE_SIZE、FILE_NOTIFY_CHANGE_LAST_WRITE、FILE_NOTIFY_CHANGE_LAST_ACCESS、FILE_NOTIFY_CHANGE_CREATION、與FILE_NOTIFY_CHANGE_SECURITY,詳細(xì)所代表的意義可參閱ReadDirectoryChangesW function
了解了函式原型后,就可以開(kāi)始進(jìn)入實(shí)際的使用。剛有提到說(shuō)在ReadDirectoryChangesW API函式必須要帶入的第一個(gè)參數(shù)是要監(jiān)控的目錄Handle,所以我們必須透過(guò)CreateFile API取得要監(jiān)控的目錄Handle,像是下面這樣:
HANDLE hDirectoryHandle = NULL; hDirectoryHandle = ::CreateFileA( file, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if(hDirectoryHandle == INVALID_HANDLE_VALUE) return;
取得監(jiān)控的目錄Handle后,將其帶入ReadDirectoryChangesw API,順帶帶入像是回傳變動(dòng)資料的Buffer空間、與要監(jiān)控的變動(dòng)類(lèi)型等必要參數(shù)。像是下面這樣:
int nBufferSize = 1024; char* buffer = new char[nBufferSize]; DWORD dwBytes = 0; memset(buffer, 0, nBufferSize); if(!::ReadDirectoryChangesW( hDirectoryHandle, buffer, nBufferSize, bIncludeSubdirectories, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytes, NULL, NULL) || GetLastError() == ERROR_INVALID_HANDLE) { break; } if(!dwBytes) { printf("Buffer overflow~~\r\n"); }
這邊需注意到的是,若是變動(dòng)的資料太多,提供的存儲(chǔ)空間不足以存放時(shí),回傳的變動(dòng)資料長(zhǎng)度會(huì)是0,此時(shí)所有變動(dòng)資料都會(huì)丟失。這樣的情況多半只會(huì)出在一瞬間大量的變動(dòng),可以增大存儲(chǔ)空間或是減少監(jiān)控的變動(dòng)類(lèi)型,以減少回傳的資料量,避免溢位的發(fā)生。
若是運(yùn)行沒(méi)發(fā)生問(wèn)題,變動(dòng)的資料會(huì)存放在當(dāng)初塞進(jìn)去的存儲(chǔ)空間,該空間的資料其實(shí)是FILE_NOTIFY_INFORMATION structure的型態(tài)存在,因此我們可將存儲(chǔ)空間的資料轉(zhuǎn)換成PFILE_NOTIFY_INFORMATION。裡面的Action是我們所關(guān)注的變動(dòng)類(lèi)型,F(xiàn)ileName是變動(dòng)的檔案名稱(chēng),檔案名稱(chēng)的部分是沒(méi)有結(jié)尾符號(hào)的,必須要搭配FileNameLength去截取。另外變動(dòng)的資料有時(shí)候不止一筆,因此我們必須在這邊用迴圈搭配N(xiāo)extEntryOffset去重覆運(yùn)行處理流程,處理所有變動(dòng)的資料。
PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; DWORD cbOffset = 0; do { switch (record->Action) { case FILE_ACTION_ADDED: printf("FILE_ACTION_ADDED:"); break; case FILE_ACTION_REMOVED: printf("FILE_ACTION_REMOVED:"); break; case FILE_ACTION_MODIFIED: printf("FILE_ACTION_MODIFIED:"); break; case FILE_ACTION_RENAMED_OLD_NAME: printf("FILE_ACTION_RENAMED_OLD_NAME:"); break; case FILE_ACTION_RENAMED_NEW_NAME: printf("FILE_ACTION_RENAMED_NEW_NAME:"); break; default: break; } char fileBuffer[512]; WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); printf(fileBuffer); printf("\r\n"); cbOffset = record->NextEntryOffset; record = (PFILE_NOTIFY_INFORMATION)((LPBYTE) record + cbOffset); }while(cbOffset);
這邊示范一個(gè)簡(jiǎn)易的使用范例,實(shí)際使用時(shí)最好還是搭配執(zhí)行緒處理:
// ConsoleApplication10.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "Windows.h" void MonitorDir(char* file, bool bIncludeSubdirectories = false) { int nBufferSize = 1024; char* buffer = new char[nBufferSize]; HANDLE hDirectoryHandle = NULL; hDirectoryHandle = ::CreateFileA( file, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if(hDirectoryHandle == INVALID_HANDLE_VALUE) return; while(1) { DWORD dwBytes = 0; memset(buffer, 0, nBufferSize); if(!::ReadDirectoryChangesW( hDirectoryHandle, buffer, nBufferSize, bIncludeSubdirectories, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytes, NULL, NULL) || GetLastError() == ERROR_INVALID_HANDLE) { break; } if(!dwBytes) { printf("Buffer overflow~~\r\n"); } PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; DWORD cbOffset = 0; do { switch (record->Action) { case FILE_ACTION_ADDED: printf("FILE_ACTION_ADDED:"); break; case FILE_ACTION_REMOVED: printf("FILE_ACTION_REMOVED:"); break; case FILE_ACTION_MODIFIED: printf("FILE_ACTION_MODIFIED:"); break; case FILE_ACTION_RENAMED_OLD_NAME: printf("FILE_ACTION_RENAMED_OLD_NAME:"); break; case FILE_ACTION_RENAMED_NEW_NAME: printf("FILE_ACTION_RENAMED_NEW_NAME:"); break; default: break; } char fileBuffer[512]; WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); printf(fileBuffer); printf("\r\n"); cbOffset = record->NextEntryOffset; record = (PFILE_NOTIFY_INFORMATION)((LPBYTE) record + cbOffset); }while(cbOffset); } delete buffer; if(hDirectoryHandle) CloseHandle(hDirectoryHandle); } int _tmain(int argc, _TCHAR* argv[]) { MonitorDir("C:\\Users\\larry\\Desktop\\新增資料夾");
運(yùn)行后去對(duì)監(jiān)控的目錄操作~可得到類(lèi)似如下的結(jié)果:
分享文章:VC++文件監(jiān)控之ReadDirectoryChangesW
網(wǎng)址分享:http://muchs.cn/article22/jchijc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、小程序開(kāi)發(fā)、網(wǎng)站內(nèi)鏈、網(wǎng)站設(shè)計(jì)、網(wǎng)站排名、虛擬主機(jī)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)