孙鑫MFC笔记之十四--多线程同步与异步套接字编程
線程同步有三種方式:
1.????? 互斥對(duì)象涉及方法:
HANDLE hMutex=CreateMutex(NULL,FALSE,NULL);?//第二個(gè)參數(shù)為FALSE,將互斥對(duì)象聲明為空閑狀態(tài)
WaitForSingleObject(hMutex,INFINITE);?//第二個(gè)參數(shù)為INFINITE表示一直等待,直到擁有互斥對(duì)象
ReleaseMutex(hMutex);?//使用完了,將互斥對(duì)象還給操作系統(tǒng)
????? 具體代碼及各種情況的分析見上一章,這里就不再敘述。
?
2.????? 事件對(duì)象:
事件對(duì)象也屬于內(nèi)核對(duì)象,包含一個(gè)使用計(jì)數(shù),一個(gè)用于指明該事件是一個(gè)自動(dòng)重置的事件還是一個(gè)人工重置的事件的布爾值,另一個(gè)用于指明該事件處于可用狀態(tài)還是不可用的布爾值。
有兩種不同類型的事件對(duì)象。一種是人工重置的事件,另一種是自動(dòng)重置的事件。當(dāng)人工重置的事件得到通知時(shí),等待該事件的所有線程均變?yōu)榭烧{(diào)度線程。當(dāng)一個(gè)自動(dòng)重置的事件得到通知時(shí),等待該事件的線程中只有一個(gè)線程變?yōu)榭烧{(diào)度線程。所以優(yōu)先選擇自動(dòng)重置的事件。
說(shuō)明:CreateEvent方法第一個(gè)參數(shù)是關(guān)于的安全的結(jié)構(gòu)體,一般設(shè)置為NULL;第二個(gè)參數(shù)表示是人工重置還是自動(dòng)重置,TRUE代表人工重置,如果為TURE需要調(diào)用這個(gè)ResetEvent函數(shù)來(lái)人工重置為非信號(hào)狀態(tài);第三個(gè)參數(shù)表示初始化狀態(tài),如果為TURE初始化狀態(tài)信號(hào)為有信號(hào)的;第四個(gè)參數(shù)表示Event名稱,NULL的話,默認(rèn)。
BOOL ResetEvent(
? HANDLE?hEvent?? // handle to event
);
?
?BOOL SetEvent(
? HANDLE?hEvent?? // handle to event
);//設(shè)置信號(hào)為有信號(hào)狀態(tài)
?HANDLE g_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
?
以下代碼會(huì)出現(xiàn)異常:在將CreateEvent的第二個(gè)參數(shù)設(shè)置為人工重置的時(shí)候,因?yàn)榈却撌录乃芯€程均變?yōu)榭烧{(diào)度線程,所以發(fā)現(xiàn)售票實(shí)例程序最終會(huì)出現(xiàn)0。所以最好還是選擇自動(dòng)重置的事件。
#include?<windows.h>
#include?<iostream.h>
?
DWORD?WINAPI ThreadProc1(LPVOID lpParameter);
DWORD?WINAPI ThreadProc2(LPVOID lpParameter);
?
int?ticket=100;
HANDLE?g_hEvent;
?
void?main()
{
??????g_hEvent=CreateEvent(NULL,TRUE,TRUE,NULL);//初始化代碼應(yīng)放在創(chuàng)建線程以前
?
????? HANDLE handle1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
????? HANDLE handle2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
????? CloseHandle(handle1);
????? CloseHandle(handle2);?
??????g_hEvent=CreateEvent(NULL,TRUE,TRUE,NULL);//放在最前面
????? Sleep(4000);
????? CloseHandle(g_hEvent);
}
?
DWORD?WINAPI ThreadProc1(LPVOID lpParameter)
{
????? while(TRUE)
????? {
?????????? WaitForSingleObject(g_hEvent,INFINITE); //第二個(gè)參數(shù)為INFINITE表示一直等待,直到擁有互斥對(duì)象
?????????? if(ticket>0)
?????????? {
???????????????? Sleep(1);
???????????????? cout<<"thread1 sale the ticket id is:"<<ticket--<<endl;
?????????? }
?????????? else
?????????????????break;
????? }
????? return 0;
}
?
DWORD?WINAPI ThreadProc2(LPVOID lpParameter)
{
????? while(TRUE)
????? {
?????????? WaitForSingleObject(g_hEvent,INFINITE);
?????????? if(ticket>0)
?????????? {
???????????????? Sleep(1);
???????????????? cout<<"thread2 sale the ticket id is:"<<ticket--<<endl;
?????????? }
?????????? else
?????????????????break;
????? }
????? return 0;
}
說(shuō)明:
如果一個(gè)線程循環(huán)內(nèi)部已經(jīng)調(diào)用了WaitForSingleObject(g_hEvent,INFINITE);但是在單個(gè)循環(huán)完成前沒(méi)有調(diào)用SetEvent(g_hEvent)將狀態(tài)設(shè)置成可用的話,下一次進(jìn)入循環(huán)時(shí)再次調(diào)用WaitForSingleObject時(shí)發(fā)現(xiàn)狀態(tài)不可用,所以一直等待,代碼例子將上面的代碼g_hEvent=CreateEvent(NULL,TRUE,TRUE,NULL);修改為g_hEvent=CreateEvent(NULL,FALSE,TRUE,NULL);則會(huì)出現(xiàn)這個(gè)問(wèn)題,其結(jié)果就是僅僅線程1售出了100這張票。如果在循環(huán)退出前調(diào)用SetEvent(g_hEvent);則問(wèn)題可以解決。
?
綜上所述,涉及到的方法:
HANDLE g_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
WaitForSingleObject(g_hEvent,INFINITE); //等待事件,如果事件可用,運(yùn)行下面的代碼,并且將事件狀態(tài)設(shè)置為不可用狀態(tài),如果事件不可用,一直等待。
SetEvent(g_hEvent)? //將事件設(shè)置為可用的狀態(tài)
ResetEvent(g_hEvent) //將事件設(shè)置為不可用狀態(tài)
一般情況下WaitForSingleObject和SetEvent配對(duì)使用。
?
3.????? 關(guān)鍵代碼段:
?
關(guān)鍵代碼段(臨界區(qū))是指一個(gè)小代碼段,在代碼能夠執(zhí)行前,它必須獨(dú)占對(duì)某些資源的訪問(wèn)權(quán)。
??????????? 可以將關(guān)鍵代碼段想象成電話亭資源:
?????????? CRITICAL_SECTION g_cs;
?????????? InitializeCriticalSection(&g_cs); //創(chuàng)建電話亭資源,一般放在構(gòu)造函數(shù)中
?????????? EnterCriticalSection(&g_cs); //判斷關(guān)鍵資源所有權(quán)是否可用,可用則進(jìn)入
?????????? LeaveCriticalSection(&g_cs);使用完關(guān)鍵資源后,釋放所有權(quán)
?????????? DeleteCriticalSection(&g_cs); //銷毀電話亭資源,一般放在析構(gòu)函數(shù)中
????? 其中InitializeCriticalSection和DeleteCriticalSection配對(duì)使用;
????? EnterCriticalSection和LeaveCriticalSection配對(duì)使用,中間存放訪問(wèn)共享資源的代碼。?
4.????? 互斥對(duì)象、事件對(duì)象與關(guān)鍵代碼段的比較
互斥對(duì)象和事件對(duì)象屬于內(nèi)核對(duì)象,利用內(nèi)核對(duì)象進(jìn)行線程同步,速度較慢,但利用互斥對(duì)象和事件對(duì)象這樣的內(nèi)核對(duì)象,可以在多個(gè)進(jìn)程中的各個(gè)線程間進(jìn)行同步。
關(guān)鍵代碼段是工作在用戶方式下,同步速度較快,但在使用關(guān)鍵代碼段時(shí),很容易進(jìn)入死鎖狀態(tài),因?yàn)樵诘却M(jìn)入關(guān)鍵代碼段時(shí)無(wú)法設(shè)定超時(shí)值。
?
5.????? 死鎖:
哲學(xué)家進(jìn)餐的問(wèn)題:每個(gè)哲學(xué)家手中只有一根筷子,要進(jìn)餐必須有兩根,但誰(shuí)也不愿意先給出自己的那根給別人。大家都處于等待狀態(tài)。
線程1擁有了臨界區(qū)對(duì)象A,等待臨界區(qū)對(duì)象B的擁有權(quán),線程2擁有了臨界區(qū)對(duì)象B,等待臨界區(qū)對(duì)象A的擁有權(quán),就造成了死鎖。
死鎖代碼:
#include?<windows.h>
#include?<iostream.h>
?
DWORD?WINAPI ThreadProc1(LPVOID lpParameter);
DWORD?WINAPI ThreadProc2(LPVOID lpParameter);
?
int ticket=100;
?
//創(chuàng)建兩個(gè)關(guān)鍵資源
CRITICAL_SECTION g_cs1;
CRITICAL_SECTION g_cs2;
?
void?main()
{
????? HANDLE handle1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
????? HANDLE handle2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
????? CloseHandle(handle1);
????? CloseHandle(handle2);???
????? InitializeCriticalSection(&g_cs1);//初始化要放在前面
????? InitializeCriticalSection(&g_cs2);
?
????? Sleep(4000);
????? DeleteCriticalSection(&g_cs1);
????? DeleteCriticalSection(&g_cs2);
}
?
DWORD?WINAPI ThreadProc1(LPVOID lpParameter)
{
????? while(TRUE)
????? {
?????????? EnterCriticalSection(&g_cs1);
?????????? Sleep(1);
?????????? EnterCriticalSection(&g_cs2);
?????????? if(ticket>0)
?????????? {
???????????????? Sleep(1);
???????????????? cout<<"thread1 sale the ticket id is:"<<ticket--<<endl;
?????????? }
?????????? else
?????????????????break;
?????????? LeaveCriticalSection(&g_cs1);
?????????? LeaveCriticalSection(&g_cs2);
????? }
????? return 0;
}
?
DWORD?WINAPI ThreadProc2(LPVOID lpParameter)
{
????? while(TRUE)
????? {
?????????? EnterCriticalSection(&g_cs2);
?????????? Sleep(1);
?????????? EnterCriticalSection(&g_cs1);
?????????? if(ticket>0)
?????????? {
???????????????? Sleep(1);
???????????????? cout<<"thread2 sale the ticket id is:"<<ticket--<<endl;
?????????? }
?????????? else
?????????????????break;
?????????? LeaveCriticalSection(&g_cs1);
?????????? LeaveCriticalSection(&g_cs2);
????? }
????? return 0;
}
?
說(shuō)明:首先線程1得到資源1的所有權(quán),然后睡眠1毫秒,線程1就讓出了執(zhí)行權(quán),這個(gè)時(shí)候線程2得到執(zhí)行權(quán),運(yùn)行,得到資源2的所有權(quán),線程2然后睡眠1毫秒,線程2就讓出了執(zhí)行權(quán),這個(gè)時(shí)候線程1得到執(zhí)行權(quán),線程1繼續(xù)執(zhí)行,想得到資源2的資源,但發(fā)現(xiàn)資源2被線程1所占用,等待。當(dāng)線程1的事件片過(guò)了以后,線程2得到執(zhí)行權(quán),繼續(xù)執(zhí)行,想得到資源1的所有權(quán),但發(fā)現(xiàn)資源1被線程1占用,所以也繼續(xù)等待,這樣線程1和線程2都互相等待,造成死鎖。
?
1.??????異步套接字編程:
Windows套接字在兩種模式下執(zhí)行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,執(zhí)行操作的Winsock函數(shù)會(huì)一直等待下去,不會(huì)立即返回程序(將控制權(quán)交還給程序)。而在非阻塞模式下,Winsock函數(shù)無(wú)論如何都會(huì)立即返回。采用異步套接字,可有效改善程序的運(yùn)行性能。
Windows Sockets為了支持Windows消息驅(qū)動(dòng)機(jī)制,使應(yīng)用程序開發(fā)者能夠方便地處理網(wǎng)絡(luò)通信,它對(duì)網(wǎng)絡(luò)事件采用了基于消息的異步存取策略。
Windows Sockets的異步選擇函數(shù)WSAAsyncSelect()提供了消息機(jī)制的網(wǎng)絡(luò)事件選擇,當(dāng)使用它登記的網(wǎng)絡(luò)事件發(fā)生時(shí),Windows應(yīng)用程序相應(yīng)的窗口函數(shù)將收到一個(gè)消息,消息中指示了發(fā)生的網(wǎng)絡(luò)事件,以及與事件相關(guān)的一些信息。
??????在上一章中編寫的Chat程序中,因?yàn)榻邮粘绦蚍旁诹艘粋€(gè)線程中,所以雖然它是阻塞的,也沒(méi)有影響到主線程的運(yùn)行性能。?
2.??????編寫基于異步套接字的聊天室程序:
?
相關(guān)函數(shù):
int WSAEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, ILPDWORD lpdwBufferLength );
Win32平臺(tái)支持多種不同的網(wǎng)絡(luò)協(xié)議,采用Winsock2,就可以編寫可直接使用任何一種協(xié)議的網(wǎng)絡(luò)應(yīng)用程序了。通過(guò)WSAEnumProtocols函數(shù)可以獲得系統(tǒng)中安裝的網(wǎng)絡(luò)協(xié)議的相關(guān)信息。
lpiProtocols,一個(gè)以NULL結(jié)尾的協(xié)議標(biāo)識(shí)號(hào)數(shù)組。這個(gè)參數(shù)是可選的,如果lpiProtocols為NULL,則返回所有可用協(xié)議的信息,否則,只返回?cái)?shù)組中列出的協(xié)議信息。
lpProtocolBuffer,[out],一個(gè)用WSAPROTOCOL_INFO結(jié)構(gòu)體填充的緩沖區(qū)。 WSAPROTOCOL_INFO結(jié)構(gòu)體用來(lái)存放或得到一個(gè)指定協(xié)議的完整信息。
lpdwBufferLength,[in, out],在輸入時(shí),指定傳遞給WSAEnumProtocols()函數(shù)的lpProtocolBuffer緩沖區(qū)的長(zhǎng)度;在輸出時(shí),存有獲取所有請(qǐng)求信息需傳遞給WSAEnumProtocols ()函數(shù)的最小緩沖區(qū)長(zhǎng)度。這個(gè)函數(shù)不能重復(fù)調(diào)用,傳入的緩沖區(qū)必須足夠大以便能存放所有的元素。這個(gè)規(guī)定降低了該函數(shù)的復(fù)雜度,并且由于一個(gè) 機(jī)器上裝載的協(xié)議數(shù)目往往是很少的,所以并不會(huì)產(chǎn)生問(wèn)題。
?
???SOCKET WSASocket( int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags );
前三個(gè)參數(shù)和socket()函數(shù)的前三個(gè)參數(shù)含義一樣。
lpProtocolInfo,一個(gè)指向WSAPROTOCOL_INFO結(jié)構(gòu)體的指針,該結(jié)構(gòu)定義了所創(chuàng)建的套接字的特性。如果lpProtocolInfo為NULL,則WinSock2 DLL使用前三個(gè)參數(shù)來(lái)決定使用哪一個(gè)服務(wù)提供者,它選擇能夠支持規(guī)定的地址族、套接字類型和協(xié)議值的第一個(gè)傳輸提供者。如果lpProtocolInfo不為NULL,則套接字綁定到與指定的結(jié)構(gòu)WSAPROTOCOL_INFO相關(guān)的提供者。
g,保留的。
dwFlags,套接字屬性的描述。
?
?
?
a.??????因?yàn)镸FC自帶的AfxSocketInit函數(shù)初始化支持的是1.1版本的套接字,不適合異步套接字,我們需要調(diào)用的是Winsock2版本的套接字,那么加載套接字庫(kù)的過(guò)程只能使用WSAStartup了。在CChatApp的InitInstance初始化函數(shù)中添加:
?
WORD wVersionRequested;
??????WSADATA wsaData;
??????int?err;?????
??????wVersionRequested = MAKEWORD( 2, 2 );????
??????err = WSAStartup( wVersionRequested, &wsaData );
??????if?( err != 0 ) {
???????????return;
??????}?????????
??????if?( LOBYTE( wsaData.wVersion ) != 2 ||
????????HIBYTE( wsaData.wVersion ) != 2 ) {??????????
???????????WSACleanup( );
???????????return;
}
b.??????在StdAfx.h里添加#include <winsock2.h>,在setting里添加ws2_32.lib庫(kù)文件。
c.??????給CChatApp類添加析構(gòu)函數(shù),在其中添加WSACleanup來(lái)終止對(duì)套接字庫(kù)的使用。
d.??????給CChatDlg類添加成員變量SOCKET m_socket,并在構(gòu)造函數(shù)中初始化為0
e.??????給CChatDlg類添加析構(gòu)函數(shù),添加:
if(m_socket) //判斷socket是否有值
??????closesocket(m_socket);
f.???????創(chuàng)建初始化函數(shù)InitSocket(),代碼如下:
說(shuō)明:在Winsock2版本中提供的WSASocket這樣一個(gè)擴(kuò)展方法用于創(chuàng)建套接字,對(duì)應(yīng)于socket方法;bind方法在winsock2中沒(méi)有提供相應(yīng)的擴(kuò)展方法。然后調(diào)用WSAAsyncSelect方法請(qǐng)求一個(gè)windows基于消息的網(wǎng)絡(luò)事件通知。
m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
???if(INVALID_SOCKET==m_socket)
???{
????????MessageBox("創(chuàng)建套接字失敗!");
????????return?FALSE;
???}
??
???SOCKADDR_IN addrSock;
???addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
???addrSock.sin_family=AF_INET;
???addrSock.sin_port=htons(1234);
??
???int?retVal;
???retVal=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
???if(SOCKET_ERROR==retVal)
???{
????????MessageBox("套接字綁定失敗!");
????????return?FALSE;
???}
說(shuō)明:WSAAsyncSelect方法第二個(gè)參數(shù)表示網(wǎng)絡(luò)事件發(fā)生時(shí)用來(lái)接收消息的窗口,第三個(gè)參數(shù)表示處理響應(yīng)的消息,第四個(gè)參數(shù)表示網(wǎng)絡(luò)事件類型,采用或操作。我們當(dāng)前采用讀這樣一個(gè)事件,網(wǎng)絡(luò)上一旦有數(shù)據(jù)到來(lái)的時(shí)候就會(huì)觸發(fā)這個(gè)事件,系統(tǒng)就會(huì)通過(guò)我們自定義的消息UM_SOCK來(lái)通知我們進(jìn)行處理if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))
????????????{
?????????????????MessageBox("注冊(cè)網(wǎng)絡(luò)讀取事件失敗!");
?????????????????return?FALSE;
???};
return?TRUE;
g.??????消息響應(yīng)函數(shù)的處理:
1.??????創(chuàng)建自定以的消息UM_SOCK,注意:在消息響應(yīng)函數(shù)的申明中還是要添加WPARAM和LPARAM參數(shù),因?yàn)榫W(wǎng)絡(luò)上的數(shù)據(jù)是通過(guò)這兩個(gè)參數(shù)傳遞給消息響應(yīng)函數(shù)進(jìn)行處理的。
2.??????參看MSDN中WSAAsyncSelect方法的說(shuō)明如下:
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?lParamspecifies the network event that has occurred. The high word of?lParam?contains any error code.
3.??????WSARecvFrom函數(shù)的第二個(gè)參數(shù)可表示一個(gè)WSABUF的結(jié)構(gòu)體數(shù)組,可用于存放多個(gè)從網(wǎng)絡(luò)上接收到的信息塊,當(dāng)然也可以將所有信息放在一個(gè)結(jié)構(gòu)體中,然后將自己關(guān)心的信息塊取出,但這樣做比較麻煩,可以直接用WSABUF結(jié)構(gòu)體數(shù)組接收不同信息的塊即可。(沒(méi)有具體的實(shí)際操作經(jīng)驗(yàn))
在消息響應(yīng)函數(shù)中添加如下代碼
?
?int WSARecvFrom( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, struct sockaddr FAR *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );?
s,標(biāo)識(shí)套接字的描述符。
lpBuffers,[in, out],一個(gè)指向WSABUF結(jié)構(gòu)體的指針。每一個(gè)WSABUF結(jié)構(gòu)體包含一個(gè)緩沖區(qū)的指針和緩沖區(qū)的長(zhǎng)度。
typedef struct __WSABUF {
? u_long??????len;
? char FAR??? *buf;
} WSABUF, FAR * LPWSABUF;
dwBufferCount, lpBuffers數(shù)組中WSABUF結(jié)構(gòu)體的數(shù)目。
lpNumberOfBytesRecvd,[out],如果接收操作立即完成,則為一個(gè)指向本次調(diào)用所接收的字節(jié)數(shù)的指針。
lpFlags,[in, out],一個(gè)指向標(biāo)志位的指針。
lpFrom,[out],可選指針,指向重疊操作完成后存放源地址的緩沖區(qū)。
lpFromlen,[in, out],指向from緩沖區(qū)大小的指針,僅當(dāng)指定了lpFrom才需要。
lpOverlapped,一個(gè)指向WSAOVERLAPPED結(jié)構(gòu)體的指針(對(duì)于非重疊套接字則忽略)。
lpCompletionRoutine,一個(gè)指向接收操作完成時(shí)調(diào)用的完成例程的指針(對(duì)于非重疊套接字則忽略)。
?
?
switch(LOWORD(lParam)) {?//lParam的低字節(jié)指明網(wǎng)絡(luò)事件的類型
??????case?FD_READ:?//我們當(dāng)前只有讀取這樣一個(gè)事件,這是在WSAAsyncSelect中設(shè)定的
???????????WSABUF wsabuf;
???????????wsabuf.buf=new?char[200];?//網(wǎng)絡(luò)上接收到的數(shù)據(jù)
???????????wsabuf.len=200;
???????????DWORD dwRead;
???????????DWORD dwFlag=0;
???????????SOCKADDR_IN addrFrom;
???????????int?len=sizeof(addrFrom);
???????????CString str;
????????????if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,(SOCKADDR*)&addrFrom,&len,NULL,NULL))
???????????{
?????????????????//下面的消息框基本不會(huì)運(yùn)行,因?yàn)?/span>WSARecvFrom方法是在有網(wǎng)絡(luò)數(shù)據(jù)的情況下才會(huì)被調(diào)用的,所以運(yùn)行到這段,基本是有數(shù)據(jù)的,做這樣一個(gè)判斷,只是出于編程風(fēng)格一致而已
?????????????????MessageBox("接收數(shù)據(jù)失敗!");
?????????????????return;
???????????}
??????str.Format("from %s said:%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
???????????CString temp;
???????????GetDlgItemText(IDC_EDIT_RECV,temp);
???????????temp+="/r/n"+str;
???????????SetDlgItemText(IDC_EDIT_RECV,temp);
???????????break;
}
h.??????信息的發(fā)送:
?
?int WSASendTo( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr FAR *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
s,標(biāo)識(shí)一個(gè)套接字(可能已連接)的描述符。
lpBuffers,一個(gè)指向WSABUF結(jié)構(gòu)體的指針。每一個(gè)WSABUF結(jié)構(gòu)體包含一個(gè)緩沖區(qū)的指針和緩沖區(qū)的長(zhǎng)度。
dwBufferCount,?lpBuffers數(shù)組中WSABUF結(jié)構(gòu)體的數(shù)目。
lpNumberOfBytesSent,[out],如果發(fā)送操作立即完成,則為一個(gè)指向本次調(diào)用所發(fā)送的字節(jié)數(shù)的指針。
dwFlags,指示影響操作行為的標(biāo)志位。
lpTo,可選指針,指向目標(biāo)套接字的地址。
iToLen,lpTo中地址的長(zhǎng)度。
lpOverlapped,一個(gè)指向WSAOVERLAPPED結(jié)構(gòu)的指針(對(duì)于非重疊套接字則忽略)。
lpCompletionRoutine,一個(gè)指向接收操作完成時(shí)調(diào)用的完成例程的指針(對(duì)于非重疊套接字則忽略)。
?
DWORD dwIP;?//控件上填寫的IP地址
???CString strSend;?//需要發(fā)送的信息內(nèi)容
???WSABUF wsbuf;?//需要發(fā)送的信息內(nèi)容
???DWORD dwSend;
???((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
???GetDlgItemText(IDC_EDIT_SEND,strSend);
?
???SOCKADDR_IN addrTo;
???addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
???addrTo.sin_family=AF_INET;
???addrTo.sin_port=htons(1234);
?
???//GetBuffer函數(shù)將CString類型轉(zhuǎn)換為char*類型
???wsbuf.buf=strSend.GetBuffer(strSend.GetLength());
???wsbuf.len=strSend.GetLength()+1;?//多一個(gè)字節(jié)用于存放結(jié)束操作符
???if(SOCKET_ERROR==WSASendTo(m_socket,&wsbuf,1,&dwSend,0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
???{
????????MessageBox("發(fā)送數(shù)據(jù)失敗!");
????????return;
???}
???else
???{
????????SetDlgItemText(IDC_EDIT_SEND,"");
}
i.????????綜上所述,創(chuàng)建一個(gè)基于winsock2版本的異步套接字的網(wǎng)絡(luò)聊天室程序有以下幾個(gè)步驟:
1.??????調(diào)用WSAStartup加載套接字庫(kù)
2.??????調(diào)用WSASocket創(chuàng)建套接字
3.?????調(diào)用WSAAsyncSelect請(qǐng)求基于windows消息的網(wǎng)絡(luò)事件通知
4.??????創(chuàng)建自定義的消息響應(yīng)函數(shù),來(lái)處理捕獲的網(wǎng)絡(luò)事件
5.??????在消息響應(yīng)函數(shù)內(nèi)部調(diào)用WSARecvFrom來(lái)處理接收到的數(shù)據(jù)
6.??????調(diào)用WSASendTo處理發(fā)送數(shù)據(jù)?
?
3.通過(guò)主機(jī)名稱實(shí)現(xiàn)聊天:
??????? //IP轉(zhuǎn)換為主機(jī)名稱
??????? //str.Format("from %s said:%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);修改為以下代碼
?HOSTENT *pHost;
?pHost = gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET);????
?str.Format("from %s said:%s",pHost->h_name,wsabuf.buf);?
?
??????? //主機(jī)名稱轉(zhuǎn)換為IP
?HOSTENT * pHost;
??????? SOCKADDR_IN addrTo;?
?CString strHostName;
?
?if (GetDlgItemText(IDC_EDIT_HOSTNAME,strHostName),strHostName=="")
?{
??((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
??addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
?}
?else
?{
??pHost = gethostbyname(strHostName);
??addrTo.sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[0]);
?}
??
4.??????小結(jié):
當(dāng)前程序?qū)⑾⒌慕邮蘸桶l(fā)送放在了同一個(gè)線程中,即主線程中。如果采用先前使用過(guò)的阻塞套接字的話,程序會(huì)因?yàn)榻邮蘸瘮?shù)的調(diào)用導(dǎo)致主線程的暫停運(yùn)行,就無(wú)法及時(shí)的發(fā)送消息了。但是采用異步套接字可使得發(fā)送和接收放在同一個(gè)線程中而不會(huì)有相互的影響。
如果采用異步套接字加上多線程編程,則大大會(huì)提高網(wǎng)絡(luò)運(yùn)用程序的性能。
在第十四課中講winsock1.1的編程中一般將接收函數(shù)放在一個(gè)while循環(huán)中,來(lái)使得程序一直處于接收響應(yīng)狀態(tài),在異步套接字中,利用了在程序初始化的時(shí)候調(diào)用了WSAAsyncSelect方法來(lái)聲明程序的網(wǎng)絡(luò)的事件有相應(yīng)的自定義消息來(lái)處理,其真正的核心部分還是封裝在MFC中
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/roger_ge/archive/2008/09/09/2903337.aspx
總結(jié)
以上是生活随笔為你收集整理的孙鑫MFC笔记之十四--多线程同步与异步套接字编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 运动估计简介
- 下一篇: SendMessage、PostMess