VC++(十五)網(wǎng)絡(luò)編程

  • 程序和進程
    程序是計算機指令的集合,它以文件的形式存儲在磁盤上。
    進程通常被定義為一個正在運行的程序的實例。是一個程序在其自身的地址空間中的一次執(zhí)行活動。
    一個程序可以對應(yīng)多個進程。

  • 進程是資源申請、調(diào)度和獨立運行的單位,因此,它使用系統(tǒng)中的運行資源。
    程序不能申請系統(tǒng)資源,不能被系統(tǒng)調(diào)度,也不能作為獨立運行的單位。因此它不占用系統(tǒng)的運行資源。
    真正完成代碼執(zhí)行的是線程,而進程只是線程的容器,或者說是線程的執(zhí)行環(huán)境。

  • 主線程,也就是執(zhí)行main函數(shù)或者WinMain函數(shù)的線程,可以把main函數(shù)或者WinMain函數(shù)看做是主線程的進入點函數(shù)。此后,主線程可以創(chuàng)建其他線程。
    系統(tǒng)賦予每個進程獨立的虛擬地址空間。32位進程,4GB。

磁盤緩存、虛擬內(nèi)存、頁面文件和物理內(nèi)存的關(guān)系
磁盤緩存分為讀緩存和寫緩存。讀緩存是指,操作系統(tǒng)為已讀取的文件數(shù)據(jù),在內(nèi)存較空閑的情況下留在內(nèi)存空間中(這個內(nèi)存空間被稱之為“內(nèi)存池”),當(dāng)下次軟件或用戶再次讀取同一文件時就不必重新從磁盤上讀取,從而提高速度。寫緩存實際上就是將要寫入磁盤的數(shù)據(jù)先保存于系統(tǒng)為寫緩存分配的內(nèi)存空間中,當(dāng)保存到內(nèi)存池中的數(shù)據(jù)達到一個程度時,便將數(shù)據(jù)保存到硬盤中。這樣可以減少實際的磁盤操作,有效的保護磁盤免于重復(fù)的讀寫操作而導(dǎo)致的損壞,也能減少寫入所需的時間。
虛擬內(nèi)存是用硬盤空間做內(nèi)存來彌補計算機RAM空間的缺乏。當(dāng)實際RAM滿時(實際上,在RAM滿之前),虛擬內(nèi)存就在硬盤上創(chuàng)建了。當(dāng)物理內(nèi)存用完后,虛擬內(nèi)存管理器選擇最近沒有用過的,低優(yōu)先級的內(nèi)存部分寫到交換文件上。這個過程對應(yīng)用是隱藏的,應(yīng)用把虛擬內(nèi)存和實際內(nèi)存看作是一樣的。虛擬內(nèi)存文件也就是頁面文件。

線程有兩個部分組成

  1. 線程的內(nèi)核對象。操作系統(tǒng)用它來對線程實施管理。關(guān)于線程的統(tǒng)計信息組成的一個小型數(shù)據(jù)結(jié)構(gòu)。
  2. 線程棧。它用于維護線程在執(zhí)行代碼時需要的所有函數(shù)參數(shù)和局部變量。
    新線程可以訪問進程的內(nèi)核對象的所有句柄、進程中的所有內(nèi)存和在這個相同的進程中的所有其他線程的堆棧。

進程、線程及內(nèi)核對象

內(nèi)核對象
每個內(nèi)核對象只是內(nèi)核分配的一個內(nèi)存塊,并且只能由該內(nèi)核訪問,這個內(nèi)存塊是一種數(shù)據(jù)結(jié)構(gòu),他的成員負責(zé)維護該對象的各種信息,如進程對象有一個進程ID、一個基本優(yōu)先級和一個退出代碼。

