A la hora de implementar el diseño la programación orientada a objetos nos facilita la adaptación. Muchas de las entidades descritas anteriormente serán implementadas con una clase de Java o un conjunto de ellas. A continuación veremos una descripción global de la implementación del servidor.
La implementación propuesta es estructuralmente similar al diseño planteado, pero no todas las entidades están implementadas directamente como clases, en ocasiones se han unido varias entidades para formar una clase de la implementación, o una entidad ha quedado dividida en varias clases.
En cuanto al procesado de mensajes, hemos implementado varias clases que cumplen el papel de las entidades propuestas por el diseño:
· En el diseño tenemos una entidad llamada “Petición/Respuesta” (que podemos ver en la figura 3-3 del diseño). Esta entidad está implementada mediante las tres clases tmsg9P, rmsg9P y rpc9P. Estos clases están encargadas de extraer un mensaje 9P, ejecutar su llamada a procedimiento remoto y retornar el mensaje de respuesta.
· El diseño nos describe una entidad encargada de establecer conexiones de red y mantener sesiones abiertas (ver figura 3-4). Ésta es una estructura típica de un servidor que acepta múltiples conexiones, la hemos implementado mediante una clase llamada server9P.
La interfaz de acceso a recursos está descrita con las siguientes clases:
· La clase fidtable representa la entidad “Conjunto de descriptores” de la figura 3-5 del diseño. Una instancia de esta clase será una tabla que contenga descriptores de ficheros, representados por objetos de la clase pfid.
· La clase pfile es la representación de un fichero, y es equivalente a la entidad “Interfaz tipo fichero” que podemos ver en la figura 3-5 del diseño.
En la figura 4-1 podemos ver la interacción de todas estas clases al formar un servidor de ficheros 9P completo.

figura 4-1 Diagrama interacción de clases
Un ejemplo de cómo interactúan estas clases entre sí sería el siguiente:
1. Un mensaje es recibido por server9P.
2. El mensaje es procesado por una instancia de tmsg9P.
3. La acción requerida por el mensaje se delega a una RPC, representada por una instancia de la clase rpc9P.
4. La RPC consultará la tabla de descriptores (fidtable), para obtener el descriptor de fichero necesario, para ejecutar la acción requerida por el mensaje.
5. La RPC ejecuta las acciones sobre el descriptor de ficheros pfid.
6. Las instancias de la clase pfid acceden a la representación del fichero pfile, para ejecutar las acciones requeridas por la RPC.
7. El objeto pfid retorna el resultado a la RPC.
8. La instancia de la clase rpc9P genera un mensaje de respuesta rmsg9P.
9. El mensaje de respuesta se envía al objeto server9P.
10. Finalmente, server9P envía la respuesta por el canal de comunicación.
Dada la distinta naturaleza de las clases, éstas están agrupadas en dos paquetes:
· Clases relacionadas con los mensajes 9P, empaquetadas en el paquete P9P.
· Clase relacionadas con el
sistema de ficheros, empaquetadas PFS.
Las clases que incluye este paquete están relacionadas con el protocolo 9P. Existe una clase de tipo mensaje 9P llamada msg9P y otra clase que representa una RPC llamada rpc9P.
Para facilitar el acceso a los datos que componen un mensaje 9P, existen tipos básicos de datos creados especialmente para empaquetar y desempaquetar los mensajes, estos datos pertenecen a la clase type9P.
La clase principal de este paquete es server9P. Esta clase proporciona una implementación de un servidor 9P, gestionando las comunicaciones cliente-servidor y procesando los mensajes del protocolo. Está construida a partir de las otras clases del paquete, como son msg9P y rpc9P.
La clase server9P es un objeto encargado de esperar conexiones TCP y de leer los mensajes 9P del canal de comunicación TCP. La interfaz de uso es muy simple, sólo existe un método llamado ejecutar( ), que nos permite iniciar la escucha y recepción de mensajes. Es una clase abstracta que delega sus métodos a dos sub-clases, una desarrollada para J2SE y la otra para la versión ligera de Java J2ME (figura 4-2).

