Nius ==== Context ------- Es vol fer un atles d'ocells nidificats de Catalunya. Per fer-ho, dividim el territori amb una reixa de quadrats de 1km de costat, escampant molts observadors al llarg dels quadrats en estudi. Cada quadrat té una referència que anomenarem **zona** (geogràfica). Cada observador disposa d'un mòbil connectat a Internet amb una aplicació en que indica a un ordinador central si ha trobat un niu o més d'ocells, quin tipus d'ocell es tracta, i en quina zona geogràfica l'ha trobat. Anomenarem **troballa** a la tríada de dades observades. L'ordinador central recull les troballes enviades pels observadors. Aquestes son tractades per tenir comptabilitzats per cada tipus d'ocell i zona on es troben, el nombre de nius trobats. El resultat es guarda en un contenidor del tipus :py:class:`Recull`. Per fer això, ens demanen la funció :py:func:`central` que farà el paper de *servidor* dels *clients* observadors. Gestionarà la comunicació de cada observador mitjançant l'execució d'un fil (:py:func:`observador`) independent dels altres observadors actius. El fil rebrà les troballes enviades per l'observador connectat al fil, actualitzant en el contenidor del tipus :py:class:`Recull`, els comptadors de nius concernents a les troballes processades. El contenidor és el mateix per la resta de fils actius (observadors actius). La comunicació entre l'ordinador de la central i els mòbils dels observadors es fa via :py:class:`socket.socket` seguint el patró de client-servidor. És a dir, l'observador envia una troballa al servidor. El servidor envia la conformitat de haver-la rebuda i la processa. La comunicació la farem via *sockets* que accepten la família d'adreces d'IP v4 (:py:const:`socket.AF_INET`), i dades en forma de seqüència de bytes (:py:const:`socket.SOCK_STREAM`). Les troballes dels observadors es donaran en forma de tuple `(n, ocell, zona)`, on `n` (:py:class:`int`) és el nombre de nius trobats, `ocell` (:py:class:`str`) és l'ocell del niu, i `zona` (:py:class:`int` o :py:class:`str`) una referència a la zona geogràfica on s'ubica el niu. La comunicació es farà convertint tot el tuple en :py:class:`str`, i de :py:class:`str` a :py:class:`bytes`. Disposeu del mòdul :download:`codis.py` que us facilita dues funcions per rebre o transmetre tuples (:py:func:`repTroballa` i :py:func:`enviaTroballa`). A més, hi ha les funcions :py:func:`repOK` i :py:func:`enviaOK` com a protocols de conformitat en la comunicació de dades. Per comprovar el desenvolupament de tot el sistema farem una simulació del comportament d'un observador mitjançant processos independents que executaran la funció :py:func:`observador`. La funció la trobareu a :download:`observador.py`. El que fa és llegir un fitxer que conté troballes i enviar-les a la central. Es demana --------- #. Disposeu d'una primera versió del disseny de la classe :py:class:`Recull` a :download:`recull0.py`. Copieu el contingut del fitxer al fitxer de nom :file:`recull.py`. La seva especificació és: .. automodule:: recull0 :members: Un exemple d'ús: .. literalinclude:: test-recull.txt :language: python .. :start-after: --inici-compte .. :end-before: --fi-compte Tenint en compte que una instància d'aquesta classe serà compartit per dos o més processos, cal retocar algun o alguns mètodes per tal que la classe funcioni de forma segura. Es tracta d'afegir on calgui, les instruccions oportunes. En cap cas, cal modificar les instruccions ja existents. Tingueu en compte també que en una zona geogràfica poden haver *n* exploradors observant el mateix ocell. També, que la operació d'accés al diccionari no és atòmica i per tant hi ha situació de competència entre observadors de distintes zones i ocells. #. En el mòdul :file:`dades_observador.py` dissenyeu la funció: .. automodule:: dades_observador :members: La comunicació entre `observador` i `central` es tancarà per part de l'`observador`. Això suposarà també el final del fil d'obtenció de dades corresponent. La construcció de python :keyword:`try` :keyword:`except` us pot ajudar a preveure aquest final (:exc:`socket.error`: error del sistema operatiu al perdre la connexió). Quan hagi l'error, aprofitarem també per tancar :py:data:`ptConn` i acabar la funció. Assegureu-vos també que el funcionament de la connexió tipus :py:class:`socket.socket` sigui bloquejant :py:meth:`socket.socket.setblocking`. #. En el mòdul :file:`central.py` dissenyeu la funció: .. automodule:: central :members: Feu que el mòdul sigui també un **programa amb un paràmetre de línia** que serà el nombre d'observadors a atendre. Els dos primers arguments de la funció seran :py:data:`''` i :py:data:`50007`. El tercer és el que es dona per la línia de comanda. Per exemple:: $ python3 central.py 3 #. En el mòdul :file:`sistema.py` dissenyeu la funció: .. automodule:: sistema :members: Feu que el mòdul sigui també un **programa tenint com a paràmetres de línia els fitxers a processar pels observadors**. Per exemple:: $ python3 sistema.py /tmp/dadesexpl1.dat /tmp/dadesexpl2.dat /tmp/dadesexpl3.dat simularà 3 observadors amb la central. Cada observador processa un dels fitxers de troballes donats. Si s'haguessin posat 5 fitxers, en simularia 5. Tingueu-ho en compte doncs, el nombre variable d'arguments. #. Escriviu en el fitxer :file:`sistema.sh`, una línia de comanda en bash que executi els programes :py:mod:`central.py`, i 3 programes :py:mod:`observador.py` concurrentment. Cal que el programa central.py sigui el primer en engegar-se, i un cop fet, esperar uns dos segons abans de llençar els programes observador. Com a arguments d'observador podeu usar els fitxers creats a l'executar el doctest :download:`test-central-observador.txt`. Trobareu els fitxers al directori /tmp/. Es valorarà el fer-ho en **una sola línia** de text. Es disposa ---------- En aquest graf d'importacions podem veure tots els mòduls de l'aplicació. .. En blau el que hem de crear de nou, en vermell, els que es disposa d'una versió inicial a retocar, i en negre, els que ja estan dissenyats. .. image:: packages.svg :width: 50% Disposeu de - :download:`codis.py` per l'enviament i recepció de troballes i protocols. Heu de dissenyar: - :file:`recull.py` (redisseny de :download:`recull0.py`) - :file:`dades_observador.py` - :file:`central.py` - :file:`sistema.py` - :file:`sistema.sh` Per fer proves -------------- Per provar els mòduls que heu dissenyat o modificat disposeu dels fitxers: - :download:`observador.py` - :download:`test-central-observador.txt` . Aquest doctest crea en el directori `/tmp/` 5 fitxers per simular les troballes dels observadors (els noms son de l'estil :file:`dadesexpl1.dat`)