網絡編程筆記

 

二種線程同步的方式---事件對象
件對象也屬於內核對象,包含一個使用計數,一個用於指明該事件對象是一個自動重置的事件還是一個人工重置的事件的布爾值,另一個用於指明該事件處於已通知狀態還是未通知狀態的布爾值。
有兩種不同類型的事件對象,一種是人工重置的事件,另一種是自動重置的事件。當人工重置的事件得到通知時,等待該事件的所有線程均變爲可調度線程(所謂可調度線程就是當CPU時間片到來時,線程可以執行)當一個自動重置的事件得到通知時,等待該事件的線程中只有一個線程變爲可調度線程。
當一個線程等待到一個人工重置的事件對象之後,該事件仍然處於有信號狀態,所以別的線程也可以獲得該事件的所有全,變爲可調度線程。所以,人工重置的事件對象得到通知時,所有等待該事件對象的線程都變爲可調度線程。
線程一旦獲得了某個事件對象且使其處於了無信號狀態,線程內的執行代碼就變爲受保護代碼,在此期間別的線程不可以運行,即使別的線程獲得了CPU的時間片。
如果一個事件始終處於無信號狀態,則別的線程都無發請求獲得該事件,別的線程都無法執行,只有次線程可以執行。
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
);創建或着打開一個命名的或者沒有命名的事件對象。
BOOL bManualReset:TRUE--人工重置,FALSE--自動重置。
如果選擇了TRUE(人工重置),則必須調用ResetEvent(handle)函數來重置這個事件爲無信號狀態。
如果爲FALSE(自動重置事件),則當一個等待線程被釋放以後,系統自動設定這個事件爲無信號狀態。
也就是說,如果我們創建的是人工重置的事件對象,當我們等待到事件對象之後,我們需要手動的調用ResetEvent()函數將這個事件設爲無信號狀態。
如果我們創建的是自動重置的事件對象,當一個線程等待到這個事件對象之後,系統將自動將這個事件對象設置爲無信號狀態。
BOOL bInitialState:設置事件創建時的初始狀態。TRUE--有信號,FALSE--無信號。
如果在創建一個對象時將bInitialState設置爲了無信號狀態(FALSE),則別的線程就不能再申請到此事件對象直到該事
件對象被該線程釋放。

BOOL SetEvent(
  HANDLE hEvent
);設定指定的對象爲有信號狀態。

在單CPU的系統中,同一時刻只能有一個線程運行。當一個線程已經進入到被保護的代碼運行後,即使再把它的事件對象設置爲非信號狀態,他也不能再起作用了。人工重置的事件對象除非人工調用SetEvent將其設置爲有信號狀態,否則它將始終處於非信號狀態。

對於線程同步間的同步來講,我們不要採用人工重置的事件對象。


在線程同步時,一個線程獲得了一個自動重置的事件對象後, 系統自動將這個事件對象設置爲非信號狀態,這個時候其他線程無法獲取此事件對象,只能等待,這個線程也不能再一次獲取這個事件對象。
所以,當一個線程獲取了事件對象執行完受保護的代碼後,應該用SetEvent()將這個事件對象設置爲有信號狀態。
在主線程退出之前要調用CloseHandle()--關閉事件對象。

通過創建一個命名的事件對象,我們也可以完成讓一個程序只有一個實例運行,例如金山詞霸;用創建命名的互斥對象也可以做到。
第三種線程同步的方式--關鍵代碼段
關鍵代碼段也叫做臨界區,他工作在用戶方式下。
關鍵代碼段(臨界區)是指一個小代碼段,在代碼能夠執行前,它必須獨佔對某些資源的訪問權。
我們可以把能夠訪問同一種資源的代碼段叫做關鍵代碼段。關鍵代碼段非常類似與公用電話廳,當我們進入公用電話亭之前要判斷一下電話亭當中是否有人。如果已經有人在使用這種資源,那麼我們就只能在外面等候,當別人用完了電話我們才能用。

void EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);
等待指定的臨界區對象的所有權,當調用線程被賦予所有權後,這個函數就返回。否則該函數將一直等待。從而導致我們線程的等待。
LPCRITICAL_SECTION lpCriticalSection:指向關鍵代碼段結構體的一個指針。其功能類似於建立一個公用電話亭。

void InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);
void LeaveCriticalSection
( LPCRITICAL_SECTION
lpCriticalSection );
釋放關鍵代碼段的所有權。這樣別的線程就可以獲得關鍵代碼段的所有權。

void DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);當關鍵的代碼段不需要再存在的時候可以調用這個函數,該函數釋放一個沒有被擁有的臨界區對象相關的所有資源。
線程死鎖
哲學家進餐的問題:
有一羣哲學家在一起共進晚餐, 但是每個哲學家手上都只有跟筷子,這個時候如果有一個哲學家把筷子交出來讓其他的哲學家先吃,別人吃完之後,別人再將筷子交回來,那麼這些問都可以完成進餐。但是所有的哲學家都這麼想,都想讓別人把筷子交出來自己先吃。最終,這些哲學家都只能看着滿桌子的食物而都沒法進餐。
這就是線程死鎖的描述。
線程1擁有了臨界區對象A,等待臨界區對象B的所有權;線程2擁有了臨界區對象B,等待臨界區對象B的所有權,這樣就造成了死鎖。
所以在做多線程同步時,要避免發生線程的死鎖。
三種線程同步方法--互斥對象,事件對象,關鍵代碼段(臨界區)的比較


1--互斥對象和事件對象屬於內核對象,利用內核對象進行線程同步,速度較慢,但利用互斥對象和事件對象這樣的內核對象可以在多個進程中的各個線程間進行同步。
2--關鍵代碼段是工作在用戶方式下,同步速度較快,但在使用關鍵代碼段時,很容易進入死鎖狀態,因爲在等待進入關鍵代碼段時無法設定超時值。


我們在進行多線程同步時,首選的是關鍵代碼段。如果是在MFC程序中使用,我們可以在一個類的構造函數中去調用InitializeCriticalSection(),在類的析構函數中去調用DeleteCriticalSection(),在我們所要保護的代碼的前面調用EnterCriticalSection(),在訪問完我們調用的資源之後調用LeaveCriticalSection()。使用關鍵代碼段時一定要注意,在調用了EnterCriticalSection只有一定要調用LeaveCriticalSection,否則,其他線程都沒有辦法執行。
如果構造在多個臨街區對象,要注意線程的死鎖,

基於消息的異步套接字
1---Windows套接字在兩種模式下執行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,執行操作的Winsock函數會一直等待下去,不會立即返回程序(將控制權交還給程序)。而在非阻塞模式下,Winsock函數無論如何都會立即返回。譬如我們調用一個receive()函數,如果這個時候網路上沒有數據到來,receive函數就會阻塞,導致我們的線程暫停運行,而不會影響主程序的運行。
對於非阻塞,Winsock函數無論如何都會立即返回,當函數執行完成之後,如果我們接收的數據操作完成了,系統會採用一種方式通知我們,我們就會知道我們的接收操作是正常完成了還是出錯了。(這裏還是不太明白)
2---Windows sockets爲了支持Windows的消息機制,使應用程序開發者能夠方便的處理網絡通信,他對網絡事件採用了基於消息的異步存取策略。在Windows環境下,因爲阻塞套接字在很多應用環境當中,會影響我們程序的性能,所以在有些情況下我們需要採用非阻塞的套接字編程。工作在非阻塞模式下有多種機制,其中一種是異步選擇機制。
3--Windows Sockets的異步選擇函數WSAAsyncSelect()提供了消息機制的網絡事件選擇,當使用它登記的網絡事件發生時,Windows應用程序相應的窗口函數將收到一個消息,消息中指示了發生的網路事件,以及與事件相關的一些信息。譬如登記一個網絡讀取事件,那麼一旦有數據到來的時候,就會觸發這個事件,操作系統就會通過一個消息來通知我們,我們就可以在消息響應函數中去receive這個數據。採用異步選擇機制能夠有效的提高我們應用程序的性能。

int WSAAsyncSelect(
  SOCKET s,
  HWND hWnd,
  unsigned int wMsg,
  long lEvent
);
爲一個套接字申請基於關於網絡事件的通知。該函數自動設定socket爲非阻塞模式。也就是說當我們調用WSAAsyncSelect去註冊一個網路事件後,該套接字就爲非阻塞模式。
s:要申請的套接字,
hWnd:事件發生時通知的窗口的句柄。
wMsg:事件發生時窗口接收到的消息。該消息爲自定義消息。
lEvent:是我們感興趣的網絡事件的一個位掩碼組合。
WSAAsyncSelect

When one of the nominated network events occurs on the specified socket s, the application's window hWnd receives message wMsg. The wParam parameter identifies the socket on which a network event has occurred. The low word of lParam specifies the network event that has occurred. The high word of lParam contains any error code. The error code be any error as defined in Winsock2.h.

