全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:2094
推到 Plurk!
推到 Facebook!

封裝了P2P連接與資料傳送過程的DLL正式版-PPQ.DLL wanghao0727(原作)

 
jackkcg
站務副站長


發表:891
回覆:1050
積分:848
註冊:2002-03-23

發送簡訊給我
#1 引用回覆 回覆 發表時間:2002-11-28 02:55:41 IP:61.64.xxx.xxx 未訂閱
此為轉貼資料 http://www.csdn.net/develop/article/14/14333.shtm 封裝了P2P連接與資料傳輸過程的DLL(一) wanghao0727(原作) 關鍵字 P2P DLL 寫在前面的話 PSerSocket.DLL(在下文中將簡稱?DLL)將進行P2P連接時的連接過程和傳送資訊的方法封裝在了DLL裏面,使開發P2P的過程相對簡單化和標準化,因?使用了資料流程的方式來傳輸物件,從而使資料的傳送變得輕鬆和直觀,使開發者不用去解讀一個個的字串,可以充分地利用Windows的非同步消息機制直接去處理一個個的指令物件。 DLL採用了自定義的一套資料流程方式來傳送和合成指令物件,而沒有使用VC的序列化物件,避免了VC序列化物件不能在不同語言平臺上合成的問題。DLL的資料流程物件可以在VC和JAVA之間自由傳輸,從而使開發者不用去處理多平臺傳輸時的複雜問題。 DLL封裝了建立連接和傳送資訊的細節,使不同的開發者開發的P2P?品互相連接成?了可能,即使於國際標準化的接軌,也只需要重新下載新的DLL就可以啦,而不需要每個開發者都去改寫程式。隨著DLL的新的功能的增加與完善,每個開發者都可以直接地將這些功能集成進原有的軟體中,將大大地加快開發的速度和可靠性。 DLL具有非常強大的擴展性,它通過回調函數和消息與外部進行交互,可以使開發者隨時得到程式當前運行情況的通報。 P2P網路的建立,不是一個或幾個公司能夠完成的,只有全體開發者的共同努力,才可以建立起中國自己的P2P網路。 使用DLL來建立連接所需要的步驟 使用DLL來建立一個聊天連接或傳送、接收文件的連接是非常容易的,你只需按以下步驟就可以實現: 1 初始化DLL。通過調用以下方法和設置屬性來實現。 PDefine::SInitSocketStream(); PThreadParm::SCreateTCPListenPort(1500); //1500是你自己的監聽埠號。 PDefine::cSMsgInfo.pWnd=this; PDefine::cSMsgInfo.bIsAutoDel=false; PDefine::pSGetFriendCallBackFun=CPPSerDlg::SGetFriendFromID; /* 一個回調函數,是CPPSerDlg類中的一個靜態方法。DLL通過向這個回調函數傳遞一個好友的ID號,來得到該好友的PFriend物件的指標。 在測試時,你可以固定返回一個已創建好的PFriend物件的指標。 */ PDefine::pSGetFileNCallBackFun=CPPSerDlg::SGetFileNameByFileID; /* 一個回調函數,是CPPSerDlg類中的一個靜態方法。DLL通過向這個回調函數傳遞一個文件的數位標識,來得到具體的檔案名稱,包含路徑和副檔名,是你想傳送給被連接方的檔案名稱。 在測試時,你可以固定返回一個進行傳輸測試的文件。這個文件最好有幾十兆,因?下載速度會非常快。 */ 2 在你的視窗中回應2個消息。 CREATE_NEW_OBJ_MSG //在消息的回應函數中,直接調用PDefine::SCreateNewObj(wParam,lParam)靜態方法。 CREATE_NEW_THREAD_MSG //在消息的回應函數中,直接調用PDefine::SCreateNewThread(wParam,lParam)靜態方法。 3 創建一個PFriend物件,填充PFriend物件的三個屬性: m_lpszHostAddress; //被連接方的主機位址。 m_nHostPort; //被連接方的主機的監聽埠。 m_sFriendID; //好友的ID,在測試時你可以隨便設定一個12位元長的字串。 4 創建一個PRecvInfo結構。(如果你想創建一個聊天連接,直接跳到第6步。) PRecvInfo* prf=new PRecvInfo(); prf->szBlockMap=NULL; //固定賦值?NULL。 prf->nFileLen=110273244; //想下載的文件的總的位元組數。 prf->nSplitNo=30; //將文件分割成多少個塊下載,取值從1..999。 prf->sSaveName="e:\\test\\cs1004"; //被保存的檔案名,不含副檔名。 prf->sExpendName=".exe"; //文件的副檔名。 5 創建一個PThreadParm物件,來建立接收文件的過程。 PThreadParm* ptp=new PThreadParm(); ptp->SetMaxThreadNo(5); //設定下載時的最大線程個數。 ptp->m_pRecvInfo=prf; //前面創建的PRecvInfo結構的指標。 ptp->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789"); //該連接的PFriend物件的指標。 //以接收的身份開始下載。 ptp->StartThread(RECV_TYPE); //以接收的身份開始下載。 以上過程,就可以建立一個與PFriend物件所指定方的連接,並且完成一個文件的下載。 6 創建一個PThreadParm物件,來建立一個聊天連接。 PThreadParm* ptp=new PThreadParm(); ptp->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789"); //該連接的PFriend物件的指標。 ptp->StartThread(CHAT_TYPE); //創建一個聊天連接。 應該可以看出來,創建連接的過程是如此的簡單。當然,上面的過程省略了很多的細節,使你無法回應DLL發出的通知消息。 這是給開發者使用的,因此DLL提供了大量的介面,並且在運行過程中,你也需要回應一些消息,去設定一些參數。下面我將一步步地介紹DLL中定義的類以及消息的使用方法。 DLL的工作方式 DLL通過回調函數和消息與外部進行交互,要想正確地使用這個DLL,就需要瞭解DLL中定義的4個類,以及回調函數和結構,瞭解DLL向外部視窗發送的每個消息的含義。 DLL中定義的類 DLL中定義的類共有四個:PDefine、PFriend、PBaseAct、PThreadParm。 PDefine 定義了需要使用的結構、回調函數、常量以及DLL定義的一些消息和靜態方法。這個類不需要去創建實例,它裏面的都是靜態方法、屬性和一些定義。 PFriend 想與之建立連接的一方都稱?好友,每一個好友都必需有一個PFriend物件與之對應,在這個物件中記錄了這個好友的唯一標識、他的IP地址、監聽埠等相關資訊。 PBaseAct 是所有可以轉變?資料流程進行傳輸的指令物件的基類,用戶需要自定義傳輸物件時,都需要從該類來派生出新的類。 PThreadParm 描述並記錄了一個或一組連接,是創建連接時必需使用的類。 只要撐握了這4個類,你就撐握了這個DLL。 如何使用這個DLL呢? 下面是使用這個DLL的實例,具體程式可以參照隨包發送的PPSer專案。 第一步:初始化DLL。 通過調用PDefine類中的靜態方法,以及設定一些靜態屬性的值來完成DLL的初始化工作。 1 PDefine::SInitSocketStream(); 2 PThreadParm::SCreateTCPListenPort(1500); 3 PBaseAct::SSetUserGetObject(CPPSerDlg::UserGetObject); 4 PDefine::cSMsgInfo.pWnd=this; 5 PDefine::cSMsgInfo.bIsAutoDel=false; 6 PDefine::pSGetFriendCallBackFun=CPPSerDlg::SGetFriendFromID; 7 PDefine::pSGetFileNCallBackFun=CPPSerDlg::SGetFileNameByFileID; 第1行語句用來初始化WinSock,使建立Socket連接成?可能。這句必需首先調用。 第2行用來創建一個TCP監聽埠,方法的參數用來指定想創建的監聽埠號。 第3行設定DLL所需要使用的回調函數的指標。回調函數在PDefine中被定義。這個回調函數的類型?GOCALLBACK,返回值?CRuntimeClass*。它在PDefine中被聲明?: typedef CRuntimeClass* (WINAPI *GOCALLBACK)(BYTE nType,LPCTSTR szVersion); 它通過傳遞的2個參數來返回指定物件的CRuntimeClass*。在這裏,這個函數被定義?CPPSerDlg中的一個靜態方法,它在CPPSerDlg.h文件中的定義如下: static CRuntimeClass* WINAPI UserGetObject(BYTE nType,LPCTSTR szVersion); 關於這個函數體的實現,將在後面介紹。 第4行填充一人PRecvInfo結構中的值,這個結構保存了回應消息的視窗的指標,結構在PDefine中被定義。PDefine::cSMsgInfo變數中保存的視窗指標,是指向用來回應DLL內的一些關鍵消息的視窗的指標。 第5行表示該結構是否會被DLL自動刪除,指定?false表示不會被DLL自動刪除。 第6行設定一個回調函數的指標,這個函數的類型?GETPALCALLBACK,返回值?PFriend*。它在PDefine中被聲明?: typedef PFriend* (WINAPI* GETPALCALLBACK)(LPCTSTR szFriendID); 這個函數根據傳遞的好友的ID號來返回一個PFriend*物件的指標。在這裏,這個函數被定義?CPPSerDlg中的一個靜態方法,它在CPPSerDlg.h文件中的定義如下: static PFriend* WINAPI SGetFriendFromID(LPCTSTR sID); 關於這個函數體的實現,將在後面介紹。 第7行設定一個回調函數的指標,這個函數的類型?GETFILENCALLBACK,返回值無。它在PDefine中被聲明?: typedef void (WINAPI *GETFILENCALLBACK)(int nFileID,PFriend* pFriend,CString &sFileN); 這個函數根據傳遞的nFileID的文件數位標識,來返回一個nFileID所對應的實際的檔案名。在這個函數裏,你可以通過傳遞的第二個參數pFriend來得知想獲得該文件的好友的ID,校驗對方是否有許可權來獲取該文件,如果有許可權獲得,則將實際的檔案名填充在第三個參數sFileN中;否則設定sFileN=""。它在CPPSerDlg.h文件中的定義如下: static void WINAPI SGetFileNameByFileID(int nFileID,PFriend* pFriend,CString &sFileN); 關於這個函數體的實現,將在後面介紹。 第二步:創建好友物件PFriend 每一個PFriend物件都表示一個能夠建立連接的好友,在這個物件裏你必需填寫好友的ID號、IP地址和監聽埠號,然後將這個PFriend物件放入一個List或Map中保存起來。 在CPPSerDlg::SGetFriendFromID(LPCTSTR sID)方法中,你需要在保存PFriend物件的List或Map中檢索到該物件,然後返回PFriend*;如果沒有查到,則返回NULL。 在監聽埠接收到一個連接請求後,會與請求連接一方進行握手,以確認雙方的身份。在這個握手的過程中,會調用SGetFriendFromID()這個回調函數,以確認對方是否夠進行連接,如果返回NULL,則認?對方不具備建立連接的許可權,則會主動斷開連接。 如果你希望回應任何一個連接請求,你需要在檢索時如果沒有查找到PFriend物件,則動態地創建一個PFriend物件,然後加入列表中。 第三步:創建一個接收文件的結構PRecvInfo,適用於連接類型?RECV_TYPE(如果你建立的連接類型?SEND_TYPE,請跳過這步,如果你想建立的連接類型?CHAT_TYPE,請直接跳到第7步) PRecvInfo* prf=new PRecvInfo(); prf->szBlockMap=NULL; //固定賦值?NULL,DLL將填充具體的值。 prf->nFileLen=110273244; //想下載的文件的總的位元組數。 prf->nSplitNo=30; //文件將被分割的塊數。 prf->sSaveName="e:\\test\\cs1004"; //下載文件被保存的檔案名,包含路徑,但不含副檔名。 prf->sExpendName=".exe"; //下載文件的副檔名。 nFileLen表示想下載的文件的大小,這個位元組數你需要通過其他方式來獲得,在創建一個PRecvInfo結構時,你必需填寫一個正確的值。 nSplitNo表示文件被動態分割成多個塊來下載。你可以將一個大的文件動態地分割成若干個小的塊來下載,然後使用一個合成函數將分割下載完的文件合併成一個文件。使用這種方法是?了回應以後的擴展,在後面將提供對分割塊進行下載後校驗的方法,如果某一個下載塊出現問題,可以只傳送這個下載塊。不使用一個大文件的另外一個原因是?了避免在建立一個大文件時?生的一個延時問題,在以後的擴展中,可以把一個大的文件的分割塊保存在不同的硬碟上。 sExpendName表示想下載的文件的副檔名,這個副檔名應該是從傳送方得到的,它和傳送方傳送的文件的副檔名是相同的,接收方不能去修改這個值。 第四步:創建一個PThreadParm物件,?動接收文件。 PThreadParm* ptp=new PThreadParm(); ptp->SetMaxThreadNo(5); //設置該次下載任務的所同時開?的最大線程個數?5個。 ptp->m_dwIdentify=(DWORD)ptp; //PThreadParm的數位標識,可以是指向該物件的位址。 ptp->nFileID=150; //表示接收文件的數位標識。 ptp->m_pRecvInfo=prf; //指向接收結構PRecvInfo的指標。 ptp->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789"); ptp->m_pMsgInfo=&PDefine::cSMsgInfo; //接收傳遞消息的PMsgInfo結構。 ptp->StartThread(RECV_TYPE); //?動一個接收線程。 PThreadParm中的屬性m_dwIdentify表示該物件的數位標識,通過這個數位標識你應該可以查找到該PThreadParm物件,並且能夠明確地知道該物件是否存在。因?這個標識的值將作?大部分傳遞給m_pMsgInfo所指定的視窗的消息的lParam參數。 你可以建立一個List或Deque來保存每個PThreadParm的值,一旦這個物件被delete,則從List或Deque中取消,當接收到消息時,你可以在List或Deque中去檢索這個值,如查存在,表示物件可用;否則,不用回應該消息,簡單的返回0即可。 第五步:在PDefin::cSMsgInfo結構中指定的表單中回應2個消息。 CREATE_NEW_OBJ_MSG 在這個消息的回應函數中直接調用PDefine::SCreateNewObj(wParam,lParam)靜態方法,傳遞消息回應函數的2個參數wParam和lParam即可,其他不必作任何事情。 CREATE_NEW_THREAD_MSG 這個消息的處理較?複雜,也比較關鍵。其中lParam參數中保存的是新創建或已經存在的PThreadParm物件的指標。你可以通過判斷PThreadParm->m_nIdentify==0來得知該PThreadParm物件是否已設定了標識。如果值?0,你需要?這個物件設定一個唯一的標識。而且你還需要去判斷物件的類型,如果類型?RECV_TYPE,則需要根據pParm->m_nFileID來填充PRecvInfo結構,這時候,你是接收文件的一方,這次的連接,有可能是對方主動向你傳送一個文件,或者對方在防火牆內,而你在防火牆外,因此由對方進行一個主動連接,而這個m_nFileID所表示的文件ID正是你請求對方傳送的文件的數位標識。想完成一個接收的連接,你必需設定PRecvInfo結構中相應的屬性。另外,你還需要去設定m_pMsgInfo或m_pCallBackFun的值,以使物件能?生回調。 最後在消息的回應函數中調用PDefine::SCreateNewThread()靜態方法,傳遞wParam和lParam參數。 該消息的回應函數參考方法如下: { PThreadParm* pParm=(PThreadParm*)lParam; if(pParm->m_dwIdentify==0) { //如果是第一次創建該物件,即該物件還沒有得到標識。 //將PThreadParm物件的指標作?標識傳回。 pParm->m_dwIdentify=lParam; pParm->m_pMsgInfo=&PDefine::cSMsgInfo; //將該物件放入一個佇列中。 //如果物件類型?接收類型,則需要根據pParm->m_nFileID來填充PRecvInfo結構。 if(pParm->GetThredType()==RECV_TYPE) { PRecvInfo* pRecvInfo=new PRecvInfo(); pParm->m_pRecvInfo=pRecvInfo; //在下面填充結構。 //pRecvInfo->nFileLen= //pRecvInfo->nSplitNo= //pRecvInfo->sExpendName= //pRecvInfo->sSaveName= } } PDefine::SCreateNewThread(wParam,lParam); return 0; } 第六步:完成在初始化DLL的第7行中設定的回調函數。 void WINAPI SGetFileNameByFileID(int nFileID,PFriend* pFriend,CString &sFileN) { //進行校驗,是否是指定的好友,如果不是,則不允許下載。 if(pFriend->m_sFriendID=="PPQ123456789") sFileN="e:\\cs1004.exe"; else sFileN=""; } 第七步:創建一個聊天的PThreadParm則要簡單的多,你只需要指定PFriend物件和PMsgInfo結構的指標。 PThreadParm* pt=new PThreadParm(); pt->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789"); PMsgInfo* pmi=new PMsgInfo(); pmi->pWnd=this; pmi->bIsAutoDel=true; //?這個PThreadParm單獨創建的PMsgInfo結構,在PThreadParm刪除時,也自動刪除該PMsgInfo結構。 pt->m_pMsgInfo=pmi; //你也可以直接將PDefine::cSMsgInfo的位址賦給該變數。 pt->StartThread(CHAT_TYPE); 經過以上的步驟,你已經完成了所有必需設定的回調函數,並且對如何使用DLL的流程也有了一定的認識。在後面將繼續介紹如何回應DLL的消息,以及如何自定義傳輸的指令物件。 關於PBaseAct派生類的說明: 在StdAfx.h文件中增加對PBaseAct派生類的類型的定義: enum { SELF1_OBJ=USER_OBJ, //定義用戶類型標識?SELF1_OBJ SELF2_OBJ, //在後面可加入更多的類型標識。 //USER_OBJ是用戶自定義派生類可以使用的最小類型標識編號。 //你可以創建的派生類的最大標識號?255,類型標識是用位元組來表示的。 } PBaseAct的派生類必需重載以下三個方法: virtual void SelfSerialize(CString& src) { PBaseAct::InitUniteValue(src); //該方法的第一句必需調用該方法。 //加入自對該類屬性的序列化。 //重復調用靜態方法void SUniteValue(src,LPCTSTR value), //將想序列化的屬性依次傳送,value是想傳送的屬性的字串(LPCTSTR)或整型(int)表示。 PBaseAct::SelfSerialize(src); //該方法的最後一句必需調用基類的方法。 } virtual void InitObject(LPVOID data); { PBaseAct::InitObject(data); //該方法的第一句必需調用基類的方法。 //按照你在SelfSerialize()方法中序列化屬性時的順序, //依次調用靜態方法int SGetValue(data,value) //其中的value是屬性的變數名稱,可以是一個CString或int類型的變數。 //PBaseAct中有2個SGetValue()方法,一個是傳遞字串的值,一個是傳遞int的值, //實際上都是調用得到字串的方法,然後再函數內部進行了轉換。 //SGetValue()方法會返回當前屬性的順序號。順序號從3開始計數。 //順序號的0是傳送者ID,1是接收者ID,2是該物件的版本號。在基類的方法中會自動分解得到這些值。 } virtual BYTE GetObjType() { return SELF1_OBJ; //在Self1Act派生類中返回。 //在Self2Act中則返回 return SELF2_OBJ。 } 如何在接收過程中處理自定義的PBaseAct派生類物件? 下面以CPPSerDlg中的靜態方法?例:(具體程式請參考PPSer例副程式) 1 必需建立以下函數或類中的靜態方法: 在CPPSerDlg.h文件中增加public方法: static CRuntimeClass* WINAPI UserGetObject(BYTE nType,LPCTSTR szVersion); static BOOL WINAPI ParseUserObj(WPARAM wParam,LPARAM lParam); 在CPPSerDlg.cpp文件中對相應方法定義如下: CRuntimeClass* WINAPI CPPSerDlg::UserGetObject(BYTE nType,LPCTSTR szVersion) { //szVersion變數中保存了該物件的版本號,可以通過檢測版本號來判斷是否 //是你所要求的物件。 if(szVersion=="#MySelfDefineObj" { switch(nType) { case SELF1_OBJ: return RUNTIME_CLASS( Self1Act ); //Self1Act?自定義的PBaseAct的派生類。 case SELF2_OBJ: return RUNTIME_CLASS( Self2Act ); } } else if(szVerion=="#User1DefineObj") { //加入對其他用戶創建的物件的支援。 switch(nType) { case SELF1_OBJ: return RUNTIME_CLASS( Self1Act ); //Self1Act?自定義的PBaseAct的派生類。 case SELF2_OBJ: return RUNTIME_CLASS( Self2Act ); } } return NULL; } BOOL WINAPI CPPSerDlg::ParseUserObj(WPARAM wParam,LPARAM lParam) { //其中wParam的低8位元表示該物件的類型標識。wParam的高位暫時保留,可能以後會使用。 //lParam是指向一個類型標識的物件的指標,可通過強制轉換符轉換成物件的指標。 //以Self1Act?例: BYTE nType=(BYTE)wParam; PBaseAct* pa=(PBaseAct*)lParam; //如果使用了版本號,還要注意版本號的區別。pa->m_sVersion內保存有物件的版本號。 switch(nType) { case SELF1_OBJ: Self1Act* sa=(Self1Act*)pa; //在下面對該接收到的該指令進行處理。 break; case SELF2_OBJ: Self2Act* sa=(Self2Act*)pa; //在下面對該接收到的該指令進行處理。 break; } } 2 建立自已的PBaseAct派生類還必需重載下面這個方法: virtual GETCALLBACK* GetParseActFunPointer() { return CPPSerDlg::ParseUserObj; } 你也可以?每一個派生類指定一個不同的處理函數,這時無需比較類型, 每個派生類的處理函數都是該派生類中的一個靜態方法。 3 需要調用一個方法設定回調物件。 PBaseAct::SetUserGetObject(CPPSerDlg::UserGetObject); 這個調用應該加在主程序的初始化階段。 4 也可以設定一個視窗控制碼和自定義一個消息,然後由程式將該消息發送到指定視窗。 在派生類中定義一個靜態的PMsgInfo結構,然後重載PBaseAct中的方法GetCallBackMsg() PMsgInfo* GetCallBackMsg() { return <指向PMsgInfo變數的指標>; } 5 不管是發送或者是由接收所?生的指令物件,都可以修改物件中屬性的值後,重復發送,這個是和C 的物件流是不一樣的。 6 每一個開發者都可以定義自已的派生類,但是派生類的標識都是從USER_OBJ來開始的,這樣如何區分是你自已的派生類還是其他開發者的派生類呢? 在PBaseAct的基類中,定義了一個字元型變數m_sVersion,在這個變數中保存了用戶的版本號(關於如何創建自已的版本號,還沒有確定),這個版本號暫時沒有長度的限制,?了不使你自己的派生類和其他開發者的派生類相混淆,你可以定義的足夠長,例如:由16位元字元組成,使用的字元可以是a..z、A..Z、0..9、_和 、- 、#符號,請不要使用其他的符號。 版本號使程式不但可以支援開發者自己創建的物件,而且可以支援其他開發者創建的物件,而不需要修改程式。 開發者可以將自己定義的用戶物件和指令解析程式打包成一個DLL,直接發佈出來,隱藏具體的實現細節,提供足夠的介面,使其他開發者可以直接調用這些物件來完成指定的功能。在發佈自己的派生類物件時,必需提供一個唯一的版本號。 看完以上的內容,你應該已經很清楚如何創建自己的派生類啦,每一個派生類都可以表示一個或一組指令,這個指令物件不象VC的序列化物件,它可以被重復使用,如果你修改了物件的值,被修改過的值會被發送出去。當然,這個物件流還不如VC的序列化物件的功能強大,物件中暫時還只能包含一些簡單的類型,如CString、LPCTSTR、int這三種類型,而且物件的最大長度不能超過16850個位元組。我將在下一版本中,改進物件流,使其能夠支援無限大資料,並且會將語言傳輸與接收集成進來。希望大家有什?意見,可以隨時給我發信。 DLL的消息 以下是用戶必需回應的消息,回應這些消息的視窗被定義在PDefine::cSMsgInfo結構中。 CREATE_NEW_OBJ_MSG=29600, 在消息函數執行體中直接調用PDefine::SCreateNewObj()靜態方法,傳遞wParam和lParam參數。其他不必作任何事情。 CREATE_NEW_THREAD_MSG, lParam參數中保存的是新創建或已經存在的PThreadParm物件的指標。你可以通過判斷PThreadParm->m_nIdentify==0來得知該PThreadParm物件是否已設定了標識。 如果值?0,你需要?這個物件設定一個唯一的標識。而且你還需要去判斷物件的類型,如果類型?RECV_TYPE,則需要根據pParm->m_nFileID來填充PRecvInfo結構。 另外,你還需要去設定m_pMsgInfo或m_pCallBackFun的值,以使物件能?生回調。 具體方法請參看實例。 最後在消息的回應函數中調用PDefine::SCreateNewThread()靜態方法,傳遞wParam和lParam參數。 你接收到一個PThreadParm物件工作結束的消息時,你可以決定是否要繼續使用該物件,如果想繼續使用該物件,則先調用InitValue()方法,InitValue()不會改變原有的賦值,如果你想繼續工作,則不用改動已經賦過值的屬性,而直接調用StartThread()方法。 如果你想完成其他的工作,你必需要設定相應的屬性的值,就象你新創建了一個該PThreadParm物件一樣,然後再調用StartThread()方法。 如果不想繼續使用該物件,你需要delete這個物件。 如果你在delete這個物件這前,不希望物件去刪除PRecvInfo物件的指標,你可以保存該物件的指標,然後設定m_pRecvInfo=NULL。 ALL_CONNECT_END_MSG, 一個PThreadParm的所有的連接請求都已終止,詢問該如何處理。 lParam保存的是指向PThreadParm的指標。 MISSION_END_MSG, 指示一個PThreadParm結束了工作,但它的任務還沒有完成。 lParam保存的是指向PThreadParm的指標。 終止工作的原因可能是用戶發出的中斷,或在傳送過程中連接出現了問題。 MISSION_COMPLETED_MSG, 指示一個PThreadParm任務完成。 lParam保存的是指向PThreadParm的指標。 //以下是用戶可以回應的消息 CHAT_CONNECT_CREATED_MSG, 一個聊天Socket被建立,並且已經連接到了好友物件上。 lParam參數指向擁有該聊天連接的PFriend物件的指標。不要刪除該指標。 CHAT_CLOSED_MSG, 一個聊天Socket被關閉。 lParam參數指向擁有該聊天連接的PFriend物件的指標。不要刪除該指標。 ONE_RECV_THREAD_END_MSG, 一個接收線程結束。 lParam參數保存的線程所在的PThreadParm中的m_dwIdentify屬性的值。 wParam參數的低8位表示該線程在PThreadParm線程組中的標識號。 ONE_SEND_THREAD_END_MSG, 一個傳送線程結束。 lParam參數保存的線程所在的PThreadParm中的m_dwIdentify屬性的值。 wParam參數的低8位表示該線程在PThreadParm線程組中的標識號。 THREAD_MAX_NO_CREATED_MSG, 已經創建了最大的線程個數。 RECV_NEW_BLOCK_MSG, 接收線程準備接收一個新分配的塊號。 lParam參數保存的線程所在的PThreadParm中的m_dwIdentify屬性的值。 wParam參數的低8位表示該線程在PThreadParm線程組中的標識號。 你可以調用GetThreadIDInArray((BYTE)wParam)來獲得該線程所在的PThreadID物件的指標。 SEND_NEW_BLOCK_MSG, 傳送線程準備傳送一個新的部分。 lParam參數保存的線程所在的PThreadParm中的m_dwIdentify屬性的值。 wParam參數的低8位表示該線程在PThreadParm線程組中的標識號。 你可以調用GetThreadIDInArray((BYTE)wParam)來獲得該線程所在的PThreadID物件的指標。 RECV_DATA_MSG, 接收到了新的資料資訊。在消息的回應函數中,你需要設定PThreadParm中的屬性 m_bIsSendMsg=!m_bIsSendMsg,否則無法接收到新的消息。 在PThreadParm中的m_dwCompletedLen屬性中保存的是總的完成的位元組數。 lParam參數保存的線程所在的PThreadParm中的m_dwIdentify屬性的值。 wParam參數的低8位表示該線程在PThreadParm線程組中的標識號。 你可以調用GetThreadIDInArray((BYTE)wParam)來獲得該線程所在的PThreadID物件的指標。 SEND_DATA_MSG, 同RECV_DATA_MSG消息。 由DLL發送的消息還將繼續擴充。對於DLL的使用就暫時介紹到這裏啦,在隨包發送的頭文件中都有相應的說明,並且隨包發送了一個測試的例子,這個例子在PPSer目錄下。PPSer專案中的PChatAct是一個用戶自定義物件的實例。 在CPPSerDlg.cpp文件中的SGetFileNameByFileID()函數體內,將sFileN="e:\\cs1004.exe"這一句改成你想測試下載的已存在的檔案名。 在CPPSerDlg.cpp文件中的OnTest()函數體中,是如何創建一個接收線程和一個聊天線程的例子,文件發送方和接收方都是用戶自己的機器,修改其中的PRecvInfo結構中prf->sSaveName="e:\\test\\cs1004"這一句,可以改變下載後文件被保存的位置和檔案名,改成自己機器上相應的目錄和檔案名。修改prf->nFileLen=110273244這一句?你想下載的文件以位元組?單位的大小。 重新編譯程序,然後點擊Test按鈕,就會自動開始下載,點擊StartThread按鈕,將發送一條聊天消息,發送的物件是PChatAct,點擊StopThread按鈕,將中止下載線程。重新點擊Test按鈕,將進行中斷點續傳下載。 注意:點擊Test按鈕後,除非點擊StopThread按鈕,否則不要重新點擊Test按鈕,否則下載會不正常。 聊天連接如果在一段時間內沒有發送或接收消息,將自動斷開連接,以釋放資源。 你可以同創建多個PThreadParm物件,然後調用物件的StartThread()方法進行連接。DLL會將你請求的連接放入一個佇列中,迴圈進行連接測試,如果成功,則?動線程或建立聊天連接;如果不成功,會將連接請求重新放入佇列的尾部,然後從佇列的頭部取出一個連接請求,償試進行連接。 DLL充分利用了Windows的非同步消息機制,只有連接被建立,並且經過校驗成功後,才會創建新的線程,而並不會?每個連接都去創建一個線程。 當一個線程組?動後,會依次請求創建屬於線程組的多個線程,這些連接請求具有較高的優先順序,會優先於其他的連接請求而首先處理。 現在只是DLL的第一個版本,在下一個版本中,我會在DLL中集成語音傳輸,使開發者可以利用DLL直接開發出具有語音聊天功能的軟體。 希望大家將使用DLL開發過程中碰到的任何問題或意見及時的通知我,也希望更多的朋友能加入這個開發行列,共同探討這個話題。 email:wanghao0727@sohu.com
------
**********************************************************
哈哈&兵燹
最會的2大絕招 這個不會與那個也不會 哈哈哈 粉好

Delphi K.Top的K.Top分兩個字解釋Top代表尖端的意思,希望本討論區能提供Delphi的尖端新知
K.表Knowlege 知識,就是本站的標語:Open our mind
jackkcg
站務副站長


發表:891
回覆:1050
積分:848
註冊:2002-03-23

發送簡訊給我
#2 引用回覆 回覆 發表時間:2002-11-28 02:57:40 IP:61.64.xxx.xxx 未訂閱
太長了改貼於 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=23326 發表人 - jackkcg 於 2002/11/28 03:11:50
------
**********************************************************
哈哈&兵燹
最會的2大絕招 這個不會與那個也不會 哈哈哈 粉好

Delphi K.Top的K.Top分兩個字解釋Top代表尖端的意思,希望本討論區能提供Delphi的尖端新知
K.表Knowlege 知識,就是本站的標語:Open our mind
jackkcg
站務副站長


發表:891
回覆:1050
積分:848
註冊:2002-03-23

發送簡訊給我
#3 引用回覆 回覆 發表時間:2002-11-28 03:01:30 IP:61.64.xxx.xxx 未訂閱
此為轉貼資料 封裝了P2P連接與傳輸過程的DLL最新版--支援語音傳輸 wanghao0727(原作) 關鍵字 P2P,DLL 封裝了P2P連接與傳輸過程的DLL--最新改進版 在DLL的前一版本中,採用的是CString來保存資料,但CString在頻繁進行刪除和添加操作時,效率太低啦,在新發佈的DLL中,去掉了所有的CString,在原來使用CString的地方,使用了一個新的類CStringEx來代替,並且改進了接收與傳送的過程,使原來的接收、複製、再複製過程,直接由一個接收過程來完成,使效率提高了60%,並節省了資源。 對資料的發送過程,也進行了大的改動,明顯提高了效率,尤其是對大資料流程的傳送和接收作了特別的優化。 改進以後的最大差別是消除了原先資料流程大小不能超過18000位元組的限制,資料流程的大小可以無限大,並且支援二進位流的傳送,比如直接傳送音頻和視頻緩衝區,將二進位流的緩衝區直接賦給CStringEx,或直接使用CStringEx來代替原先的緩衝區,你就可以直接傳送緩衝區中的資料。 在新的DLL中,?了適應將來實現IP多播,取消了原先在PBaseAct基類中定義的m_sReceiver欄位(即接收者ID),在物件的序列化方法進行了一些改動,具體改動可以參考在常式中的PChatAct類的定義。 CStringEx將作?DLL提供的第5個類出現。CStringEx可以實現CString的大多數功能,?了方便,僅重載了 =、=操作符,可以直接賦值或加上一個int、LPCTSTR類型的值。另外它對Delete(0,nCount)方法作了特別優化,使效率提高了100%,使進行該操作的時間?常數n,而原先CString的Delete(0,nCount)操作的時間?n*len,len?CString的長度。另外CStringEx還支援二進位流緩衝的增加。 通過對新版DLL的發佈,應該可以看出,對傳輸與接收過程的改動,對資料流程本身的改動,僅是針對於DLL內部,而對於使用DLL的開發者來講,完全不用改動自己的程式,就可以直接享受到DLL改動所帶來的傳輸與接收效率的提高。 在下一版的DLL中,我將加入音頻的傳送與重播,實現語音聊天。在下下一個版本中,我將加入對防火牆的突破(目前僅限於支援Socket5協定的防火牆)。 希望大家在使用後能將自己的使用心得寫出來,讓更多的人瞭解和使用DLL來開發,更希望大家能提出你對DLL的發展的看法和意見。 封裝了P2P連接與傳輸過程的DLL最新版--支援語音傳輸 經過了幾天的努力,語音傳輸終於被集成到了DLL中,因?我僅在2台機器上進行了測試,效果如何,還希望大家在使用後能及時的告訴我。 聲音的捕捉和重播採用了DirectSound,在捕捉和重播時都採用了DirectSound的Notify功能來讀寫緩衝區,使聲音能夠較流暢地表現出來。聲音資料被捕捉後,保存在了一個指令的CStringEx物件中,然後傳輸到連接方,連接方接收到指令後,寫入重播緩衝區中,從而完成整個聲音的傳送過程。 在這個版本裏,我還沒有作得很完善,並沒有對每次傳送的聲音資料進行格式校驗,因此連接雙方必需使用相同的格式(即採樣速率、8/16位元、單聲道或身歷聲)來捕捉和重播聲音,否則聲音可能會變調。 在你使用缺省值(即22.05KHz,8位元,單聲道)方式進行聲音捕捉和重播時,每秒傳送的資料量約?22K,這個資料量對於56K的Modem也應該可以承受。因?我用的是寬帶,因此無法在低帶寬的網路中進行測試,希望測試過的朋友能告訴我結果。 另外,在這個版本裏,我作了一個小的償試,並沒有象傳統的語音傳輸程式在整個的對話過程中,不斷地傳送聲音,而不管你是否在說話。我在DLL里加了一小段濾音程序,如果聲音捕捉時沒有有效地聲音資料,就不會再向對方傳送資料,只有當出現有效資料時,才會向對方傳送聲音指令。我只在自己的機器上作了測試,不知道效果如何,希望大家使用後,能告訴我使用後的效果。 如何?動聲音傳輸呢? 非常簡單,只需要調用幾個在PDefine類中定義的靜態方法,你就可以完成整個語音的傳輸過程,擁有一部IP_Phone。請按照以下幾個步驟來?動聲音傳輸: 1 給PDefine::cSMsgInfo結構賦值。 2 創建PFriend物件並于對方建立聊天連接。 這兩步的過程的具體作法在前面的文章中有詳細地介紹,這裏就不再詳細介紹了,具體方法可以參看PPSer例副程式。 3 PDefine::SPlaySound(8); //?動聲音播放設備,參數?0..15之間的整數。缺省值?8。 //整數所表示的含義與捕捉設備的格式列表中的順序是一樣的。 //聲音播放的格式應和錄音的格式一樣,否則會?生變調。 4 HWND hCombo=this->GetDlgItem(IDC_COMBO1)->m_hWnd; PDefine::SGetSoundCaptureDeviceList(hCombo); //創建聲音捕捉設備,傳遞一個下拉式列示方塊控制項的HWND,DLL會將可利用的聲音捕捉設備填充在下拉式列示方塊內。 //如果給出的HWND?空,DLL不作填充。下拉式列示方塊控制項不要排序。 5 PDefine::SInitSoundCaptureDevice(NULL); //如果你在第4步的方法中給出了一個下拉式列示方塊控制項的HWND,DLL會自動地設定初始選擇?第0個條例。 //你可以在等待用戶選擇下拉式列示方塊,按確定按鈕後,將下拉式列示方塊控制項的HWND作?參數傳送給 //SInitSoundCaptureDevice()方法。 //如果你在前一個方法中HWND?空,則在調用SInitSoundCaptureDevice()方法時, //設定第一個參數?NULL,然後傳遞第二個參數,第二個參數表示你選擇第幾個設備,這個值必需 //是一個有效的選擇,第二個參數缺省時?0。 6 HWND hListBox=this->GetDlgItem(IDC_LIST1)->m_hWnd; PDefine::SGetSoundCaptureAvailableFormats(hListBox); //得到選擇的捕捉設備的可利用的格式列表,傳遞一個列表框控制項的HWND,DLL會將可利用的捕捉 //格式填充在列表框中。如果HWND?空,DLL不作填充。列表框控制項不要排序。 7 PDefine::SSetSoundCaptureAcceptdFriend(CPPSerDlg::SGetFriendFromID("PPQ123456789")); //通過這個方法的調用,你可以選擇後續的語音被傳送給哪一個好友。 //這個方法可以在開始聲音捕捉後多次調用,來改變接收方。 //如果你和這個好友之間沒有創建連接,則語音不會被傳送,程式也不會出錯,我會在後續的版本中加入一些消息來通知DLL外的程式作出相應的回應。 8 PDefine::SCreateSoundCaptureBuffer(NULL); //使用方法參考上面下拉式列示方塊的使用。第二個參數缺省,表示使用缺省的值?8, //即22.05KHz,8位,單聲道格式。 9 PDefine::SRecordSound(); //開始聲音捕捉。 經過以上9個步驟,你已經創建了一個語音傳輸的通道,語音傳輸和文字傳輸採用的是同一個連接,你可以在傳輸聲音的同時,也傳輸其他指令。從第4到第9的步驟中除了第7步外,都不要重復調用,除非你釋放了捕捉或重播設備。 和聲音捕捉與重播相關的所有方法,都是PDefine類中的靜態方法,其他方法如下: static void SStopRecordSound(); //暫時停止聲音捕捉,停止後聲音捕捉設備並沒有被釋放,你可以使用PDefine::SRecordSound()方法繼續開始聲音捕捉。 static void SReleaseSoundCaptureDevice(); //釋放聲音捕捉設備。調用該方法時,不需要預先去停止聲音捕捉,方法內部會自動調用。 //如果你釋放了聲音捕捉設備,你不能夠再通過調用PDefine::SRecordSound()方法來繼續開始聲音捕捉;你必需重復第3步到第9步的過程,來重新?動聲音捕捉。 //注意: 在程式結束時,一定要調用該方法去釋放聲音捕捉設備,即使你沒有?動過聲音捕捉設備,調用該方法也不會?生錯誤。 static void SStopPlaySound(); //暫時停止聲音重播。你可以通過調用PDefine::SPlaySound()方法繼續開始聲音重播。在你停止聲音重播的期間,你所接收到的聲音都會在記憶體中被保存下來,當你繼續開始聲音重播時,會自動按照原來接收的順序將聲音依次重播出來。 //在這個版本時,還不能創建多個聲音重播,在後續的版本中,將會支援多個聲音重播。 static void SReleaseSoundPlayDevice(); //釋放聲音重播設備。你可以通過調用PDefine::SPlaySound()方法繼續開始聲音重播。 //注意: 在程式結束時,一定要調用該方法去釋放聲音重播設備,即使你沒有?動過聲音重播設備,調用該方法也不會?生錯誤。 關於聲音的傳輸方法,就暫時介紹到這裏啦。在下一個版本中,我將把突破防火牆的方法集成進DLL中,使處在任何位置的雙方都可以實現P2P的互連。 因受到開發條件的限制,試驗突破防火牆的方法,對我來講是一件很麻煩的事。因?我只有一台機器,所以作這件事情,要繞很多的彎,不知道哪一位朋友是否願意共同合作來進行測試。 希望大家能繼續關注DLL的後續版本。
------
**********************************************************
哈哈&兵燹
最會的2大絕招 這個不會與那個也不會 哈哈哈 粉好

Delphi K.Top的K.Top分兩個字解釋Top代表尖端的意思,希望本討論區能提供Delphi的尖端新知
K.表Knowlege 知識,就是本站的標語:Open our mind
spencerkuo
一般會員


發表:21
回覆:34
積分:11
註冊:2003-08-15

發送簡訊給我
#4 引用回覆 回覆 發表時間:2005-06-30 16:27:52 IP:221.169.xxx.xxx 未訂閱
請問這個DLL 是否有DELPHI 的使用方式阿 我看不董C 哪個好心人幫我翻譯成DELPHI 阿 謝謝各位大大了
系統時間:2024-07-06 6:51:59
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!