figura 4-2 Jerarquía de clases de server9P
Una instancia de esta clase es capaz de servir múltiples clientes 9P de forma simultánea. Para ello lanza un hilo de ejecución para cada cliente nuevo que quiera conectar con el servidor. Cada hilo de ejecución se encargará de la recepción y transmisión de los mensajes 9P por la conexión TCP, esto se muestra en la figura 4-3.

figura 4-3 Funcionamiento global del servidor 9P
El constructor de esta clase define varios parámetros de funcionamiento del servidor 9P. La forma del constructor es la siguiente:
server9P (int maxcon, int maxfid, int p, boolean show, pfile f)
El valor p establece el puerto TCP de escucha del objeto server9P.
El objeto pfile contenido en f (que representa un fichero) indica la raíz del sistema de ficheros que va a servir el objeto server9P. Para más información acerca de este objeto podemos consultar el apartado “4.2.1 Clase pfile” de la presente memoria.
El parámetro maxcon establece el número máximo de conexiones simultáneas admitidas por el servidor, maxfid determina el número máximo de descriptores reservados que podrán tener cada cliente en el servidor.
Por último, el campo show establece si la instancia de server9P
retornará mensajes por la salida estándar del sistema. Útil para depuración.
La clase msg9P representa un mensaje 9P. Dado que existen múltiples tipos de mensajes diferentes, existen sub-clases mensaje para cada tipo de mensaje 9P que podamos encontrar. En la figura 4-4 observamos la jerarquía de clases que sustenta la clase msg9P.

figura 4-4 Árbol de clases de la clase msg9P
Todas las sub-clases que dependen de msg9P heredan la siguiente interfaz:
Métodos de la clase msg9P |
|
|
void |
write(ByteArrayOutputStream buf) |
|
void |
read (byte[] msg, int i) |
|
String |
toString() |
|
Static msg9P |
SetTypeMsg(byte t) |
La clase msg9P es abstracta, al igual que sus sub-clases Tmsg9P y Rmsg9P. Los métodos write( ), read( ) y toString( ) sólo podrán ser invocados para instancias de mensajes 9P concretos, puesto que no tienen sentido si el tipo de mensaje 9P no está totalmente definido. El método estático setTypeMsg( ) nos sirve para especificar una instancia de un mensaje 9P. En la figura 4-5 podemos ver cómo estos métodos interactúan con un objeto msg9P.

figura 4-5 Diagrama interacción clase msg9P
El método estático SetTypeMsg( ) retorna un objeto msg9P de un tipo concreto. El tipo de mensaje retornado dependerá del Byte que se ofrezca como parámetro en el método. Si consultamos la especificación de los mensajes 9P recordaremos que el primer byte (excluyendo los dos bytes que determinan el tamaño del mensaje) define el tipo de mensaje. Por medio de este método podemos generar objetos msg9P de un tipo concreto basándonos en el primer byte del mensaje recibido por la conexión.
El método read( ) lee un mensaje 9P de un vector de Bytes, para almacenarlo en un objeto msg9P. El método write( ) escribe el mensaje 9P, que se almacena en un objeto msg9P, dentro de un buffer de Bytes. Mediante estos dos métodos, se realiza el aplanado y desaplanado de los mensajes, para su envió o recepción por el canal de comunicaciones.
El método toString( ) simplemente retorna una breve descripción del mensaje 9P que contiene el objeto. Se usa para depuración.
Dado que cada tipo de mensaje tiene una estructura diferente, las clases concretas que los representan adquieren campos de datos basados en la estructura. Así pues, la clase Tversion9P debe tener los campos de datos de un mensaje Tversion.
Recordando como es un mensaje 9P de tipo Tversion:
|
Tversion[1] |
Tag[2] |
Msize[4] |
Version[S] |
El mensaje Tversion9P deberá tener las propiedades tag, msize y version. Para no crear confusión, estos datos se llaman de la misma forma dentro del objeto Tversion9P, y esta regla es aplicable a las demás clases de mensajes.
Las propiedades de cada mensaje son de tipos diferentes como se puede ver en la especificación de los mensajes 9P. En términos de programación, existen datos numéricos de distintos tamaños y cadenas de datos. Para asegurar una interpretación adecuada e independiente de la arquitectura de estos valores, tenemos tipos específicos de datos, pertenecientes a la clase type9P.
La clase rpc9P representa una llamada a procedimiento remoto de un mensaje 9P. La implementación actual es de lado del servidor 9P, las RPC implementadas están asociadas a T-mensajes y retornan R-mensajes como respuestas.
Para cada sub-clase de tmsg9P existe una clase concreta de tipo rpc9P. La figura 4-6 muestra la jerarquía de clases.