由于內(nèi)核對象的數(shù)據(jù)結(jié)構(gòu)只能被內(nèi)核訪問,so應(yīng)用程序是無法在內(nèi)存中找到這些數(shù)據(jù)結(jié)構(gòu)的并直接改變其內(nèi)容的。Windows提出這個限制為了確保內(nèi)核對象結(jié)構(gòu)保持狀態(tài)的一致,也是為了保證Microsoft能夠在不破壞應(yīng)用程序的情況下在這些內(nèi)核對象的結(jié)構(gòu)中添加、刪除、修改這些數(shù)據(jù)成員;

內(nèi)核對象使用引用計數(shù)
內(nèi)核對象由內(nèi)核所有,而不是進程所有,舉例說明,在做單進程限制時,我們一般會CreateMutex來創(chuàng)建一個命名的Mutex,再另外一個進程中再來創(chuàng)建或者打開相同命名的Mutex來檢驗有相同進程被創(chuàng)建。也可以這么說進程調(diào)用一個創(chuàng)建的內(nèi)核對象函數(shù),進程終止了但是內(nèi)核對象不一定被撤銷;

每個線程都有自己的一組cpu寄存器和堆棧,每個進程至少有一個線程,來執(zhí)行進程的地址空間中的代碼。如果沒有線程來執(zhí)行進程地址空間中的代碼,那么這個進程就沒有存在的理由,系統(tǒng)會自動撤銷該進程;

進程的實例句柄
加載到進程地址空間的每個可執(zhí)行文件或者dll均被賦予了一個獨一無二的實例句柄??蓤?zhí)行的文件的實例作為(w)WinMain的第一個參數(shù)hinstExe來傳遞;

  • WinMain的hinstExe參數(shù)的實際值是系統(tǒng)將可執(zhí)行文件的映像加載到進程的地址空間時使用的基本地址空間。例如,如果系統(tǒng)打開了可執(zhí)行文件并將它的內(nèi)容加載到地址的0x00400000,那么WinMain的hinstExe的參數(shù)值就是0x00400000;

  • 可執(zhí)行文件的映像加載到的基地址是由連接程序決定的,不同的鏈接程序可以使用不同的默認基地址。VC++鏈接程序使用的默認基地址是0x00400000;只是運行在windows98時可執(zhí)行文件的映像可以加載到的最低地址。

  • 進程的前一個實例句柄
    C/C++運行期啟動代碼總是將NULL傳遞給WinMain的hinstExePrev參數(shù),該參數(shù)使用在16位windows中的,并且保留了WinMain的一個參數(shù),目的僅僅是為了能夠容易的轉(zhuǎn)用16位windows應(yīng)用程序,so絕不應(yīng)該在代碼中引用這個參數(shù)。

  • 線程有兩部分組成:
    一個是線程的內(nèi)核對象,操作系統(tǒng)用他來對線程實施管理。內(nèi)核對象也是系統(tǒng)用來存放線程統(tǒng)計信息的地方;
    一個是線程堆棧,用于維護線程在執(zhí)行代碼時需要的所有函數(shù)參數(shù)和局部變量;

進程是不活潑的,進程從不執(zhí)行任何東西,它只是線程的容器。線程總是在某個進程環(huán)境中創(chuàng)建的,而且它的整個壽命期都在該進程中。那么這意味著線程在他的進程地址空間中執(zhí)行代碼,并在進程的地址空間中對數(shù)據(jù)進行操作。so在單進程中,如果存在多個線程的運行,那么這些線程將共享單個地址空間。這些線程能夠執(zhí)行相同的代碼對相同數(shù)據(jù)進行操作。而且他們還能共享內(nèi)核對象句柄,因為句柄表依賴于每個進程而非每個線程存在的。

進程使用的系統(tǒng)資源要比線程對很多,原因是他需要更多的地址空間。為進程創(chuàng)建一個虛擬的地址空間需要系統(tǒng)很多的資源。系統(tǒng)中要保留大量的記錄,這需要占用大量的內(nèi)存。另外,由于exe和dll文件需要加載到一個地址空間,因此也需要文件資源,而線程使用的系統(tǒng)資源就小很多了,他只需要一個內(nèi)核對象和一個堆棧就ok,保留的記錄也很少,so只需要很少的內(nèi)存。