int WSAEnumProtocols(
  LPINT lpiProtocols,
  LPWSAPROTOCOL_INFO lpProtocolBuffer,
  LPDWORD lpdwBufferLength
);//取得可用的傳輸協議
Win32平臺支持多種不同的網絡協議,採用Winsock32,就可以編寫可以直接使用任何一種協議的網絡應用程序了。通過WSAEnumProtocols函數可以獲取到系統中安裝的網絡協議的相關信息。
lpiProtocols:一個以NULL結尾的信息。可選,如果爲NULL將返回所有可用協議的信息;否則值返回數組中列的協議的信息。
lpProtocolBuffer:一個以WSAPROTOCOL_INFO結構填充的緩衝區。
lpdwBufferLength:輸入時描述了lpProtocolBuffer的長度;輸出時能夠存放協議信息的最小緩衝區長度。該函數不能重複調用。

SOCKET WSASocket(
  int af,
  int type,
  int protocol,
  LPWSAPROTOCOL_INFO lpProtocolInfo,
  GROUP g,
  DWORD dwFlags
);//創建一個基於某種傳輸協議的socket。
前三個參數同於socket(  int af,
  int type,
  int protocol)函數的參數,
LPWSAPROTOCOL_INFO lpProtocolInfo:指向一個LPWSAPROTOCOL結構體的指針,該結構指定了所創建的套接字的特性。如果該參數爲NULL,winsock2 DLL自己判斷決定使用那一個服務體供者,他選擇能夠支持規定的地址族、套接字類型、和協議值的第一個傳輸提供者。如果不爲NULL,則套接字綁定到指定的結構WSAPROTOCOL_INFO相關的提供者。
g:保留,
dwFlags:套接字屬性的描述。

int WSARecvFrom(
  SOCKET s,//套接字描述符
  LPWSABUF lpBuffers,//WSABUF結構的指針,該結構包含windows socket buffer的信息
  DWORD dwBufferCount,//windows socket buffer 的個數
  LPDWORD lpNumberOfBytesRecvd,//實際接收到的字節數
  LPDWORD lpFlags,
  struct sockaddr* lpFrom,
  LPINT lpFromlen,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);//接收數據報, 存儲源地址

typedef struct __WSABUF {  u_long len;  char FAR* buf;
} WSABUF結構。

int WSASendTo(
  SOCKET s,
  LPWSABUF lpBuffers,//WSABUF 結構體指針
  DWORD dwBufferCount,//WSABUF結構體個數
  LPDWORD lpNumberOfBytesSent,//實際發送的字節數
  DWORD dwFlags,
  const struct sockaddr* lpTo,
  int iToLen,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);


基於消息的異步套接字編程步驟:
1--創建套接字WSASocket()
2--綁定套接字調用bind
3--調用WSAAsyncSelect()爲某個套接字請求某種事件的通知
4--等事件到來後在消息相應函數中進行處理,

阻塞模式下套接字相關函數VS基於消息的異步套接字

windows下的套接字函數包含了對原始套接字函數的擴充;
1--創建套接字   socket               WSASocket();
2--關閉套接字   closesocket        closesocket
3--綁定套接字    bind               bind
4--接收數據      recvfrom            WSARecvFrom()
5--發送數據      sendto               WSASendTo()

gethostbyname()將主機名轉換爲 IP地址
The gethostbyname function retrieves host information corresponding to a host name from a host database.
可以通過主機名獲取主機的信息, ip,別名等
內存之間可以互相轉換,只要內存的數據模型是兼容的就可以。
譬如dword 類型指針指向的數據佔四個字節,ulong 類型數據佔四個字節
HOSTENT *pHostent;
pHostent = gethostbyname("papa");
addrTo.sin_addr.S_un.S_addr = *((DWORD*)pHost->h_adrr_list[0]);
其中, pHost->h_adrr_list[0]爲 char*的網絡字節序,
可以將其轉變爲DWORD*類型, 因爲dword類型的數據與ulong類型的數據內存模型相同,所以可以直接將其值付給S_addr。DOWRD*類型取值正好取出四個字節,與ulong類型相同。


DWORD *dwP;
char *cP;
(DWROD*)cp;
ulong 類型佔四個字節。

struct HOSTENT* FAR gethostbyaddr(
  const char* addr,
  int len,
  int type
);
//通過地址獲取主機名
pHost = gethostbyaddr((char *)&addrFrom.sin_addr_S_un.S_addr)...)

addrFrom.sin_addr_S_un.S_addr).爲ulong類型地址, 取地址後爲ulong類型指針, 然後再將其轉換爲char *類型指針即可。

發佈了33 篇原創文章 · 獲贊 28 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章