figura 4-6 Árbol de clases de la case rpc9P
La interfaz que heredan las sub-clases de la clase abstracta rpc9P es la siguiente:
|
Métodos de la clase rpc9P |
|
|
static rpc9P |
getrpc(tmsg9P tmsg) |
|
rmsg9P |
process(fidtable fidt) |
El método de clase getrpc( ) retorna una instancia de un objeto rpc9P concreto de un tipo de mensaje específico. El tipo de rpc9P que retorna depende del mensaje 9P que le pasemos por parámetro. Si usamos un objeto Tread9P como parámetro, nos retornará la RPC del mensaje Rread, un objeto rpc9Pread.
Hemos visto como en el diseño del servidor, las entidades RPC eran las encargadas de comunicarse con los recursos por medio de la interfaz de acceso a recursos. Por ello es necesario el parámetro fidtable en la llamada al método process( ). El objeto fidtable se analiza más adelante y representa un sistema de ficheros.
El método process( ) es el encargado de ejecutar una RPC. Para que un objeto rpc9P pueda invocar este método tiene que ser una instancia concreta de una de las sub-clases de rpc9P (no tendría sentido ejecutar una RPC indefinida).
El método process( ) retornará un mensaje 9P de tipo rmsg9P como respuesta a la petición realizada por un cliente.

figura 4-7 Diagrama interacción de la clase rpc9P
En la figura 4-7 podemos ver como interactúa un objeto rpc9P con el resto de entidades. Un ejemplo de lo que sucede en la figura podría ser que tmsg9P represente un mensaje de tipo Topen, dado este mensaje se usaría el método getrpc( ) para obtener un objeto rpc9P correspondiente a un Topen (1). Posteriormente, se usaría el método process( ) para ejecutar la RPC (2), usando como parámetro un objeto fidtable (representante del sistema de ficheros). Finalmente, se retornaría una respuesta en un mensaje rmsg9P, que podría ser un Ropen o un Rerror, si algo fue mal.
La clase type9P implementa los tipos de datos que podemos encontrar dentro de los mensajes 9P. En la especificación del protocolo encontramos datos de tipo numérico de varios tamaños, cadenas de caracteres y estructuras de datos. Cada tipo de dato está codificado de una forma determinada dentro del mensaje, tal como se define en la especificación de 9P. En la figura 4-8 podemos ver la jerarquía de clases que desciende de type9P.

