现在的位置: 首页 > 综合 > 正文

通讯技术精品汇总-网络通信 (HTTP引擎)(转)

2013年08月11日 ⁄ 综合 ⁄ 共 13099字 ⁄ 字号 评论关闭

原文地址:http://www.devdiv.net/bbs/viewthread.php?tid=26987

作者:Flying
邮箱:Flying#devdiv.net
适用平台:S60 V2 V3 V5
开发工具:Carbide C++
摘要:连接网络 接入点 设置代理 GET POST

正文:
        利用Symbian的Http方面的功能自然首先得建立网络连接,然后提交HTTP请求,比如GET、POST、HEAD,最常见的就是用GET从网络上取得需要的数据了,POST用来上传数据,比如发送表单中的用户名、密码之类的信息,这些信息由于不包含在URL中,因此安全性稍高些,同时也不受URL总长度的限制,同时利用http协议上传文件也用的POST,只是数据格式不同,HEAD主要用来取得一些http头信息,比如得知服务器某文件的最新修改日期、大小、是否支持分段下载等等。
        文中基本所有说明都是穿插在代码中以注释的形式出现。
        连接网络
连接网络比较简单,依葫芦画瓢就行了,首先检查当前使用的接入点号码是否为0,如果是的话就要求系统弹出选择接入点的提示框,反之就用已经设定好的接入点。
void SetConnectionL()
{
        //已定义的类成员变量
        //RConnection   iConnection;
        //TUint32                iIapID;        //如果为0则弹出选择接入点的系统提示框,否则不显示
        //TCommDbConnPref        iConnectPref;
        //RSocketServ                 iSocketServ;
        if (!iIapID)
        {
                iConnectPref.SetDialogPreference(ECommDbDialogPrefPrompt);
        }
        else
        {
                iConnectPref.SetDialogPreference(ECommDbDialogPrefDoNotPrompt);
        }
        iConnectPref.SetIapId(iIapID);
#if !defined(__SERIES60_3X__) && defined(__WINS__)
        iConnectPref.SetBearerSet(KCommDbBearerPSD);
#else
        iConnectPref.SetBearerSet(ECommDbBearerUnknown);
#endif
        iConnectPref.SetDirection(ECommDbConnectionDirectionOutgoing);
        iConnection.Start(iConnectPref, iStatus); //采用异步方式开始网络连接
        SetActive();
}

在连接成功后可以创建一个或者多个RHTTPSession,一次创建多个RHTTPSession可供后续重复使用,相当于创建一个Session池了。
void RunL()
{
        //iConnection.Start(iConnectPref, iStatus)异步函数运行结束后
        if (iStatus == KErrNone)
        {
                //连接成功后,可以创建多个RHTTPSession,同时设置参数,比如代理
                CreateSession();
        }
        else
        {
                //如果连接网络失败,可以使用Observer通知上层程序。
        }
}

