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 Recull.

Per fer això, ens demanen la funció central() que farà el paper de servidor dels clients observadors. Gestionarà la comunicació de cada observador mitjançant l’execució d’un fil (observador()) independent dels altres observadors actius. El fil rebrà les troballes enviades per l’observador connectat al fil, actualitzant en el contenidor del tipus 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 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 (socket.AF_INET), i dades en forma de seqüència de bytes (socket.SOCK_STREAM). Les troballes dels observadors es donaran en forma de tuple (n, ocell, zona), on n (int) és el nombre de nius trobats, ocell (str) és l’ocell del niu, i zona (int o str) una referència a la zona geogràfica on s’ubica el niu. La comunicació es farà convertint tot el tuple en str, i de str a bytes. Disposeu del mòdul codis.py que us facilita dues funcions per rebre o transmetre tuples (repTroballa() i enviaTroballa()). A més, hi ha les funcions repOK() i 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ó observador(). La funció la trobareu a observador.py. El que fa és llegir un fitxer que conté troballes i enviar-les a la central.

Es demana

  1. Disposeu d’una primera versió del disseny de la classe Recull a recull0.py. Copieu el contingut del fitxer al fitxer de nom recull.py. La seva especificació és:

    Un exemple d’ús:

    >>> from  recull import Recull
    >>> troballes = [(  1, 'Alosa becuda', 100)] * 30 + \
    ...             [(  1, 'Alosa becuda', 101)] * 15 + \
    ...             [(  5, 'Pardal comú', 114)] * 40
    >>> rec = Recull()
    
    >>> for t in troballes:
    ...     n_nius, ocell, zona = t
    ...     rec.compta(n_nius, ocell, zona)
    ... 
    >>> print(rec)
    (Alosa becuda,100): 30
    (Alosa becuda,101): 15
    (Pardal comú,114): 200
    
    
    
    

    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.

  2. En el mòdul dades_observador.py dissenyeu la funció:

    dades_observador.dades_observador(ptConn, recull)
    Parameters:
    • ptConn (socket) – socket per rebre troballes i enviar conformitat.

    • recull (Recull) – on es guarden les dades

    Gestiona la connexió ptConn d’un observador acceptada a la central. Rep de l’observador les seves troballes. Cada troballa rebuda, l’acumula en recull, enviant a l’observador la conformitat de la recepció.

    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 try except us pot ajudar a preveure aquest final (socket.error: error del sistema operatiu al perdre la connexió). Quan hagi l’error, aprofitarem també per tancar ptConn i acabar la funció. Assegureu-vos també que el funcionament de la connexió tipus socket.socket sigui bloquejant socket.socket.setblocking().

  3. En el mòdul central.py dissenyeu la funció:

    central.central(conn, port, nConnexions)
    Parameters:
    • conn (str) – connexió internet

    • port (int) – port de la connexió

    • nConnexions (int) – nombre de connexions a tractar simultàniament.

    La funció serveix als observadors que recullen dades pel territori. Crea un contenidor del tipus Recull, i per cada observador que es vol connectar, crea un fil concurrent de la funció dades_observador() per gestionar la seva comunicació. La funció dades_observador() treballa a partir del socket de connexió acceptat i del contenidor creat i compartit per tots els altres fils engegats. La funció acaba un cop ha tractat nConnexions connexions.

    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 '' i 50007. El tercer és el que es dona per la línia de comanda.

    Per exemple:

    $ python3 central.py 3
    
  4. En el mòdul sistema.py dissenyeu la funció:

    sistema.sistema(troballes)
    Parameters:

    troballes (list) – llista de noms de fitxers de troballes per la simulació.. Cada fitxer és l’entrada a un procés observador.

    Engega un procés que executa central.central() amb els arguments de adreça '', el port 50007, i el nombre d’observadors a atendre (nb=len(troballes)). Per evitar problemes de connexió, ens esperem dos segons per continuar amb la resta de processos a engegar.

    Iniciem i engeguem nb processos amb la funció observador.observador() i els arguments de 'localhost', port 50007, i un nom de fitxer de la llista troballes.

    Esperem que tots els processos observadors acabin. Després, esperem el procés central. Un cop acabat els processos, acabem la funció.

    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.

  5. Escriviu en el fitxer sistema.sh, una línia de comanda en bash que executi els programes central.py, i 3 programes 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 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ó.

../../../../_images/packages1.svg

Disposeu de

  • codis.py per l’enviament i recepció de troballes i protocols.

Heu de dissenyar:

  • recull.py (redisseny de recull0.py)

  • dades_observador.py

  • central.py

  • sistema.py

  • sistema.sh

Per fer proves

Per provar els mòduls que heu dissenyat o modificat disposeu dels fitxers: