察看本文應用于的產品
文章編號 : 138692
最后修改 : 2005年8月24日
修訂 : 1.1
本文的發布號曾為 CHS138692
本頁
概要
更多信息
BOOL SetTimeOut(UINT uTimeOut)
BOOL KillTimeOut()
BOOL OnMessagePending()
示例代碼
參考
概要
CSocket 操作,如“接收”(Receive)、“發送”(Send) 和“連接”(Connect) 均是阻塞操作,即要等到操作成功執行完畢或套接字上出現錯誤后,對這些函數的調用才有返回結果。
在某些情況下,操作可能永遠不能成功完成,這將導致程序無限循環等待操作完成。一種解決方法是通過編程限制完成操作使用的時間。本文將討論這種方法。
?回到頂端
更多信息
這種方法是設置定時,讓它在操作時間過長時啟動。此方法的關鍵在于處理定時器的方式。雖然操作是“阻塞的”,但仍然可以處理到達的消息。如果通過使用 SetTimer 設置定時器,那么可以查找 WM_TIMER 消息,并在收到該消息時終止操作。該過程中涉及的主要函數有:
Windows API 調用函數:
::SetTimer
MFC 函數:
CSocket::OnMessagePending
CSocket::CancelBlockingCall
為簡單起見,可以在 Csocket 衍生類中封裝該功能。
警告:在進一步閱讀本文之前,請注意在某些 MFC 版本中存在錯誤,會在試圖使用定時器并重疊 OnMessagePending 時引起問題。這一問題將在下面的 Microsoft Knowledge Base 文章中進行討論:
137632 (http://support.microsoft.com/kb/137632/EN-US/) 錯誤:定時器激活時未調用 OnMessagePending
本文僅適用于 Visual C++ 的 1.52、1.52b、2.1 和 2.2 版本。如果使用的是這些 Visual C++ 版本之一,則還需要實施所提供的變通解決方法。
本文最后部分顯示提供這種超時功能的類的示例代碼。以下內容講述由該類實現的函數。
?回到頂端
BOOL SetTimeOut(UINT uTimeOut)
調用此函數之后僅接著調用 CSocket 函數(如 Receive、Send 和 Accept)。uTimeOut 參數是以毫秒為單位指定的。之后,進行定時器的設置。如果設置定時器失敗,那么函數返回 FALSE。有關詳細情況,請參閱 SetTimer 函數的 Windows API 文檔。
?回到頂端
BOOL KillTimeOut()
在完成阻塞操作后,必須調用此函數。此函數刪除用 SetTimeOut 設置的定時器。如果調用 KillTimer 失敗,則返回 FALSE。有關詳細情況,請參閱 KillTimer 函數的 Windows API 文檔。
?回到頂端
BOOL OnMessagePending()
這是一個虛擬回調函數,在等待操作完成時由 CSocket 類進行調用。此函數給您提供處理傳入消息的機會。此實施過程檢查用 SetTimeOut 調用函數設置的定時器的 WM_TIMER 消息。如果收到消息,則調用 CancelBlockingCall 函數。有關 OnMessagePending 和 CancelBlockingCall 函數詳細的信息,請參閱 MFC 文檔。請注意:調用 CancelBlockingCall 函數 將導致操作失敗,而且 GetLastError 函數返回 WSAEINTR(表示操作中斷)。
下面是使用該類的一個例子:
?? ...
?? CTimeOutSocket sockServer;
?? CAcceptedSocket sockAccept;
?? sockServer.Create(777);
?? sockServer.Listen();
?? // Note the following sequence:
?? //? SetTimeOut
?? //?
?? //? KillTimeOut
?? if(!sockServer.SetTimeOut(10000))
?? {
???? ASSERT(FALSE);
???? // Error Handling...for some reason, we could not setup
???? // the timer.
?? }
?? if(!sockServer.Accept(sockAccept))
?? {
???? int nError = GetLastError();
???? if(nError==WSAEINTR)
?????? AfxMessageBox("No Connections Arrived For 10 Seconds");
????? else
??????? ; // Do other error processing.
?? }
?? if(!sockServer.KillTimeOut())
?? {
???? ASSERT(FALSE);
???? // Error Handling...for some reason the timer could not
???? // be destroyed...perhaps a memory overwrite has changed
???? // m_nTimerID?
???? //
?? }
?? ...
?回到頂端
示例代碼
?
?? //
?? // HEADER FILE
?? //
?? class CTimeOutSocket : public CSocket
?? {
?? public:
???? BOOL SetTimeOut(UINT uTimeOut);
???? BOOL KillTimeOut();
?? protected:
???? virtual BOOL OnMessagePending();
?? private:
???? int m_nTimerID;
?? };
?? //
?? // END OF FILE
?? //
?? //
?? // IMPLEMENTATION FILE
?? //
?? BOOL CTimeOutSocket::OnMessagePending()
?? {
???? MSG msg;
???? if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE))
???? {
?????? if (msg.wParam == (UINT) m_nTimerID)
?????? {
???????? // Remove the message and call CancelBlockingCall.
???????? ::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE);
???????? CancelBlockingCall();
???????? return FALSE;? // No need for idle time processing.
?????? };
???? };
???? return CSocket::OnMessagePending();
?? }
?? BOOL CTimeOutSocket::SetTimeOut(UINT uTimeOut)
?? {
???? m_nTimerID = SetTimer(NULL,0,uTimeOut,NULL);
???? return m_nTimerID;
?? }
?? BOOL CTimeOutSocket::KillTimeOut()
?? {
???? return KillTimer(NULL,m_nTimerID);
?? }
CSOCKET的超時設置和UDP發送接收
??? 使用CSoket多次了,但對于它的block模式的理解并不是很深入。昨天使用csoket的udp多發測試(server接到數據后,需要通過某種方式將數據發送到client,使用tcp方式比較可靠,我一直這樣用的,但是比較費時,需要逐一發送),發現了問題:
? 1)create(),sendto(),receivefrom()....
? 2)其中,發送方一直定時發送數據無問題;
? 3)而接收方,通過一個單獨的接收線程實現( 注意:csocket不能跨線程使用!主線程中socket create()后,detach()并將sock作為lpvoid傳入接收線程 )。代碼如下:
? //處理接收數據
UINT CSockSvr::DealSvrRevData(LPVOID lParam)
{
? ......
?DWORD dwError;
?TCHAR cBuff[1000];
?CString sIP;
?UINT uPort;
?for(;!pDlg->m_bExit;)
?{
? ::memset( cBuff,0,sizeof(cBuff) );
? // 如果沒有接到數據,一直等待。。。。
? // 阻塞模式的弊端:::在退出時候,通過CancelBlockingCall
?? int iRst=SockSvr.ReceiveFrom( cBuff,sizeof(cBuff),sIP,uPort,0 );
??? if( iRst!=SOCKET_ERROR )
? {
?? CString sTemp=cBuff;
?? TRACE1( _T("\r\n Rev Data: %s\r\n"),sTemp );
? }
? else
? {
?? dwError=GetLastError();
?? TRACE1( _T("\r\n Rev Data Error code: %d\r\n"),dwError );
? }
? ::Sleep(200);
?}
?
?return 0;
}
? 問題:如果發送方沒有數據,SockSvr.ReceiveFrom()會一致處于等待狀態,導致整個處理接收線程的停止,如果用戶需要退出/結束程序,無法正確釋放資源(無法關閉在線程函數重打開的socket--不能跨線程操作socket,無法關閉線程),造成系統memory leak...
? 針對這種問題,微軟的解決辦法是:http://support.microsoft.com/default.aspx?scid=kb;zh-cn;138692 ,經過測試,在socket進行connect()的時候不好用。后來,結合微軟的辦法,我通過自定義的定時器,實現了block超時間后自動退出的功能〉
? #if !defined(AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_)
#define AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// TimeOutSock.h : header file
//
/*
CSocket 操作,如接收(Receive)、發送(Send) 和連接(Connect) 均是阻塞操作,
?即要等到操作成功執行完畢或套接字上出現錯誤后,對這些函數的調用才有返回結果。
某些情況下,操作可能永遠不能成功完成,這將導致程序無限循環等待操作完成。
?一種解決方法是通過編程限制完成操作使用的時間。本文將討論這種方法。
*/
/////////////////////////////////////////////////////////////////////////////
// CTimeOutSock command target
class CTimeOutSock : public CSocket
{
// Attributes
public:
// Operations
public:
?CTimeOutSock();
?virtual ~CTimeOutSock();
// Overrides
public:
?// ClassWizard generated virtual function overrides
?//{{AFX_VIRTUAL(CTimeOutSock)
?public:
?virtual BOOL OnMessagePending();
?//}}AFX_VIRTUAL
?// Generated message map functions
?//{{AFX_MSG(CTimeOutSock)
? // NOTE - the ClassWizard will add and remove member functions here.
?//}}AFX_MSG
/*
//定時器設置block超時 不是很好用。。。徐衛話 2006.4.19
CSocket 操作,如接收(Receive)、發送(Send) 和連接(Connect) 均是阻塞操作,
?即要等到操作成功執行完畢或套接字上出現錯誤后,對這些函數的調用才有返回結果。
某些情況下,操作可能永遠不能成功完成,這將導致程序無限循環等待操作完成。
?一種解決方法是通過編程限制完成操作使用的時間。本文將討論這種方法。
*/
// 自己計算時間的辦法 OK
public:
???? BOOL SetTimeOut(UINT uTimeOut=1000);
???? BOOL KillTimeOut();
private:
? LONGLONG m_llDtStart;
? UINT? m_uTimeOut;
};
/////////////////////////////////////////////////////////////////////////////
//}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_)
? // TimeOutSock.cpp : implementation file
//
#include "stdafx.h"
#include "NetBroad.h"
#include "TimeOutSock.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CTimeOutSock
CTimeOutSock::CTimeOutSock()
{
}
CTimeOutSock::~CTimeOutSock()
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CTimeOutSock, CSocket)
?//}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CTimeOutSock member functions
//設置超時
BOOL CTimeOutSock::SetTimeOut(UINT uTimeOut)
{??
?//get start cnt
?LARGE_INTEGER llCnt;
?::QueryPerformanceCounter(&llCnt);
?m_llDtStart=llCnt.QuadPart;
?m_uTimeOut=uTimeOut;
???? return TRUE;
}
//刪除超時參數
BOOL CTimeOutSock::KillTimeOut()
{
?m_llDtStart=0;//表明取消計時
?return TRUE;
}
//檢查是否超時間
BOOL CTimeOutSock::OnMessagePending()
{
?// TODO: Add your specialized code here and/or call the base class
?/*
?MSG msg;
? if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE))
? {
?? if (msg.wParam == (UINT) m_nTimerID)
?? {
??? // Remove the message and call CancelBlockingCall.
??? ::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE);
??? CancelBlockingCall();
??? return FALSE;? // No need for idle time processing.
?? };
? };
?*/
?if( m_llDtStart )
?{
? LARGE_INTEGER lldtEnd;
? ::QueryPerformanceCounter(&lldtEnd);?
? LARGE_INTEGER llFrq;
? ::QueryPerformanceFrequency(&llFrq);
? double dbDealy=(double)(lldtEnd.QuadPart-m_llDtStart)*1000/llFrq.QuadPart;
? if( dbDealy>m_uTimeOut )
? {
?? CancelBlockingCall();
?? return FALSE;? // No need for idle time processing.
? }
?}
?
?return CSocket::OnMessagePending();
}
?
? 經過改進后, 對處理接收線成的函數進行了部分改進:
? SockSvr.SetTimeOut(500);? int iRst=SockSvr.ReceiveFrom( cBuff,sizeofcBuff),sIP,uPort,0 );? SockSvr.KillTimeOut();
?當block超過定時后,socket自動退出block,防止接收線程停止。問題終于解決了!
from: http://www.arm8.com/cv/1/6/197.html