Session创建完成后就要设置Session的连接参数。
void CreateSession()
{
        RHTTPSession *session = new (ELeave) RHTTPSession;
        TRAPD(err, session->OpenL());
        if (err != KErrNone)
        {
                //记录失败信息到日志
                User::Leave(err);
        }
        //设置参数
        ProxyL(*session);
}
2.根据接入点设置来设置Session的连接参数
设置接入点的代理参数时不建议使用固定的硬编码方式,即检查接入点的名字,包含cmwap、uniwap、移动彩信之类的字符的的就固定设置代理服务器IP为:10.0.0.172:80, 而是获取系统接入点设置里的参数,比如有些国家和地区的代理服务器的IP和端口为:196.6.128.12:8080。
void ProxyL(RHTTPSession & aSession)
{
        TBool bUsingProxy = EFalse;
        TInt32 iapService;
        TBuf<50> proxyip;
        TUint32 port=0;

        CCommsDatabase* db = CCommsDatabase::NewL();
        CleanupStack::PushL(db);
        CCommsDbTableView* pTable;
        pTable = db->OpenViewMatchingUintLC(TPtrC(IAP), TPtrC(COMMDB_ID), iIapID);
        User::LeaveIfError(pTable->GotoFirstRecord());
        TBuf<128> serveType;
        pTable->ReadTextL(TPtrC(IAP_SERVICE_TYPE), serveType);
        if (serveType == TPtrC(OUTGOING_GPRS))
        {
                pTable->ReadUintL(TPtrC(IAP_SERVICE), iapService);        //取得此接入点的IAPSERVICE值,为下一步到OUTGOING_GPRS表中取的该接入点的代理设置等信息准备
        }
        else
        {
                User::Leave(KErrNotFound);
        }
        CleanupStack::PopAndDestroy(2);

        bUsingProxy = ProxyInfoL(iapService,proxyip,port); //获取代理设置

        RStringPool strPool = aSession.StringPool();
        RHTTPConnectionInfo httpInfo = aSession.ConnectionInfo();
        //设置session的链接信息
        httpInfo.SetPropertyL(strPool.StringF(HTTP::EHttpSocketServ, RHTTPSession::GetTable()), THTTPHdrVal(iSocketServ.Handle()));
        httpInfo.SetPropertyL(strPool.StringF(HTTP::EHttpSocketConnection, RHTTPSession::GetTable()), THTTPHdrVal(REINTERPRET_CAST(TInt, &(iConnection))));

        if (bUsingProxy) //根据手机接入点设置的参数来设置代理,如10.0.0.172:80
        {
                TBuf8<50> aProxy8;
                aProxy8.Copy(proxyip);
                aProxy8.AppendFormat(_L8(":%d"),port);
                RStringF iPrxAddr = strPool.OpenFStringL(aProxy8);
                CleanupClosePushL(iPrxAddr);
                THTTPHdrVal iPrxUsage(strPool.StringF(HTTP::EUseProxy, RHTTPSession::GetTable()));
                httpInfo.SetPropertyL(strPool.StringF(HTTP::EProxyUsage, RHTTPSession::GetTable()), iPrxUsage);
                httpInfo.SetPropertyL(strPool.StringF(HTTP::EProxyAddress, RHTTPSession::GetTable()), iPrxAddr);
                CleanupStack::PopAndDestroy();
        }
}

//这里是从OUTGOING_GPRS表中读取代理IP和端口信息
TBool ProxyInfoL(const TUint32 

aGprsId, TDes &aProxyIp, TUint32 &aProxyPort)
{
        TInt result = KErrNone;
        TBool bUsingProxy = EFalse;
        CCommsDatabase* commsDb = CCommsDatabase::NewL(EDatabaseTypeIAP);
        CleanupStack::PushL(commsDb);
        CCommsDbTableView* commsView = commsDb->OpenViewOnProxyRecordLC(aGprsId, TPtrC(OUTGOING_GPRS));
        result = commsView->GotoFirstRecord();
        if (KErrNone != result)
        {
                CleanupStack::PopAndDestroy(2);
                return bUsingProxy;
        }
        commsView->ReadBoolL(TPtrC(PROXY_USE_PROXY_SERVER), bUsingProxy);
        if (!bUsingProxy)
        {
                CleanupStack::PopAndDestroy(2);
                return bUsingProxy;
        }
        aProxyIp.Copy(*commsView->ReadLongTextLC(TPtrC(PROXY_SERVER_NAME)));
        commsView->ReadUintL(TPtrC(PROXY_PORT_NUMBER), aProxyPort);
        CleanupStack::PopAndDestroy(3);
        return bUsingProxy;
}

3.通过刚刚创建的Session发布HTTP请求(GET、POST、HEAD):
1)具体负责http各个任务的类
        该类实现接口MHTTPTransactionCallback和MHTTPDataSupplier,前者负责接收HTTP下载中的事件,比如得到了服务器返回的信息头、数据等等,而后者用于自己POST时给系统提供数据使用。
class CDevDivHttp : public CBase, public MHTTPTransactionCallback, public MHTTPDataSupplier
{
public:
        IssueHttpGetL();                //GET url
        IssueHttpPostL();                //POST url
private:
        //实现MHTTPTransactionCallback
        void MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent);
        TInt MHFRunError(TInt aError, RHTTPTransaction aTransaction, const THTTPEvent& aEvent);

        //实现MHTTPDataSupplier
        TBool GetNextDataPart(TPtrC8& aDataPart);
        void ReleaseData() = 0;
        TInt OverallDataSize();
        TInt Reset();

        void SetFileData(const TDesC8 & aName, const TDesC & aFileName);
};
2)发出HTTP Get请求
void CDevDivHttp::IssueHttpGetL()
{
        //RHTTPSession      *iSession; 类成员变量
        //RHTTPTransaction iTransaction;

        iSession = GetSession(); //获取一个session,因为上面可能一次性创建多个seesion供使用

        TUriParser8 uri;
        User::LeaveIfError(uri.Parse(GetUrl());

        RStringF method = iSession->StringPool().StringF(HTTP::EGET, RHTTPSession::GetTable());

        //第二个参数即指定了系统在有http事件发生的时候通过此MHTTPTransactionCallback的实现发出通知。
        //使用this即表示是该类实现了抽象类MHTTPTransactionCallback,由该类获得通知
        iTransaction = iSession->OpenTransactionL(uri, *this, method);
        //设置请求信息头
        RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();

        //SetHeaderL见下文
        SetHeaderL(hdr, HTTP::EAccept, KHStrRequestAccept);                //_LIT8(KHStrRequestAccept,"*/*");
        SetHeaderL(hdr, HTTP::EUserAgent, KUserAgent);                        //_LIT8(KUserAgent,"DevDiv_Flying1.1");
        //如果不加下面2句,那么用cmwap接入点下载的wml数据可能是乱码
        SetHeaderL(hdr, HTTP::EAcceptCharset, KAcceptCharset);        //_LIT8(KAcceptCharset,"x-gbk,utf-8;q=0.7,*;q=0.7");
        SetHeaderL(hdr, HTTP::EAcceptLanguage, KAcceptLanguage);//_LIT8(KAcceptLanguage,"en, zh-cn");

        iTransaction.SubmitL();

        //下面就等待系统调用MHFRunL或者MHFRunError了。
}
/*
  如果需要断点续传或者分段下载,那么就在上面的SetHeaderL段落处加上
                  TBuf8<50> range;
                  range.Append(_L8("bytes="));
                range.AppendNum(666);        //起始字节序号,从0开始
                range.Append(_L8("-"));
                range.AppendNum(888);        //终止字节序号,如果要表示到结束,那么可以不要这句,即"bytes=6666-"
                SetHeaderL(hdr, HTTP::ERange, range);
*/

void  CDevDivHttp::SetHeaderL(RHTTPHeaders aHeaders, TInt aHdrField, const TDesC8& aHdrValue)
{
        RStringF valStr = iSession->StringPool().OpenFStringL(aHdrValue);
        CleanupClosePushL(valStr);
        THTTPHdrVal val(valStr);
        aHeaders.SetFieldL(iSession->StringPool().StringF(aHdrField, RHTTPSession::GetTable()), val);
        CleanupStack::PopAndDestroy();
}

//此函数由系统回调,通过两个参数得知发生何种http事件
void CDevDivHttp::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
{
        switch (aEvent.iStatus)
        {
        case THTTPEvent::EGotResponseHeaders:
                {
                        //得到了信息头,此处可以判断是否是使用的cmwap接入点,并且是网络连接后的第一次请求,如果是则停止,并重新IssueHttpGetL一次
                        //cmwap接入点目前首次连接成功后的第一次http请求返回的不是程序需要的数据,而是所谓的推送页面,所以目前可以直接抛弃首次数据
                        //而uniwap接入点此页面似乎是随机的,也许各省不同,可能需要解析数据,推送页面是wml格式的,里面有个超链接是原来请求的url加了随机的后缀
                        if (aTransaction.Response().StatusCode() == 200 || aTransaction.Response().StatusCode() == 206)
                        {        //206表示请求是分段的
                                //说明请求正常 服务器应答
                        }
                        else
                        {
                                //其他的说明并未得到自己想要的数据,比如可能是404找不到此页面。
                        }
                        break;

                        case THTTPEvent::EGotResponseBodyData:
                        {
                                MHTTPDataSupplier* dataSupplier = aTransaction.Response().Body();
                                TPtrC8 ptr;
                                TBool bLast = dataSupplier->GetNextDataPart(ptr);
                                //这儿得到了服务器返回的数据,可以写到文件或者通过Observer通知上层程序
                                dataSupplier->ReleaseData();
                        }
                        break;
                        case THTTPEvent::EResponseComplete:
                        {
                                //响应完成
                        }
                        break;
                        case THTTPEvent::ESucceeded:
                        {
                                //本次http请求成功完成,可以做收尾操作,比如关闭文件
                        }
                        break;
                        case THTTPEvent::ERedirectedTemporarily:
                        case THTTPEvent::ERedirectedPermanently:
                        {
                                //发生301、302跳转,此处可以把新url通知给上层程序或其它处理
                                //注意文中处理301、302两种跳转都是在此处,而且对于我们的客户端来说一般不用区分具体是那种,
                                //经过与Mark帅哥的讨论,得知还有一种处理跳转的方式,就是使用iSession.FilterCollection().RemoveFilter(pool.StringF(HTTP::ERedirect,
RHTTPSession::GetTable()))移除对ERedirect的过滤,这样302跳转就可以在EGotResponseHeaders里使用aTransaction.Response().StatusCode()==302来判断处理,但是个人认为这样处理麻烦了些,而且301跳转还是要用ERedirectedTemporarily,所以干脆还是不移除过滤,统一放到这儿处理。
                                const TUriC8& uri = aTransaction.Request().URI();
                        }
                        break;
                        default:
                        {
                                //其他事件,一般需要在用户设定的重试次数范围内重新发出http请求
                        }
                        break;
                }
        }
}

