Hirdetés

Alkalmazásfejlesztés badára: Hálózatkezelés (1. rész)

A hálózati kommunikáció nagyon fontos egy okostelefon életében. Manapság gyakorlatilag egyeduralkodó a TCP/IP protokoll (nyomokban UDP/IP-vel fűszerezve), implementáció szempontjából pedig a BSD socketek, vagy szemantikailag nagyon hasonló megoldások.

A bada se kivétel az előbb leírtak alól, mindössze a saját osztályhierarchiába illesztették be a socketeket. A továbbiakban ezt járom körbe, valamint mutatok egy módszert az egyszerűsítésre.

A nagy kép

Minden alacsony szintű hálózati funkció az Osp::Net névtérben található meg. Tételesen: interfészek kezelése (NetAccountInfo, NetAccountManager), Bluetooth SPP és OPP (Bluetooth alnévtér), socketek (Sockets alnévtér), ad hoc Wifi támogatás (Wifi alnévtér), HTTP protokoll (Http alnévtér), IP címek és DNS támogatás (Dns, IpHostEntry) és forgalmi statisztika (NetStatistics).

Interfész

A funkciók használatának nulladik, többségében nem kötelező része a hálózati interfész kiválasztása és felélesztése a NetAccountManageren keresztül. A soron következő lépésekben résztvevő osztályok metódusainak általában két verziója van, a kézzel választott interfészhez egy NetConnection referenciát is át lehet adni, ellenkező esetben a rendszer az alapértelmezett kapcsolatot használja. Én kapásból átsiklottam az utóbbiakon, érdemes legalább kétszer elolvasni a dokumentációt.

Ez azt jelenti, hogy a WiFi kapcsolatot preferálja, ha pedig az nem elérhető, a PS adatkapcsolatot, mindezt a felhasználó külön megkérdezése nélkül. A saját programomban biztosan, de úgy vélem, hogy a többi esetben is elegendő ez a viselkedés.

DNS

A hostnevek feloldását kézzel kell elvégeznünk. Szerencsére ez elég következetesen működik: implementálnunk kell az IDnsEventListener interfészt, kell egy példány a DNS objektumból, végül ki kell adni a kérést. Mint minden jólnevelt hálózati művelet, ez is aszinkron lefutású — a callback metódus pedig ideális hely a kapcsolat felépítésének elkezdéséhez.

Én ezt így használtam fel:

// IrcSessionPrivate.h
class SessionPrivate : public Osp::Base::Object,
        public Osp::Net::IDnsEventListener,
        public ISocketEventListener // erre még visszatérek
{
    ...
private:
    // from Osp::Net::IDnsEventListener
    void OnDnsResolutionCompletedN(Osp::Net::IpHostEntry* ipHostEntry, result r);

private:
    result StartDnsQuery();
    ...

};

Ami a terjedelem miatt nem látszik, hogy a publikus Connect metódus tagváltozókba menti a hostnevet, a portot, és hogy TLS-t használjon-e a csatlakozáshoz az objektum. Ezek megjelennek mindjárt az implementációs fájlban.

// IrcSessionPrivate.cpp
result
SessionPrivate::StartDnsQuery()
{
    result r;

    if (!__pDns)
        {
        Osp::Net::Dns *pDns = new Osp::Net::Dns();
        r = pDns->Construct(*this);
        TryCatch(r == E_SUCCESS, delete pDns, "Dns Construct failed [%s]", GetErrorMessage(r));

        r = pDns->GetHostByName(__hostname);
        TryCatch(r == E_SUCCESS, delete pDns, "Dns GetHostByName failed [%s]", GetErrorMessage(r));

        __pDns = pDns;
    }
    else
    {
        r = __pDns->GetHostByName(__hostname);
        TryCatch(r == E_SUCCESS, {}, "Dns GetHostByName failed [%s]", GetErrorMessage(r));
    }

CATCH:
    return r;
}

Lusta módon létrehozva és újrahasznosítva a DNS objektumot, ez a metódus indítja el a lekérdezést. A TryCatch makró is látható, egy kis gyakorlás után elég jól használható a hibakezelésre. A lekérdezés végén a következő callback hívódik meg:

void
SessionPrivate::OnDnsResolutionCompletedN(Osp::Net::IpHostEntry* ipHostEntry, result r)
{
    Osp::Base::Collection::IList* addressList = ipHostEntry->GetAddressList();

    if (addressList != null)
    {
        int count = addressList->GetCount();
        if (count)
        {
            Osp::Net::IpAddress *address = static_cast(addressList->GetAt(0));

            delete __pEndPoint;
            __pEndPoint = NULL;
            __pEndPoint = new Osp::Net::NetEndPoint(*address, __port);

            SocketWrapper *pSocket = SocketWrapper::CreateSocketN(*this, *__pEndPoint, false);

            if (pSocket)
            {
                r = pSocket->Connect();
                TryCatch(r == E_SUCCESS || r == E_WOULD_BLOCK, delete pSocket, "Socket Connect failed [%s]", GetErrorMessage(r)); // !!!
                __pSocket = pSocket;
            }
            else
            {
                AppLog("Socket creation failed");
            }
        }
    }

CATCH:
    delete ipHostEntry;
}

A metódus működése egyszerű: a kapott infóból kinyeri az első IP-címet, a korábban letárolt porttal összerakva létrehoz egy NetEndPointot, melyhez példányosít egy socketet és elindítja a csatlakozást. Ami a dokumentációból számomra nem volt egyértelmű, az a socket által visszaadott E_WOULD_BLOCK eredmény. Ez, bár nem ezt sugallná, teljesen normális, ezért kell beletenni a TryCatch feltételébe. Vagy nem vettem észre valamit.

Socketek

Mint korábban említettem, a bada Socket és SecureSocket osztályai is a BSD socketeket implementálja saját, de nagyon hasonló interfésszel. Sok "mélyebb" funkció is elérhető, mint például a select(), de sajnos a POSIX szintaxis nem használható, így hálózati kód portolásánál többletmunka szükséges — mennyivel egyszerűbb lenne pedig egy metódushívással socket handle-t kapni, és arra függvényeket hívni, hiszen Linux.

A másik kellemetlenség a következő osztálydiagrammon látható: a Socket és a SecureSocket osztályoknak nincs közös őse az Objecten kívül, és két nagyon hasonló, de különálló observer interfész tartozik hozzájuk.

Mivel szeretném a titkosított kapcsolatot transzparensen kezelni, készítenem kellett egy wrapper osztályt a két típus egységesítésére. Ez lett a második kódrészletben előkerült SocketWrapper, a hozzá tartózó ISocketEventListener interfésszel együtt (az IRC névtérben, ezért nem ütközik). A kódja annyira nem izgalmas, hiszen belül a bada alap socket példányosítása és kezelése zajlik, a felépítése viszont hasznos lehet. A kód is előbb-utóbb elérhető lesz, kérésre azonnal.

A fentebbi példányosítással, és az ábrán látható metódusokkal mindjárt sokkal egyszerűbb a kommunikáció. A folytatásban megmutatom majd élesben is a használatát, valamint azt, hogy mivel segíti a bada a szöveges protokollok kezelését.

Karma

Azóta történt

Előzmények