接 : 一個簡單的IOCP(IO完成端口)服務器/客戶端類(1/2)
5.1 文件傳輸
使用Winsock 2.0的TransmitFile 函數傳輸文件。TransmitFile 函數在連接的套接字句柄上傳輸文件數據。此函數使用操作系統的緩衝管理機制接收文件數據,在套接字上提供高性能的文件傳輸。在異步文件傳輸上有以下幾個重要方面:
l 除非TransmitFile 函數返回,否則不能再對套接字執行 發送 或 寫入 操作,不然會破壞文件的傳輸。在執行PrepareSendFile(..) 函數後,所有對ASend函數的調用都是不允許的。
l 由於系統是連續讀文件數據,打開文件句柄的FILE_FLAG_SEQUENTIAL_SCAN特性可以提高緩存性能。
l 在發送文件(TF_USE_KERNEL_APC)時,我們使用內核的異步程序調用。TF_USE_KERNEL_APC的使用可以帶來明顯的性能提升。很可能(儘管不一定),帶有TransmitFile 的線程的上下文環境的初始化會有沉重的計算負擔;這種情況下可以防止反覆執行APC(異步程序調用)。
文件傳輸的順序如下:服務器通過調用PrepareSendFile(..)函數初始化文件傳輸。客戶端接收到文件信息時,通過調用PrepareReceiveFile(..)函數準備接收,並且給服務器發送一個包來開始文件傳輸。在服務器收到包後,它調用使用高性能的TransmitFile函數的StartSendFile(..)函數傳輸指定的文件。
6 源代碼例子
提供的源代碼是一個模擬客戶端/服務器的例子,它也提供了文件傳輸功能。在源碼中,從類IOCP派生出的類MyIOCP處理客戶端和服務器端的通信。在4.1.1 部分提到了這個虛函數的用法。
在客戶端,或者服務器端的代碼中,虛函數
NotifyReceivedPackage
是重點。描述如下:void MyIOCP::NotifyReceivedPackage(CIOCPBuffer *pOverlapBuff,
int nSize,ClientContext *pContext)
{
BYTE PackageType=pOverlapBuff->GetPackageType();
switch (PackageType)
{
case Job_SendText2Client :
Packagetext(pOverlapBuff,nSize,pContext);
break;
case Job_SendFileInfo :
PackageFileTransfer(pOverlapBuff,nSize,pContext);
break;
case Job_StartFileTransfer:
PackageStartFileTransfer(pOverlapBuff,nSize,pContext);
break;
case Job_AbortFileTransfer:
DisableSendFile(pContext);
break;};
}
這個函數處理進來的消息和遠程連接發送的請求。在這種情形下,它只不過進行一個簡單的回覆或者傳輸文件。源代碼分爲兩部分,IOCP和IOCPClient,
它們是連接的雙方。
6.1 編譯器問題
在使用VC++ 6.0 或者 .NT時,在處理類CFile時可能會出現一些奇怪的錯誤。像下面這樣:
“if (pContext->m_File.m_hFile !=
INVALID_HANDLE_VALUE) <-error C2446: '!=' : no conversion "
"from 'void *' to 'unsigned int'”
在你更新頭文件(*.h),或者更新你的VC++ 6.0版本後,或者只是改變類型轉換錯誤,都可能會解決這些問題。經過一些修改,這個客戶端/服務器的源代碼在沒有MFC的情況下也能使用。
7 注意點和解決規則
在你將此代碼用於其它類型的程序時,有一些編程的陷阱和源代碼有關,使用“多線程編程”可以避免。不確定的錯誤是那些隨時發生的錯誤,並且通過執行相同的出錯的任務的順序這種方式很難降低這些不確定的錯誤。這類錯誤是存在的最嚴重的錯誤,一般情況下,它們出錯是因爲源代碼設計執行的內核的出錯上。當服務器運行多個IO工作線程時,爲連接的客戶端服務,假如編程人員沒有考慮源代碼的多線程環境,就可能會發生像違反權限這種不確定的錯誤。
解決規則 #1:
像下面例子那樣,絕不在使用上下文 “鎖”之前鎖定客戶端的上下文(例如
ClientContext
)之前進行讀/寫。通知函數(像:Notify*(ClientContext *pContext)
)已經是“線程安全的”,你訪問ClientContext
的成員函數,而不考慮上下文的加鎖和解鎖。//Do not do it in this way
// …
If(pContext->m_bSomeData) pContext->m_iSomeData=0; // …
|
// Do it in this way.
//….
pContext->m_ContextLock.Lock(); If(pContext->m_bSomeData) pContext->m_iSomeData=0; pContext->m_ContextLock.Unlock(); //… |
當然,你要明白,當你鎖定一個上下文時,其他的線程或GUI都將等待它。
解決規則 #2:
要避免,或者“特別注意”使用那些有複雜的“上下文鎖”,或在一個“上下文鎖”中有其他類型的鎖的代碼。因爲它們很容易導致“死鎖”。(例如:A等待B,B等待C,而C等待A => 死鎖)。
pContext-> m_ContextLock.Lock();
//… code code ..
pContext2-> m_ContextLock.Lock();
// code code..
pContext2-> m_ContextLock.Unlock();
// code code..
pContext-> m_ContextLock.Unlock();
上面的代碼可以導致一個死鎖。
解決規則 #3:
絕不要在通知函數(像
Notify*(ClientContext *pContext)
)的外面訪問一個客戶端的上下文。假如你必須這樣做,務必使用m_ContextMapLock.Lock();
… m_ContextMapLock.Unlock()
對它進行封裝。如下面代碼所示:ClientContext* pContext=NULL ;
m_ContextMapLock.Lock();
pContext = FindClient(ClientID);
// safe to access pContext, if it is not NULL
// and are Locked (Rule of thumbs#1:)
//code .. code..
m_ContextMapLock.Unlock();
// Here pContext can suddenly disappear because of disconnect.
// do not access pContext members here.
8 下一步的工作
下一步,代碼會被更新,在時間順序上會具有下面的特性:
1. 可以接受新的連接的
AcceptEx(..)
函數的應用將添加到源代碼中,用來處理短時的連接爆炸(short lived connection bursts)和DOS***。2. 源代碼可以很容易的用於其它平臺,像Win32, STL, 和 WTL。
說明:最近比較忙,各種事情應接不暇。終於弄完了,呵呵。
源代碼可以到網上下載,我分析了,很好,可以應用到我的項目中,嘿嘿。