TInt CDevDivHttp::MHFRunError(TInt aError, RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
{
        //此函数被调用,说明系统发生了某些错误,比如内存不足,超时等等
        //可以在此处加上日志,并在用户设定的重试次数范围内重新发出http请求
        //如果返回KErrNone,那么表示用户处理了错误。返回其它值,系统将会终止程序并报错
        //此处一定要做处理,不可简单的直接返回KErrNone,再不济也要写到日志,否则发生了错误都不知道,导致程序流程有瑕疵。
        return KErrNone;
}
3)发出HTTP POST请求
使用POST,常用的要上传的数据至少有两种格式,一种是单纯的字符,一种是文件。
        单纯的字符比如用于像服务器发送登录论坛时用的用户名密码等数据。发送这种单纯字符方式的POST与GET中url后面加?id=xxx&pw=xxx类似,但是安全性稍高,而且不受url长度限制,有兴趣的朋友可以阅读相关的资料。
        而上传文件协议RFC1867属于HTTP协议的补充协议,它有自己的一套格式,可以参考下面一段代码。

void CDevDivHttp::IssueHttpPostL()
{
        //前面基本与Get类似
        iSession = GetSession();
        TUriParser8 uri;
        User::LeaveIfError(uri.Parse(GetUrl());

        RStringF method = iSession->StringPool().StringF(HTTP::EPOST, RHTTPSession::GetTable());

        TBuf8<70> postContentType;
        if (IsPostFile())        //是Post文件还是Post一些简单的字符串,比如提交的表单内容,格式不同
        {
                postContentType.Copy(_L8("multipart/form-data; boundary=-------------------devdivsunnyflying830809831224"));
        }
        else
        {
                postContentType.Copy(_L8("application/x-www-form-urlencoded"));
        }

        iTransaction = iSession->OpenTransactionL(uri, *this, method);
        RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();

        SetHeaderL(hdr, HTTP::EContentType, postContentType);
        SetHeaderL(hdr, HTTP::EAccept, KHStrRequestAccept);
        SetHeaderL(hdr, HTTP::EUserAgent, KUserAgent);
        SetHeaderL(hdr, HTTP::EAcceptCharset, KAcceptCharset);
        SetHeaderL(hdr, HTTP::EAcceptLanguage, KAcceptLanguage);

        MHTTPDataSupplier* dataSupplier = this;
        iTransaction.Request().SetBody(*dataSupplier);//系统通过自己实现的MHTTPDataSupplier获得要上传的数据
        iTransaction.SubmitL();
}

//注意:THTTPEvent::EGotResponseBodyData里用的GetNextDataPart是服务器返回数据时从系统开辟的缓冲区里得到数据,此处的POST是上传阶段由系统获取我们需要上传的数据,两者时机不同,不可混淆
TBool CDevDivHttp::GetNextDataPart(TPtrC8& aDataPart)
{
        //得到需要发送的数据,返回值表示是否是最后一次数据
        if (iPostData) //iPostData是实现准备好的数据:纯粹的字符或者是文件内容,后者设置比较复杂,通过SetFileData进行
        {
                aDataPart.Set(iPostData->Des());
        }
        return ETrue;
}

TInt CDevDivHttp::OverallDataSize()
{//需要需要发送的数据长度
        if (iPostData)
                return iPostData->Length();
        else
                return KErrNotFound;
}

void CDevDivHttp::ReleaseData()
{
        delete iPostData;
        iPostData = NULL;
}

TInt CDevDivHttp::Reset()
{
        return KErrNone;
}

//加载文件上传的数据,此处是小文件上传一次读取完毕,如果是大文件上传,也可以读取一部分数据,配合GetNextDataPart的返回参数进行
void CDevDivHttp::SetFileData(const TDesC8 & aName, const TDesC & aFileName)
{
        //iFile和iFs是RFile、FFs类型的成员变量
        TInt err = iFile.Open(iFs, aFileName, EFileShareAny);
        if (err!=KErrNone)
        {
                //此处可以加上日志
                User::Leave(err);
        }

        TInt aSize;
        User::LeaveIfError(iFile.Size(aSize));

        HBufC8 *postData = HBufC8::NewLC(aSize);
        TPtr8 aPtr = postData->Des();
        User::LeaveIfError(iFile.Read(0, aPtr, aSize));
        iFile.Close();

        //以HTTP协议有关上传文件协议RFC1867规定的格式准备数据
        TParsePtrC parsePtr(aFileName);
        TPtrC filename = parsePtr.NameAndExt();
        _LIT8(KDataStart,"---------------------devdivsunnyflying830809831224");
//前面比boundary多2个-,当初自己调试来调试去死活不成功就因为这个。
        _LIT8(KCrlf,"/r/n");
        _LIT8(KContent,"Content-Disposition: form-data; name='");
        _LIT8(KContent1,"'; filename='");
        _LIT8(KFileCompletion,"'");
        _LIT8(KContent3,"Content-Type: application/octet-stream");
        _LIT8(KDataEnd,"---------------------devdivsunnyflying830809831224--");
//前后都比boundary多2个-

        if (iPostData)
        {
                delete iPostData;
                iPostData = NULL;
        }
        iPostData = HBufC8::NewLC(3000 + aPtr.Size());
        TPtr8 iPostDataPtr = iPostData->Des();
        iPostDataPtr.Zero();

        iPostDataPtr.Append(KCrlf);
        iPostDataPtr.Append(KDataStart);
        iPostDataPtr.Append(KCrlf);
        iPostDataPtr.Append(KContent);
        iPostDataPtr.Append(aName);
        iPostDataPtr.Append(KContent1);
        iPostDataPtr.Append(aFileName);
        iPostDataPtr.Append(KFileCompletion);
        iPostDataPtr.Append(KCrlf);
        iPostDataPtr.Append(KContent3);
        iPostDataPtr.Append(KCrlf);
        iPostDataPtr.Append(KCrlf);
        iPostDataPtr.Append(aPtr);
        iPostDataPtr.Append(KCrlf);
        iPostDataPtr.Append(KDataEnd);
        CleanupStack::PopAndDestroy(); //postData
        CleanupStack::Pop();                        //iPostData
}

        上文只是简单的介绍了利用Symbian的HTTP框架来下载上传数据或文件,代码仅供参考,主要的是了解其流程、细节以及一些容易让人走弯路的地方。重在实践,细心调试。

 

抱歉!评论已关闭.