figura 4-8 Árbol de clases de la case type9P
Todos las sub-clases que dependen de type9P heredan la siguiente interfaz:
|
Métodos de la clase type9P |
|
|
void |
write(ByteArrayOutputStream buf) |
|
void |
read (byte[] msg, int i) |
|
int |
len() |
|
String |
toString() |
El método write( ) escribe el valor del dato dentro de un buffer de bytes. El método read( ) lee el tipo de dato de un array de bytes, a partir de la posición i. Por último, len( ) retorna el tamaño del dato.
El método toString( ) retorna una representación del dato, dentro de una cadena de caracteres.
Los datos almacenados por estas clases son de carácter diverso, al igual que sus campos de datos internos.
Diferenciamos tres tipos de clases que heredan de type9P:
· Tipos numéricos: Clases byte9P, short9P, int9 y long9P.
· Tipos cadena: Clases String9P y data9P.
· Estructuras: Clases qid9P
y stat9P.
Estas clases abarcan todas las representaciones numéricas que existen en los mensajes 9P. Los tamaños numéricos que aparecen en los mensajes son de 1, 2, 4 y 8 Bytes, representados por las clases byte9P, short9P, int9P, long9P respectivamente.
En la especificación de mensajes 9P los valores numéricos son UNSIGNED (sin signo). Están escritos de la forma little-endian, el byte menos significativo primero. Esta forma de codificación de los valores no debe preocuparnos, ya que, los métodos read( ) y write( ) de cada tipo se encargan de transformarlos.
Todos estos tipos tienen un campo llamado value, donde encontramos el valor numérico almacenado por el objeto.
|
TIPO |
Campo |
|
byte9P |
short value |
|
short9P |
int value |
|
int9P |
long value |
|
long9P |
long value |
Tipo cadena
Estas clases se usan para almacenar texto o bloques de datos. Existen dos clases de este tipo, String9P y data9P, diferenciadas en que el tamaño del String9P es auto contenido, como especifica el protocolo 9P. En cambio data9P es una ristra de tamaño desconocido, por ello tiene un método read( ) diferente que especifica el tamaño de datos que se van a leer:
public void read(byte[] b, int i, int sz)
Dentro del campo value encontraremos la cadena de caracteres que almacenan estas clases.
|
TIPO |
Campo |
|
String9P |
String value |
|
data9P |
String value |
Estructuras
Los mensajes 9P tienen una estructura muy parecida, por ello existen estructuras de datos con múltiples campos que aparecen en muchos mensajes.
Las clases qid9P y stat9P representan estas estructuras. Están construidas a partir de los tipos básicos anteriores. La clase qid9P almacena el QID de un fichero, un identificador único. La clase stat9P almacena los metadatos del fichero, información sobre los atributos del fichero.
Los tipos anteriores sólo tenían un campo donde almacenaban la información, estas estructuras tienen múltiples campos de información.
La siguiente figura muestra los campos asociados a estas estructuras:
|
TIPO |
Campo |
|
qid9P |
byte9P type int9P vers long9P path |
|
stat9P |
short9P type int9P dev qid9P qid int9P mode int9P atime int9P mtime long9P length String9P name String9P uid String9P gid String9P muid |
El paquete PFS alberga las clases relacionadas con el acceso a los recursos del sistema. Está implementado como una interfaz de acceso a ficheros en un disco, aunque internamente pueda tratarse de otros recursos.
Podemos distinguir principalmente tres clases importantes: pfid, pfile y fidtable. Éstas clases están estrechamente relacionadas y su función es implementar un sistema de ficheros y descriptores independiente. La figura 4-9 muestra cómo interactúan estas clases para generar un sistema de ficheros.

