轉:http://www.cnblogs.com/jsjkandy/archive/2008/08/06/1262445.html
一、前言
在前篇《GPRS開發系列文章之入門篇》裏,我主要對GPRS開發中遇到的一些常用概念和一些業務邏輯做了簡單的介紹,沒想到得到了很多網友的支持和關注,因昨天有事因此延遲到今天才奉上這篇進階系列文章,還請各位見諒。希望大家支持同時歡迎拍磚,共同提高。
在最後一篇《GPRS開發系列文章之實戰篇》我將詳細敘述如何利用類庫開發Client和Server端通信程序,因此本文的多數講解將是爲下文服務的。本文將向您介紹基於PPC2003的Windows
mobile 系列的客戶端和基於.Net 2005的服務器端進行開發所需掌握和了解的開發庫,並着重圍繞客戶端進行GPRS連接所用到的一些API函數進行講解。本文的最後是一些用於引用和學習用的鏈接和利用API建立GPRS連接的demo,感興趣的同志可以去點擊或下載後進行深入研究。
關於客戶端API的敘述我基本上都是翻譯過來的,如欠妥還請各位多多斧正!同時demo程序我也是隻做了部分加工,主要都是參照了謝紅偉的發表的文章(後面有引用鏈接)
二、進階系列篇詳解
1. 客戶端建立GPRS連接 API
客戶端開發採用 EVC4.0進行開發,主要講解的類庫爲Connection Manager 系列API,客戶端在進行GPRS撥號連接時將使用下文所介紹的API。
Connection Manager(本人譯爲連接管理器,此對象爲一系列API的集合) 系列API的主要目的是爲了集中管理基於Windows Mobile系列的設備網絡連接的建立與維護。移動應用程序使用連接管理器API去建立或規劃一個網絡連接,而連接管理器則掌控連接過程中的所有狀態信息.應用程序在要發起一個連接(比如Internet),只要簡單的告知連接管理器就OK了。
當一個應用程序發起一個網絡連接的請求時,連接管理器首先從連接服務提供商(CSPS)處獲取所有可能的連接信息,然後連接管理器會從這一系列連接信息中根據開銷,延遲、帶寬等因素來選擇一個最佳的連接,最後連接管理器將被請求的連接排入隊列,然後在一個適合的時間使用CSPS來建立連接。
【API函數】:
a) ConnMgrApiReadyEvent()函數
函數原型:HANDLE WINAPI ConnMgrApiReadyEvent();
利用此函數來我們可以返回一個連接事件的句柄,注意在的得到句柄後要記得及時釋放
b) ConnMgrConnectionStatus()函數
函數原型:
HRESULT WINAPI ConnMgrConnectionStatus(HANDLE hConnection,DWORD * pdwStatus );利用此函數的返回值pdwStatus,我們可以得到很多的關於連接的信息,例如如果我們連接成功將返回CONNMGR_STATUS_CONNECTED,斷開連接將返回CONNMGR_STATUS_DISCONNECTED,他的返回狀態信息非常豐富,有14中之多,完全可以滿足我們的應用需要;
c)
ConnMgrEnumDestinations()函數
函數原型:
HRESULT WINAPI ConnMgrEnumDestinations(int nIndex,CONNMGR_DESTINATION_INFO * pDestInfo );
一般我們的PDA在連接GPRS時都有好幾個連接,利用此函數我們可以枚舉出所有可用的連接,然後再對挑選的連接進行篩選得到一個最佳連接
接下來講兩個很重要的函數,我們將利用兩個函數中的一個來發起連接,他們是:
d) ConnMgrEstablishConnection()和ConnMgrEstablishConnectionSync()函數,這兩個函數一個用於發起一個異步連接請求,一個用於同步請求,使用異步連接請求我們可以在發起連接後立即返回,而使用同步請求客戶端將一直被阻塞知道函數返回確認連接,關於異步和同步我就不再敘述;
它們的原型依次爲:
HRESULT WINAPI ConnMgrEstablishConnection( CONNMGR_CONNECTIONINFO * pConnInfo, HANDLE * phConnection );
HRESULT WINAPI ConnMgrEstablishConnectionSync( CONNMGR_CONNECTIONINFO * pConnInfo, HANDLE * phConnection, DWORD dwTimeout, DWORD * pdwStatus );
可以看到兩個函數的第一個參數都爲一個CONNMGR_CONNECTIONINFO對象,此對象爲一個結構體,它保存了客戶端發起連接請求的一系列信息,因此,在我們調用此函數時必須構造一個該對象,然後將其作爲參數傳入連接函數中。這裏很有必要講一下該結構體,該結構體的原型如下:
typedef struct _CONNMGR_CONNECTIONINFO
{
DWORD cbSize; DWORD dwParams;DWORD dwFlags;
DWORD dwPriority;BOOL bExclusive;BOOL bDisabled;GUID guidDestNet;
HWND hWnd; UINT uMsg; LPARAM lParam;ULONG ulMaxCost; ULONG ulMinRcvBw;
ULONG ulMaxConnLatency;
} CONNMGR_CONNECTIONINFO;
其中參數dwFlags用於指定我們的接入點,比如我們常說的CMNER和CMWAP,而參數GUID則標誌了我們對應於每個接入點的全球唯一標誌符,關於如何得到或者查看GUID,我們可以在“\Program
Files\Windows CE Tools\wce420\POCKET PC 2003\Include\Armv4”目錄下查看connmgr.h文件,裏面包含了各個接入點的GUID,例如:
CMNET 爲:(0x436ef144, 0xb4fb, 0x4863, 0xa0, 0x41, 0x8f, 0x90, 0x5a, 0x62, 0xc5, 0x72)
CMWAP爲:0x7022e968, 0x5a97, 0x4051, 0xbc, 0x1c, 0xc5, 0x78, 0xe2, 0xfb, 0xa5, 0xd9
如果想更進一步瞭解,我們還可以通過查看註冊表方式來查看PDA上連接管理器的相關連接信息,在PPC 2003中註冊表路徑爲:
[HKEY_LOCAL_MACHINE\Comm\ConnMgr],如下圖所示:
在Destinations一項中就對應我們所有可用的網絡連接,這個跟用ConnMgrEnumDestinations()方法得到的是一樣的效果,在默認Internet設置中我們將看到CMNET的GUID,如下所示:
在這裏裏面有DestId一項,就對應着我們久違的GUID
e) ConnMgrReleaseConnection函數
我們在上一步中建立連接後我們將得到一個連接句柄,在重新開始一個新的連接或者斷開連接都要調用此函數來釋放掉之前創建的連接,它的原型爲:
HRESULT WINAPI ConnMgrReleaseConnection( HANDLE hConnection,BOOL bCache );
【連接管理API大致使用步驟】:
首先我們利用ConnMgrApiReadyEvent()函數來確認是否有可用連接,如果有可用連接我們則利用ConnMgrEnumDestinations()函數枚舉所有可用連接,然後遍歷所有連接調用我們的同步或異步連接方法ConnMgrEstablishConnectionSync()與ConnMgrEstablishConnection()來發起連接,一旦連接成功後我們就可以進行我們偉大的下一步了,就是和我們的服務器進行通信。
【GPRS demo效果圖】
【GPRSDemo介紹】
GPRSDemo主要利用了上述的幾個重要的API函數來獲取當前可用連接,並自動選擇一個最佳的連接途徑,然後啓用這個連接,在連接啓動成功以後再用socket 進行網絡連接,與公網服務器進行通信。
首先檢查是否有可用連接
{
CONNMGR_DESTINATION_INFO networkDestInfo = {0};
// 得到網絡列表
for ( DWORD dwEnumIndex=0; ; dwEnumIndex++ )
{
memset ( &networkDestInfo, 0, sizeof(CONNMGR_DESTINATION_INFO) );
if ( ConnMgrEnumDestinations ( dwEnumIndex, &networkDestInfo ) == E_FAIL )
{
break;
}
StrAry.Add ( networkDestInfo.szDescription );
}
}
接下來找到“Internet”這個連接,可用遠程URL映射的方式來完成,這樣可以讓系統自動選取一個最好的連接。
{
if ( !lpszURL || lstrlen(lpszURL) < 1 )
return FALSE;
memset ( &guidNetworkObject, 0, sizeof(GUID) );
int nIndex = 0;
HRESULT hResult = ConnMgrMapURL ( lpszURL, &guidNetworkObject, (DWORD*)&nIndex );
if ( FAILED(hResult) )
{
nIndex = -1;
DWORD dwLastError = GetLastError ();
AfxMessageBox ( _T("Could not map a request to a network identifier") );
}
else
{
if ( pcsDesc )
{
CONNMGR_DESTINATION_INFO DestInfo = {0};
if ( SUCCEEDED(ConnMgrEnumDestinations(nIndex, &DestInfo)) )
{
*pcsDesc = DestInfo.szDescription;
}
}
}
return nIndex;
}
最後啓用指定編號的連接並檢查連接狀態
{
// 釋放之前的連接
ReleaseConnection ();
// 得到正確的連接信息
CONNMGR_DESTINATION_INFO DestInfo = {0};
HRESULT hResult = ConnMgrEnumDestinations(dwIndex, &DestInfo);
BOOL bRet = FALSE;
if(SUCCEEDED(hResult))
{
// 初始化連接結構
CONNMGR_CONNECTIONINFO ConnInfo;
ZeroMemory(&ConnInfo, sizeof(ConnInfo));
ConnInfo.cbSize = sizeof(ConnInfo);
ConnInfo.dwParams = CONNMGR_PARAM_GUIDDESTNET;
ConnInfo.dwFlags = CONNMGR_FLAG_PROXY_HTTP | CONNMGR_FLAG_PROXY_WAP | CONNMGR_FLAG_PROXY_SOCKS4 | CONNMGR_FLAG_PROXY_SOCKS5;
ConnInfo.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
ConnInfo.guidDestNet = DestInfo.guid;
ConnInfo.bExclusive = FALSE;
ConnInfo.bDisabled = FALSE;
DWORD dwStatus = 0;
hResult = ConnMgrEstablishConnectionSync(&ConnInfo, &m_hConnection, 10*1000, &dwStatus );
if(FAILED(hResult))
{
m_hConnection = NULL;
}
else bRet = TRUE;
}
return bRet;
}
檢測連接狀態
BOOL CConnectManager::WaitForConnected ( int nTimeoutSec, DWORD *pdwStatus/*=NULL*/ )
{
DWORD dwStartTime = GetTickCount ();
BOOL bRet = FALSE;
while ( GetTickCount ()-dwStartTime < (DWORD)nTimeoutSec * 1000 )
{
if ( m_hConnection )
{
DWORD dwStatus = 0;
HRESULT hr = ConnMgrConnectionStatus ( m_hConnection, &dwStatus );
if ( pdwStatus ) *pdwStatus = dwStatus;
if ( SUCCEEDED(hr) )
{
if ( dwStatus == CONNMGR_STATUS_CONNECTED )
{
bRet = TRUE;
break;
}
}
}
Sleep ( 100 );
}
return bRet;
}
2. 客戶端與服務器端進行socket通信API
socket通信相關開發API在Winsock2.h.文件中定義,因爲SOCKET通信不是本文的重點但是又是必須要涉及的
a) WSAStartup函數。在應用程序進行Windows Sockets通信時,必須首先調用此函數來指定應用程序要加載的Windows Scoket版本等信息,應用程序結束前我們應該調用WSACleanup去釋放掉所用的系統資源
b) Connect函數。此函數用來建立連接
c) Select 函數。建立連接後,用來偵聽是否有數據傳輸
d) Send函數。用於給服務器發送消息
3. 服務器端與客戶端進行socket通信
服務器端主要涉及到的庫爲:
System.Net,System.Net.Sockets,System.IO;
System.Net 命名空間爲當前網絡上使用的多種協議提供了簡單的編程接口,System.Net.Sockets
命名空間爲需要嚴密控制網絡訪問的開發人員提供了 Windows Sockets (Winsock)
接口的託管實現。
System.IO 命名空間包含允許讀寫文件和數據流的類型以及提供基本文件和目錄支持的類型。
需要了解的技術有:多線程,事件與委託,SOCKET通信等
三、 引用(參考)文章地址
1. http://msdn.microsoft.com/en-us/library/ms827527.aspx(MSDN移動開發中心);
2. http://www.codeguru.com/cpp/w-p/ce/pocketpc/article.php/c7355/(PPC應用程序Internet連接);
3. http://www.vckbase.com/document/viewdoc/?id=1803(如何建立GPRS連接)
4. http://www.microsoft.com/china/msdn/library/NetFramework/netcompactframework/understandingnetcfFAQ.mspx?mfr=true(常見開發問題解答)
5. PPC SDK參考(安裝 ppc 2003 SDK後纔有)
6. GPRS Demo下載