A UDP egy kapcsolat nélküli protokoll. A kommunikáció UDP csomagokkal (packet) történik, és a csomagok megérkezése nem garantált (elveszhetnek útközben), továbbá, mivel az egyes csomagok akár más-más útvonalon is eljuthatnak a célhoz, a csomagok megérkezésének sorrendje is eltérhet a küldési sorrendtől.
UDP-t alkalmaznak például olyan esetekben, ahol egy csomagot érdemes inkább eldobni, mint várni annak újraküldésére. Ilyen lehet például: video-streaming, voice-over-IP alkalmazások (pl. internetes telefon), illetve bizonyos online játékok.
Az Erlangban a gen_udp modul tartalmazza a UDP protokollal kapcsolatos függvényeket.
Egy egyszerű példa UDP socketek használatára:
A kliensen a következőképpen nézhet ez ki:
Míg a szerveren:
Figyeljük meg, hogy mindkét üzenet listaként érkezett a szerverhez, pedig az egyiket binary-ként küldtük. Ez a viselkedés befolyásolható, ennek részletei később következnek.
UDP socketek létrehozásához a következő függvényhívások használhatók:
A Port paraméter határozza meg azt a portot, ami a sockethez lesz kötve. Az OptionList segítségével adhatunk meg opciókat, amelyekkel felülbírálhatjuk az alapértelmezett értékeket. A fontosabb opciók a következők:
Az {active, true} opcióval adható meg. Egy socket alapértelmezett esetben aktív módban jön létre. Minden beérkező üzenet továbbítódik a socketet birtokló processz részére Erlang üzenetként, a következő formában:
A Socket a fogadó socket, az IP és a PortNo a küldő IP címe és portszáma, a Packet pedig magát az üzenetet tartalmazza.
Az {active, false} opcióval adható meg. Passzív módban az üzeneteket a gen_udp:recv/2 és gen_udp:recv/3 függvények segítségével kell lekérdezni.
Az {active, once} opcióval adható meg. Ebben a módban az első üzenet automatikusan továbbítódik, az ezután következő üzeneteket viszont a recv függvénnyel kell lekérni.
A visszatérési érték minden esetben egy ok atom.
Passzív mód esetén explicit módon le kell kérni a csomagot a sockettől.
Ez a következő függvényhívások segítségével tehető meg:
Ha az időkorláton belül érkezett csomag:
Ellenkező esetben:
Ha a gen_udp:recv függvényt nem passzív módban hívja meg egy process, akkor {error, einval} hibakódra számíthat, ami az "invalid argument" megfelelője.
Egy egyszerű UDP szerver a következőképpen nézhet ki Erlangban:
Egy egyszerű UDP kliens pedig a következőképpen:
A timeoutra azért van szükség, mert a UDP nem megbízható protokoll, és a csomag elveszhet.
A UDP leggyakoribb alkalmazása az SNMP protokoll, ami a Simple Network Management Protocol rövidítése. Az SNMP-t IP-alapú hálózatokban használják a különböző eszközök és rendszerek monitorozására.
A TCP egy megbízható, kapcsolat-orientált protokoll, amely adatfolyamokkal dolgozik. A csomagok megérkezése garantált, a megérkezés sorrendje pedig a küldés sorrendjével megegyezik. Egy TCP kapcsolat a felépülés után mindaddig megmarad, amíg valamelyik fél le nem zárja azt, vagy valamilyen hiba miatt meg nem szakad.
TCP-t alkalmaznak például a HTTP kérésekben, peer-to-peer alkalmazásokban, azonnali üzenetküldő szolgáltatásokban.
Egy kapcsolat kialakításakor gyakran minden beérkező kérésnek egy-egy új folyamatot indítanak, amelyek a kapcsolatokat mindaddig nyitva tartják, amíg az adott kérés kiszolgálása folyamatban van.
Egy beérkező kérés esetén kétféle megoldást szoktak használni:
Az Erlangban a gen_tcp modul tartalmazza a TCP protokollal kapcsolatos függvényeket.
Aktív módban a folyamat {tcp, Socket, Packet} formájú üzeneteket kap, ahol:
Passzív mód esetén a UDP-hez hasonlóan:
Visszatérési értéke: {ok, Packet}
A Length paraméter csak "raw" módban releváns. Ez a paraméter mondja meg, hogy hány bájt adatot várjon, mielőtt visszatér az eredménnyel. Ha a paraméter értéke 0, akkor minden elérhető adatot megkapunk. Ha a küldő lezárja a kapcsolatot, mielőtt a megfelelő számú bájt elküldésre került volna, ezek az adatok eldobásra kerülnek.
A passzív módú átvitel egy jó módszer arra, hogy elkerüljük a túl sok kérés beérkezéséből adódó problémákat. Gyakori tervezési minta, hogy a beérkező kérések kezelésére egy-egy új folyamatot indítanak. Ez azonban extrém esetekben, nagy terhelés esetén azzal járhat, hogy a virtuális gép memóriája elfogy, ahogy szaporodnak a kérések, és ezáltal a folyamatok. Passzív módban a TCP puffer használható ennek megelőzésére: ha túl sok kérés jön be, és azok még nem kerültek lekérésre, a puffer megtelik, és a kliens kérése visszautasításra kerül. Az, hogy ilyen védelmi mechanizmusra szükség van-e hirtelen megugró hálózati forgalom esetén, például egy terhelésteszt segítségével dönthető el.
A kliens egy hostnevet és egy binary-t vár paraméterül. Létrehoz egy socketet az 1234-es porton, és a binary-t 100 bájtos részekre darabolva elküldi a célgépre.
Az üzenet darabokra bontása a <<Chunk:100/binary, Rest/binary>> kifejezéssel lehetséges. Az első 100 bájt a Chunk-nak, a maradék rész a Rest-nek fog megfelelni. Ha az üzenet kevesebb, mint 100 bájtot tartalmaz, az első klózra történő mintaillesztés sikertelen lesz. A második klózra való mintaillesztés viszont sikeres lesz, és a maradék üzenet elküldésre kerül:
A szerveroldalon egy listener folyamat várja a kliensektől a kapcsolatkérelmeket. Amikor a kérés megérkezik, a listener folyamat veszi át a kérés kezelését, és passzív módban fogad binary-ket. Az ezután következő kapcsolatkérelmek figyelésére létrehoz egy új folyamatot, ez lesz az új listener folyamat. Az eredeti listener most a létrejött kapcsolat kezelésével foglalkozik, a kapott adatokat egy listához fűzi, és amikor a socket lezárásra kerül, kiírja az adatokat egy fájlba.
A wait_connect függvény végzi a beérkező kapcsolatok kezelését, és gondoskodik az új folyamatok indításáról is (spawn/3).
A get_request függvény érdekessége, hogy (hatékonysági okokból) a lista elejére teszi az újonnan kapott adatokat, így a socket lezárásakor a listát meg kell fordítani. Ezt a lists:reverse/1 segítségével teszi.
A szervert a tcp:server() paranccsal indíthatjuk el, a klienst pedig a következőképpen:
Látható, hogy a parancsok nagy része hasonló a UDP példában látottakhoz. A fő különbség ez a hívás:
Ez létrehoz egy listener socketet, amely vár egy bejövő kapcsolatra. A paraméterezése nagyon hasonló a gen_udp:open/2-éhez, néhány TCP-specifikus opciót kivéve. Ezek az alábbiak:
A többi flag leírása az inet és gen_tcp modulok dokumentációjában található.
A gen_tcp:listen/2 hívás azonnal visszatér egy socket azonosítóval, amit aztán a következő függvényeknek lehet továbbadni:
Az első hívás blokkolja a program futását mindaddig, amíg be nem érkezik egy kapcsolódási kérelem. A második esetben a TimeOut paraméter az időlimit ezredmásodpercben megadott értéke. Amennyiben ez alatt az idő alatt nem érkezik kapcsolódási kérelem, az accept {error, timeout} értékkel tér vissza.
Kapcsolatot a következő hívással lehet kezdeményezni:
Mivel a példánkban passzív módú socketeket használunk, a gen_tcp:recv/2 és gen_tcp:recv/3 hívásokkal kell lekérni a bejövő üzeneteket. Ha aktív módban futna, a folyamat Erlang üzeneteket kapna, melyek lehetséges tartalma a következő:
A gen_tcp:close(Socket) hívással tehető meg, akár a kliens-, akár a szerveroldalon. Mindkét esetben egy {tcp_closed, Socket} üzenet kerül küldésre a túloldal felé, ezzel lezárva a másik oldalon is a socketet.
A vezérlőfolyamat általában az a folyamat, amely a kapcsolatot egy gen_tcp:accept hívással elfogadta vagy egy gen_tcp:connect hívással kezdeményezte. Az üzeneteket átírányítani, és ezzel a vezérlést egy másik folyamatnak átadni a
függvény meghívásával lehetséges.
Az előző példánkban a gen_tcp:accept-t hívó processz lett a vezérlőfolyamat, és létrehoztunk egy új listener folyamatot. Ha azt szeretnénk, hogy a listener folyamat megmaradjon, és a beérkezett kapcsolat kezelésére szeretnénk egy másik folyamatot léthrehozni, akkor a kód így nézne ki:
Azaz egy gen_tcp:controlling_process(Socket, Pid) hívással átadtuk az új kapcsolathoz tartozó socket vezérlését az újonnan létrehozott folyamatnak.