figura 4-9 Diagrama interacción clases del paquete PFS
La clase pfile es la representación de un fichero, pero al igual que en un sistema operativo, para poder acceder al objeto pfile es necesario un descriptor de fichero, que es lo que representa la clase pfid. La clase fidtable es una tabla de descriptores pfid disponibles para el acceso a recursos.
La clase pfile representa un fichero. Cada instancia de esta clase tiene un recurso real asociado, como un fichero en un disco. Aunque el recurso pueda no ser un fichero, su interfaz a través del objeto pfile será parecida a la de un fichero.
En la siguiente tabla vemos los métodos asociados de la clase pfile.
|
Métodos de la clase pfile |
|
|
boolean |
isDir() |
|
boolean |
isReadable() |
|
boolean |
isWritable() |
|
boolean |
isExecutable() |
|
String |
getname() |
|
stat9P |
getStat() |
|
qid9 |
getqid() |
|
void |
wstat(stat9P newst) |
|
pfile |
create(String name, long perm) |
|
pfile |
walk(String s) |
|
void |
remove() |
|
pfile[] |
getListOfNodes() |
El método create( ) crea un pfile nuevo de nombre name, los permisos del nuevo fichero pfile vienen dados por el parámetro perm, esta clase retorna el objeto pfile creado, permitiendo que creemos nuevos objetos en la jerarquía de objetos pfile. El parámetro perm definirá (entre otras cosas) si el nuevo objeto creado es de tipo directorio o tipo fichero. El método remove( ) elimina el pfile, siempre que no tenga descendencia en la jerarquía, es el otro mecanismo que tenemos para gestionar la estructura.
El método walk( ) retorna un objeto pfile requerido mediante el parámetro s, que define una ruta de búsqueda dentro de la jerarquía de objetos pfile.
El método wstat( ) permite modificar las propiedades de un fichero, en función de las indicadas en la estructura STAT que pasamos como parámetro al método, esto nos permite modificar valores tales como el nombre de fichero.
Algunos métodos son puramente informativos, nos proporcionan información del objeto pfile retornando verdadero o falso según las propiedades del pfile. Estos métodos son isDir( ), que retorna verdadero si el objeto es la representación de un fichero tipo directorio, isReadable( ) e isWritable( ), informan de si el objeto puede ser leído o escrito respectivamente, y por último isExecutable( ), retorna verdadero si el fichero tiene la propiedad de ser ejecutable.
Otros métodos informativos son getname( ), que retorna una cadena con el nombre del fichero pfile, getStat( ), que retorna una estructura de tipo stat9P con información acerca del fichero, y getqid( ), retornando una estructura de tipo qid9P con el QID del fichero.
Por último, getListOfNodes( ) retorna una tabla de objetos pfile con los hijos del pfile (con los objetos que podría retornar el método walk( )).

figura 4-10 Diagrama interacción de la clase pfile
La clase pfile es una clase abstracta, no especifica el tipo de recurso que estamos abstrayendo en un fichero. Pueden implementarse sub-clases de pfile que accedan a cualquier recurso que nos interese. Actualmente sólo existen dos subclases que se sirven de la interfaz de la superclase pfile. En la figura 4-11 vemos la jerarquía de clases de pfile.

figura 4-11 Diagrama clases de pfile
La sub-clase pfileramfs crea una abstracción de fichero en memoria y pfiledisk crea una capa de abstracción sobre ficheros reales. Ambas clases heredan todos los métodos de pfile por lo que su interfaz es idéntico.
Muchos recursos están organizados de una forma jerárquica, el caso más claro son los sistemas de ficheros. Los objetos pfile pueden imitar esta característica creando estructuras con los recursos, para ello existen dos clases de objetos pfile, “directorios” y “ficheros”.
Podría darse el caso de que queramos generar un tipo de pfile que ofrezca acceder a la información de un sensor de temperatura, como si de un archivo se tratase. Una forma de representar la información del sensor es un fichero que contenga la temperatura del sensor, podríamos llamarlo “/temperatura” y podría estar ubicado en la raíz del sistema de ficheros. Este objeto pfile no permitiría crear mas ficheros, ni modificar el fichero “/temperatura”. A través de la lectura del contenido del único fichero obtendríamos la información sobre el sensor.
La estructura de objetos pfile se construye bajo demanda. Esto significa que la estructura no existe en memoria hasta que no se intente acceder a los recursos. En la figura 4-12 vemos que la estructura de objetos estaría parcialmente construida, pero si intentásemos acceder al fichero “/file1”, los objetos pfile se encargarían de construir el nuevo nodo de la estructura para dicho fichero.