So能用線程解決的問題,要避免創(chuàng)建新進程來解決。

注:CreateThread函數(shù)是用來創(chuàng)建線程的windows函數(shù),如果你正在編寫C/C++代碼,絕不應(yīng)該調(diào)用CreateThread,相反,應(yīng)該使用VC++運行庫函數(shù)_beginthreadex。原因是,標準C/C++運行庫在最初設(shè)計的時候并沒有考慮到多線程的問題。若要使多線程C/C++程序能夠正常的運行起來,必須創(chuàng)建一個數(shù)據(jù)結(jié)構(gòu),并將它與使用的C/C++運行庫函數(shù)的每一個線程關(guān)聯(lián)起來,當(dāng)你調(diào)用C/C++運行庫時,這些函數(shù)必須知道查看調(diào)用線程的數(shù)據(jù)塊,這樣就不會對別的線程產(chǎn)生不良影響。那么系統(tǒng)是否知道在創(chuàng)建新線程時分配該數(shù)據(jù)塊呢?no,系統(tǒng)是無法知道你的應(yīng)用程序使用C/C++編寫的,自然不知道你調(diào)用的線程本身不安全,所以不要調(diào)用操作系統(tǒng)的CreateThread函數(shù),而需要調(diào)用_beginthreadex.

線程的運行時間
有時要計算線程執(zhí)行某個任務(wù)需要多長時間,很多時候我們會采用如下代碼:

//start time

DWORD starttime = GetTickCount();

//complex algorithm here;

//subtract start time from current time to get duration;

DWORD dwElapsedTime = GetTickCount() – dwStartTime;

這個代碼有個簡單的假設(shè):運行不會被中斷;但是在搶占式的windows操作系統(tǒng)是不可能存在的,so我們需要用另外一個方式來計算,該函數(shù)為GetThreadTime;

內(nèi)存映射文件

Windows提供了3種進行內(nèi)存管理的方法:

  • 虛擬內(nèi)存,最適合用來管理大型對象或結(jié)構(gòu)數(shù)組。

  • 內(nèi)存映射文件,最適合用來管理大型數(shù)據(jù)流(通常來自文件)以及在單個計算機上運行的多個進程之間共享數(shù)據(jù)。

  • 內(nèi)存堆棧,最適合用來管理大量的小對象。

這里將先講述一下關(guān)于內(nèi)存映射文件的使用情況。

與虛擬內(nèi)存一樣,內(nèi)存映射文件可以用來保留一個地址空間的區(qū)域,并將物理存儲器提交給該區(qū)域,他們之間的差別是物理存儲器來自一個已經(jīng)位于磁盤上的文件,而不是系統(tǒng)的頁文件。一旦該文件被映射,就可以訪問它,就像是整個文件已經(jīng)加載到內(nèi)存中一樣。

內(nèi)存映射文件的3個目的:

  • 系統(tǒng)使用內(nèi)存映射文件,以便快速加載和執(zhí)行exe和dll文件。

  • 可以使用內(nèi)存映射文件快速訪問磁盤上的數(shù)據(jù)文件,這樣使得你不必對文件執(zhí)行I/O操作,并且不必對文件內(nèi)容進行緩存。

  • 可以使用內(nèi)存映射文件來進行進程間的數(shù)據(jù)共享。

內(nèi)存映射文件的步驟:

若要使用內(nèi)存映射文件,必須執(zhí)行下列操作步驟:

  1. 創(chuàng)建或打開一個文件內(nèi)核對象,該對象用于標識磁盤上你想用作內(nèi)存映射文件的文件。(CreateFile)

  2. 創(chuàng)建一個文件映射內(nèi)核對象,告訴系統(tǒng)該文件的大小和你打算如何訪問該文件。(CreateFileMapping)

  3. 讓系統(tǒng)將文件映射對象的全部或一部分映射到你的進程地址空間中。(MapViewOfFile)

當(dāng)完成對內(nèi)存映射文件的使用時,必須執(zhí)行下面這些步驟將它清除:

  1. 告訴系統(tǒng)從你的進程的地址空間中撤消文件映射內(nèi)核對象的映像。(UnmapViewOfFile)

  2. 關(guān)閉文件映射內(nèi)核對象和文件內(nèi)核對象。(CloseHandle)

CreateThread,該函數(shù)將創(chuàng)建一個線程。

很多程序在創(chuàng)建線程都這樣寫的:
ThreadHandle = CreateThread(NULL,0,.....);
CloseHandle(ThreadHandle );
1,線程和線程句柄(Handle)不是一個東西,線程句柄是一個內(nèi)核對象。我們可以通過句柄來操作線程,但是線程的生命周期和線程句柄的生命周期不一樣的。線程的生命周期就是線程函數(shù)從開始執(zhí)行到return,線程句柄的生命周期是從CreateThread返回到你CloseHandle()。
2,線程句柄是一種內(nèi)核對象,系統(tǒng)維護著每一個內(nèi)核對象,當(dāng)每個內(nèi)核對象引用記數(shù)為0時,系統(tǒng)就從內(nèi)存中釋放該對象,CloseHandle就是將該線程對象的引用記數(shù)減1。所有的內(nèi)核對象(包括線程Handle)都是系統(tǒng)資源,用了要還的,也就是說用完后一定要closehandle關(guān)閉之,如果不這么做,你系統(tǒng)的句柄資源很快就用光了。
只是關(guān)閉了一個線程句柄對象,表示我不再使用該句柄,即不對這個句柄對應(yīng)的線程做任何干預(yù)了。并沒有結(jié)束線程.

  • 如果線程需要訪問共享資源,就需要進行線程之間的同步處理。

  • 利用互斥對象實現(xiàn)線程同步
    互斥對象mutex屬于內(nèi)核對象。它能確保線程擁有對單個資源的互斥訪問權(quán)。
    互斥對象包含一個使用數(shù)量,一個線程ID和一個計數(shù)器。
    ID用于標識系統(tǒng)中的哪個對象擁有互斥對象。
    計數(shù)器用于指明該線程擁有互斥對象的次數(shù)。

HANDLE hMutex; 互斥對象的句柄
hMutex=CreateMutex(NULL,FALSE,NULL);

WaitForSingleObject(hMutex,INFINITE);
ReleaseMutex(hMutex);
對互斥對象來說,誰擁有誰釋放。

如果多次在同一線程中請求同一個互斥對象,那么就需要相應(yīng)的多次調(diào)用
ReleaseMutex函數(shù)釋放該互斥對象。

注:主線程擁有該互斥對象時,該對象就處于未通知狀態(tài)了。主線程通過WaitForSingleObject函數(shù)再次請求該互斥對象的所有權(quán)時,因為ID相同,所以仍然能夠請求到這個互斥對象。操作系統(tǒng)通過互斥對象內(nèi)部的計數(shù)器來維護同一個線程請求到該互斥對象的次數(shù)。

  • 保證程序只有一個實例運行
    對這種同時只能有應(yīng)用程序的一個實例運行的功能,可以通過命名的互斥對象來實現(xiàn)。
    CreateMutex
    GetLastError 返回值 ERROR_ALREADY_EXISTS 或其他

  • LPVOID,是一個沒有類型的指針,也就是說你可以將任意類型的指針賦值給LPVOID類型的變量(一般作為參數(shù)傳遞),然后在使用的時候再轉(zhuǎn)換回來。

  • 不能使用全局函數(shù)和全局變量,我們就可以采用靜態(tài)成員函數(shù)和靜態(tài)成員變量的方法來解決上述問題。

  • PVOID是void*的別名。

  • 在windef.h中,LPVOID是這么定義的:typedef void far *LPVOID。

  • 和void*的區(qū)別是遠指針,因為win32編程中,經(jīng)常要調(diào)用外部DLL堆變量。但現(xiàn)在的大部分平臺已經(jīng)無所謂了,因為尋址方式成flat了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容