<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
    數(shù)據(jù)加載中……

    Socket API,CAsyncSocket,CSocket內(nèi)幕及其用法

    ??? Socket有同步阻塞方式和異步非阻塞方式兩種使用,事實上同步和異步在我們編程的生涯中可能遇到了很多,而Socket也沒什么特別。雖然同步好用,不費勁,但不能滿足一些應(yīng)用場合,其效率也很低。
    ??? 也許初涉編程的人不能理解“同步(或阻塞)”和“異步(或非阻塞)”,其實簡單兩句話就能講清楚,同步和異步往往都是針對一個函數(shù)來說的,“同步”就是函數(shù)直到其要執(zhí)行的功能全部完成時才返回,而“異步”則是,函數(shù)僅僅做一些簡單的工作,然后馬上返回,而它所要實現(xiàn)的功能留給別的線程或者函數(shù)去完成。例如,SendMessage就是“同步”函數(shù),它不但發(fā)送消息到消息隊列,還需要等待消息被執(zhí)行完才返回;相反PostMessage就是個異步函數(shù),它只管發(fā)送一個消息,而不管這個消息是否被處理,就馬上返回。

    一、Socket API
    ??? 首先應(yīng)該知道,有Socket1.1提供的原始API函數(shù),和Socket2.0提供的一組擴展函數(shù),兩套函數(shù)。這兩套函數(shù)有重復(fù),但是2.0提供的函數(shù)功能更強大,函數(shù)數(shù)量也更多。這兩套函數(shù)可以靈活混用,分別包含在頭文件Winsock.h,Winsock2.h,分別需要引入庫wsock32.lib、Ws2_32.lib。

    1、默認(rèn)用作同步阻塞方式,那就是當(dāng)你從不調(diào)用WSAIoctl()和ioctlsocket()來改變Socket IO模式,也從不調(diào)用WSAAsyncSelect()和WSAEventSelect()來選擇需要處理的Socket事件。正是由于函數(shù)accept(),WSAAccept(),connect(),WSAConnect(),send(),WSASend(),recv(),WSARecv()等函數(shù)被用作阻塞方式,所以可能你需要放在專門的線程里,這樣以不影響主程序的運行和主窗口的刷新。
    2、如果作為異步用,那么程序主要就是要處理事件。它有兩種處理事件的辦法:
    ??? 第一種,它常關(guān)聯(lián)一個窗口,也就是異步Socket的事件將作為消息發(fā)往該窗口,這是由WinSock擴展規(guī)范里的一個函數(shù)WSAAsyncSelect()來實現(xiàn)和窗口關(guān)聯(lián)。最終你只需要處理窗口消息,來收發(fā)數(shù)據(jù)。
    ??第二種,用到了擴展規(guī)范里另一個關(guān)于事件的函數(shù)WSAEventSelect(),它是用事件對象的方式來處理Socket事件,也就是,你必須首先用WSACreateEvent()來創(chuàng)建一個事件對象,然后調(diào)用WSAEventSelect()來使得Socket的事件和這個事件對象關(guān)聯(lián)。最終你將要在一個線程里用WSAWaitForMultipleEvents()來等待這個事件對象被觸發(fā)。這個過程也稍顯復(fù)雜。
    二、CAsyncSocket
    ??? 看類名就知道,它是一個異步非阻塞Socket封裝類,CAsyncSocket::Create()有一個參數(shù)指明了你想要處理哪些Socket事件,你關(guān)心的事件被指定以后,這個Socket默認(rèn)就被用作了異步方式。那么CAsyncSocket內(nèi)部到底是如何將事件交給你的呢?
    ??? CAsyncSocket的Create()函數(shù),除了創(chuàng)建了一個SOCKET以外,還創(chuàng)建了個CSocketWnd窗口對象,并使用WSAAsyncSelect()將這個SOCKET與該窗口對象關(guān)聯(lián),以讓該窗口對象處理來自Socket的事件(消息),然而CSocketWnd收到Socket事件之后,只是簡單地回調(diào)CAsyncSocket::OnReceive(),CAsyncSocket::OnSend(),CAsyncSocket::OnAccept(),CAsyncSocket::OnConnect()等虛函數(shù)。所以CAsyncSocket的派生類,只需要在這些虛函數(shù)里添加發(fā)送和接收的代碼。
    ??
    ??簡化后,大致的代碼為:
    ??bool CAsyncSocket::Create( long lEvent ) file://參數(shù)lEvent是指定你所關(guān)心的Socket事件
    ??{
    ???m_hSocket = socket( PF_INET, SOCK_STREAM, 0 ); file://創(chuàng)建Socket本身

    ???CSocketWnd* pSockWnd = new CSocketWnd; file://創(chuàng)建響應(yīng)事件的窗口,實際的這個窗口在AfxSockInit()調(diào)用時就被創(chuàng)建了。
    ???pSockWnd->Create(...);

    ???WSAAsyncSelect( m_hSocket, pSockWnd->m_hWnd, WM_SOCKET_NOTIFY, lEvent ); file://Socket事件和窗口關(guān)聯(lián)
    ??}
    ??
    ??static void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)
    ??{
    ???CAsyncSocket Socket;
    ???Socket.Attach( (SOCKET)wParam ); file://wParam就是觸發(fā)這個事件的Socket的句柄
    ???int nErrorCode = WSAGETSELECTERROR(lParam); file://lParam是錯誤碼與事件碼的合成
    ???switch (WSAGETSELECTEVENT(lParam))
    ???{
    ???case FD_READ:
    ????pSocket->OnReceive(nErrorCode);
    ????break;
    ???case FD_WRITE:
    ????pSocket->OnSend(nErrorCode);
    ????break;
    ???case FD_OOB:
    ????pSocket->OnOutOfBandData(nErrorCode);
    ????break;
    ???case FD_ACCEPT:
    ????pSocket->OnAccept(nErrorCode);
    ????break;
    ???case FD_CONNECT:
    ????pSocket->OnConnect(nErrorCode);
    ????break;
    ???case FD_CLOSE:
    ????pSocket->OnClose(nErrorCode);
    ????break;
    ???}
    ??}

    ??CSocketWnd類大致為:

    ??BEGIN_MESSAGE_MAP(CSocketWnd, CWnd)
    ???ON_MESSAGE(WM_SOCKET_NOTIFY, OnSocketNotify)
    ??END_MESSAGE_MAP()

    ??LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)
    ??{
    ???CAsyncSocket::DoCallBack( wParam, lParam ); file://收到Socket事件消息,回調(diào)CAsyncSocket的DoCallBack()函數(shù)
    ???return 0L;
    ??}

    ??然而,最不容易被初學(xué)Socket編程的人理解的,也是本文最要提醒的一點是,客戶方在使用CAsyncSocket::Connect()時,往往返回一個WSAEWOULDBLOCK的錯誤(其它的某些函數(shù)調(diào)用也如此),實際上這不應(yīng)該算作一個錯誤,它是Socket提醒我們,由于你使用了非阻塞Socket方式,所以(連接)操作需要時間,不能瞬間建立。既然如此,我們可以等待呀,等它連接成功為止,于是許多程序員就在調(diào)用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或者CAsyncSocket::GetLastError()查看Socket返回的錯誤,直到返回成功為止。這是一種錯誤的做法,斷言,你不能達到預(yù)期目的。事實上,我們可以在Connect()調(diào)用之后等待CAsyncSocket::OnConnect()事件被觸發(fā),CAsyncSocket::OnConnect()是要表明Socket要么連接成功了,要么連接徹底失敗了。至此,我們在CAsyncSocket::OnConnect()被調(diào)用之后就知道是否Socket連接成功了,還是失敗了。
    ??類似的,Send()如果返回WSAEWOULDBLOCK錯誤,我們在OnSend()處等待,Receive()如果返回WSAEWOULDBLOCK錯誤,我們在OnReceive()處等待,以此類推。
    ??還有一點,也許是個難點,那就是在客戶方調(diào)用Connect()連接服務(wù)方,那么服務(wù)方如何Accept(),以建立連接的問題。簡單的做法就是在監(jiān)聽的Socket收到OnAccept()時,用一個新的CAsyncSocket對象去建立連接,例如:

    ?void CMySocket::OnAccept( int ErrCode )
    ?{
    ?????? CMySocket* pSocket = new CMySocket;
    ?????? Accept( *pSocket );
    ?}
    ??? 于是,上面的pSocket和客戶方建立了連接,以后的通信就是這個pSocket對象去和客戶方進行,而監(jiān)聽的Socket仍然繼續(xù)在監(jiān)聽,一旦又有一個客戶方要連接服務(wù)方,則上面的OnAccept()又會被調(diào)用一次。當(dāng)然pSocket是和客戶方通信的服務(wù)方,它不會觸發(fā)OnAccept()事件,因為它不是監(jiān)聽Socket。

    三、CSocket
    ?? CSocket是MFC在CAsyncSocket基礎(chǔ)上派生的一個同步阻塞Socket的封裝類。它是如何又把CAsyncSocket變成同步的,而且還能響應(yīng)同樣的Socket事件呢?
    ??其實很簡單,CSocket在Connect()返回WSAEWOULDBLOCK錯誤時,不是在OnConnect(),OnReceive()這些事件終端函數(shù)里去等待。你先必須明白Socket事件是如何到達這些事件函數(shù)里的。這些事件處理函數(shù)是靠CSocketWnd窗口對象回調(diào)的,而窗口對象收到來自Socket的事件,又是靠線程消息隊列分發(fā)過來的。總之,Socket事件首先是作為一個消息發(fā)給CSocketWnd窗口對象,這個消息肯定需要經(jīng)過線程消息隊列的分發(fā),最終CSocketWnd窗口對象收到這些消息就調(diào)用相應(yīng)的回調(diào)函數(shù)(OnConnect()等)。
    ?? 所以,CSocket在調(diào)用Connect()之后,如果返回一個WSAEWOULDBLOCK錯誤時,它馬上進入一個消息循環(huán),就是從當(dāng)前線程的消息隊列里取關(guān)心的消息,如果取到了WM_PAINT消息,則刷新窗口,如果取到的是Socket發(fā)來的消息,則根據(jù)Socket是否有操作錯誤碼,調(diào)用相應(yīng)的回調(diào)函數(shù)(OnConnect()等)。
    ??大致的簡化代碼為:

    ??BOOL CSocket::Connect( ... )
    ??{
    ???if( !CAsyncSocket::Connect( ... ) )
    ???{
    ????if( WSAGetLastError() == WSAEWOULDBLOCK ) file://由于異步操作需要時間,不能立即完成,所以Socket返回這個錯誤
    ????{
    ?????file://進入消息循環(huán),以從線程消息隊列里查看FD_CONNECT消息,直到收到FD_CONNECT消息,認(rèn)為連接成功。
    ?????while( PumpMessages( FD_CONNECT ) );
    ????}
    ???}
    ??}
    ??BOOL CSocket::PumpMessages( UINT uEvent )
    ??{
    ????? CWinThread* pThread = AfxGetThread();
    ????? while( bBlocking ) file://bBlocking僅僅是一個標(biāo)志,看用戶是否取消對Connect()的調(diào)用
    ????? {
    ????????? MSG msg;
    ????????? if( PeekMessage( &msg, WM_SOCKET_NOTIFY ) )
    ????????? {
    ???????????? if( msg.message == WM_SOCKET_NOTIFY && WSAGETSELECTEVENT(msg.lParam) == uStopFlag )
    ???????????? {
    ???????????????? CAsyncSocket::DoCallBack( msg.wParam, msg.lParam );
    ???????????????? return TRUE;
    ???????????? }?????
    ???????? }
    ???????? else
    ??????? {
    ???????????? OnMessagePending(); file://處理消息隊列里的其它消息
    ???????????? pThread->OnIdle(-1);
    ????????}
    ???? }
    ??}
    ??BOOL CSocket::OnMessagePending()
    ??{
    ????? MSG msg;
    ?????? if( PeekMessage( &msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE ) )
    ?????? { file://這里僅關(guān)心WM_PAINT消息,以處理阻塞期間的主窗口重畫
    ??????? ?? ::DispatchMessage( &msg );
    ??????? ?? return FALSE;
    ?????? }
    ?????? return FALSE;
    ??}

    ?? 其它的CSocket函數(shù),諸如Send(),Receive(),Accept()都在收到WSAEWOULDBLOCK錯誤時,進入PumpMessages()消息循環(huán),這樣一個原本異步的CAsyncSocket,到了派生類CSocket,就變成同步的了。
    ??明白之后,我們可以對CSocket應(yīng)用自如了。比如有些程序員將CSocket的操作放入一個線程,以實現(xiàn)多線程的異步Socket(通常,同步+多線程 相似于?異步 )。

    四、CSocketFile
    ??另外,進行Socket編程,不能不提到CSocketFile類,其實它并不是用來在Socket雙方發(fā)送文件的,而是將需要序列化的數(shù)據(jù),比如一些結(jié)構(gòu)體數(shù)據(jù),傳給對方,這樣,程序的CDocument()的序列化函數(shù)就完全可以和CSocketFile聯(lián)系起來。例如你有一個CMyDocument實現(xiàn)了Serialize(),你可以這樣來將你的文檔數(shù)據(jù)傳給Socket的另一方:

    ?CSocketFile file( pSocket );
    ?CArchive ar( &file, CArchive::store );
    ?pDocument->Serialize( ar );
    ?ar.Close();

    ??同樣,接收一方可以只改變上面的代碼為CArchive ar( &file, CArchive::load );即可。
    ?? 注意到,CSocketFile類雖然從CFile派生,但它屏蔽掉了CFile::Open()等函數(shù),而函數(shù)里僅扔出一個例外。那么也就是說,你不能調(diào)用CSocketFile的Open函數(shù)來打開一個實實在在的文件,否則會導(dǎo)致例外,如果你需要利用CSocketFile來傳送文件,你必須提供CSocketFile類的這些函數(shù)的實現(xiàn)。
    ??再一點,CArchive不支持在datagram的Socket連接上序列化數(shù)據(jù)。

    from: http://www.zahui.com/html/1/2196.htm

    posted on 2006-11-06 09:21 weidagang2046 閱讀(4296) 評論(0)  編輯  收藏 所屬分類: Windows

    主站蜘蛛池模板: j8又粗又长又硬又爽免费视频 | 黄色视屏在线免费播放| 成人毛片18岁女人毛片免费看| 亚洲国产精品久久久久网站 | 亚洲欧洲自拍拍偷午夜色| 亚洲成熟丰满熟妇高潮XXXXX| av无码久久久久不卡免费网站| 亚洲精品视频专区| 啦啦啦完整版免费视频在线观看| 四虎永久在线精品免费影视 | 国产精品区免费视频| 久久久久久久尹人综合网亚洲| 国色精品va在线观看免费视频 | 99热亚洲色精品国产88| 国产猛烈高潮尖叫视频免费| 亚洲日韩在线中文字幕综合 | 亚洲精品天堂在线观看| 免费无码黄十八禁网站在线观看| 国产精品亚洲视频| 精品人妻系列无码人妻免费视频 | xvideos亚洲永久网址| fc2免费人成为视频| 亚洲免费视频网站| 无码视频免费一区二三区| 国产精品亚洲一区二区无码| 国产午夜亚洲精品午夜鲁丝片| 日韩精品无码一区二区三区免费| 黄色网站软件app在线观看免费| 国产亚洲精品岁国产微拍精品| 99re在线这里只有精品免费| 亚洲一日韩欧美中文字幕在线| 免费看国产一级片| 免费网站看av片| 亚洲精品蜜桃久久久久久| 18禁止看的免费污网站| 久久水蜜桃亚洲AV无码精品| 久久久亚洲精品蜜桃臀| 精品香蕉在线观看免费| 一区二区三区免费在线视频 | 精品国产成人亚洲午夜福利| 亚洲av无码天堂一区二区三区|