figura 4-12 Generación de estructuras pfile bajo demanda
La clase pfid representa un descriptor de fichero. Una instancia de pfid permite acceder a un objeto pfile.
Un pfile puede ser referenciado por varios pfids y cada uno de ellos puede estar haciendo uso del fichero de diversas formas. Los métodos que pueden ser invocados en un pfile son los siguientes.
|
Métodos de la clase pfid |
|
|
boolean |
isOpen() |
|
qid9P |
getqid() |
|
stat9P |
getStat() |
|
pfile |
getfile() |
|
void |
setfile(pfile f) |
|
pfile |
walk (String s) |
|
pfile |
create (String name, long perm) |
|
void |
open (short nmode) |
|
void |
close() |
|
byte[] |
read (long offset, long count) |
|
int |
write (long offset, byte[] data) |
|
void |
remove() |
Con el método getfile( ) obtendremos el objeto pfile asociado al pfid. El método setfile( ) establece cual será el pfile referenciado por el descriptor pfid. Estos métodos son útiles a la hora de instanciar nuevos descriptores ya que un pfid puede no referenciar a ningún fichero pfile o podemos desear que lo haga a un pfile diferente.
El método open( ) realiza una apertura del descriptor según el modo indicado por el parámetro nmode. Si queremos que un pfile deje de estar abierto debemos usar el método close( ). La necesidad de que un fichero esté abierto, para realizar operaciones, facilita aspectos en la implementación. Generalmente, la operación de apertura de un fichero prepara el acceso al recurso, y aunque esto puede llegar a ser innecesario para algunos recursos mantiene la transparencia de la interfaz.
El método isOpen( ) retorna verdadero si el pfid es un descriptor abierto o falso en caso contrario. Es útil para verificar el estado del pfid.
Los métodos principales del objeto pfile son sin duda read( ) y write( ), que nos permiten leer y escribir la información almacenada por el objeto pfile. Antes de poder usar estos métodos, el descriptor tendrá que estar abierto para el modo de acceso adecuado. El tamaño de la información leída o escrita debe ser como máximo el valor definido por count. El parámetro offset especifica el punto a partir del cual se realizará la operación en el fichero pfile.
Los métodos getqid( ), getstat( ), create( ), walk( ) y remove( ) realizan la misma función que los métodos, con el mismo nombre, asociados a la clase pfile. Están implementados por motivos de comodidad a la hora de usar los descriptores pfid.
La clase fidtable es una colección de objetos pfid, junto con una estructura de objetos pfile. Todos estos elementos que forman la clase fidtable implementan una abstracción de una tabla de descriptores similar a la de un sistema operativo.
La interfaz que ofrece la clase fidtable para gestionar los descriptores es la siguiente:
|
Métodos de la clase fidtable |
|
|
pfile |
getroot () |
|
pfid |
get(long fid) |
|
pfid |
getNew(long fid) |
|
boolean |
isFreeFid(long fid) |
|
int |
drop(long fid) |
El método getroot( ) retorna el fichero pfile raíz de la estructura. Esto nos es útil como punto de partida de la estructura, inicialmente no conocemos otro pfile.
Los métodos get( ) y getnew( ) retornan un descriptor de fichero pfid basándose en un dato numérico de tipo long, un FID similar al que usan la mayoría de los sistemas para identificar descriptores. El método getnew( ) reserva un nuevo pfid y le asigna el identificador fid que usamos como parámetro. El método get( ) devuelve el objeto pfid que estaba reservado para el identificador fid. Si no hay ninguno asociado a ese FID retornará un error, igual que si no es posible generar nuevos pfid.
El método isFreeFid( ) nos sirve para verificar si un FID (el que pasamos por el parámetro fid) está disponible. Esto nos sirve para realizar su reserva posteriormente.
Por último, el método drop( ) elimina un descriptor de fichero y libera el FID asociado (esto no eliminará el fichero al que hacia referencia el descriptor), es útil para deshacernos de descriptores que no necesitemos usar más.
Cuando generemos una instancia de fidtable debemos usar el siguiente constructor:
public fidtable (int max, pfile f)
Donde el valor max establece el número máximo de descriptores que se podrán tener simultáneamente en uso y f es un objeto pfile que se usará como raíz de la estructura de ficheros.