Академический Документы
Профессиональный Документы
Культура Документы
La Implementación de un
Servidor Multiusuario
2
Contenido CAPÍTULO 19
El servidor de código fue escrito y probado en Linux. Sin embargo, debe ser
fácilmente capaz de ejecutar el servidor en cualquier plataforma Java que se
ejecuta.
4
Redes de Java
Una vez que tenemos un hilo para cada puerto, ¿cómo sabemos que el hilo
está ahí para recibir información? La respuesta está en la red código de Java.
Cuando le decimos a Java que estamos interesados en obtener alguna
información sobre un determinado puerto, se sienta y espera hasta que algo se
recibe y, a continuación, le permite a su código para continuar. Esto se conoce
como un bloqueo de llamada. El método de los bloques (es decir, no ida y
vuelta) hasta que haya algo para darle. Esto es bueno porque significa que
usted no necesita escribir su propio bucle sin fin que continuamente pruebas
de información de la red. Cada vez que el método devuelve, usted sabe que
tiene información válida.
Una vez que tengamos algo de información, tenemos que ser capaces de
volver a escuchar el reloj lo más rápidamente posible. Decir que tenemos un
gran paquete cuyo contenido tenemos que digerir. Si acabamos de ir y el
proceso que inmediatamente después de su recepción, no se regresan, a la
escucha durante mucho tiempo. Ahora, usted ya debería haber adivinado la
5
Nota: Java 1.0 no ofrece ninguna de las opciones que podrían utilizarse para el Berkeley
Socket de C / C implementaciones, como la fijación de veces persisten. Algunas de estas
opciones se incluirán en Java 1.1.
intentar
{
ServerSocket listen_socket = new ServerSocket (puerto);
}
catch (IOException e)
{
System.err.println ( "No se puede abrir el Registro socket"
e.getMessage ());
retorno; / / o hacer algo que se detiene el programa, etc
}
Una vez que haya creado un ServerSocket con los número de puerto, tiene un
6
El zócalo de la escucha no puede hacer nada con una conexión a menos que
usted le pedirá que le avise, que se realiza con el método de aceptar. Este
método trata de dos problemas para usted. No sólo para escuchar una nueva
conexión, pero, una vez que haya uno, sino que también aprobar una nueva
instancia para Socket que usted comience a tratar.
if (new_connection! = null)
{
/ / Hacer algo aquí con el recién creado conexión
Thread.yield) ();
}
}
Tenga en cuenta que el método de ejecución tiene un bucle sin fin en el mismo.
Si no, entonces después de responder a la primera conexión que salir, y usted
no sería capaz de responder a cualquier duda, más conexiones, no lo que
7
quiere hacer. Una vez que el plazo finaliza el método, lo hace el hilo. No hay
manera de que sólo una copia de seguridad de fuego de nuevo.
Procesamiento de la información
Ahora que una buena relación se ha hecho, tenemos que hacer algo con la
información que llega desde el cable. Como ya se ha señalado anteriormente
en este capítulo, una clase aparte que se hace se refiere a una solicitud
individual. Esta clase implementa un hilo. Así que una vez que haya creado una
conexión, tendrá que empezar a correr.
RegistryMessage msg;
msg = nuevo RegistryMessage (new_connection, servidor, depuración);
msg.start ();
manager.add (msg);
Con estos dos, ahora podemos utilizar los métodos de manipulación del flujo
normal como ReadLine para la entrada y la salida de println. (Usted debe estar
familiarizado con estos conceptos de la System.out.println pide utilizados en el
código de depuración!)
Registro de Conexiones
Nota: Como lo explicaré más tarde, el cierre de la conexión de socket se deja como
una responsabilidad de cada clase, no de la gerente.
Un gestor de clase es muy poco. Lo único que hace es añadir una nueva
referencia a su interior el almacenamiento de datos. Cada cierto tiempo,
que se desplaza a través de todos ellos y elimina las conexiones de
muertos. Las operaciones se definen por el hilo dejado de publicarse.
Podemos comprobar esta condición IsAlive llamando al método de cada
objeto. Para este proyecto, el dispositivo de almacenamiento de datos es
sólo un vector. 19,1 es la lista completa a la fuente para el gestor de
registro del servidor.
paquete de registro;
RegistryManager público ()
{
int i;
RegistryMessage msg;
while (true)
{
msg =
10
Cerrando la conexión
intentar
{
listen_socket = new DatagramSocket (puerto);
}
catch (IOException e)
{
System.err.println ( "No se puede abrir el filtro de toma de" +
e.getMessage ());
retorno;
}
12
Hasta ahora, todo bien. Ahora las cosas empiezan a cambiar un poco. En lugar
de escuchar para una nueva conexión, sólo tenemos que recibir un nuevo
paquete. Sí, usted adivinado, la tiene un DatagramSocket recibir método. El
código nuevo esquema es muy similar a la de TCP código, excepto que esta vez
tenemos que tener un DatagramPacket preconstructed para pasar a recibir la
llamada:
intentar
{
listen_socket.receive (paquete);
}
catch (IOException e)
{
System.err.println ( "paquete de error");
}
Procesamiento de la Información
}
catch (IOException e)
{
System.err.println ( "error en el servidor de actualización" + e.getMessage
());
}
}
El Registro
Teniendo una mirada a la Figura 19-1, verá que el Registro subsistema opera
por separado de los otros servidores. Este parece un buen lugar para empezar.
El registro es un servidor TCP, lo que significa que tenemos que comenzar con
el sistema de servidor de TCP. Ya has visto la mayoría de los del lado del
servidor parte del código, así que me limitaré a comentar sobre las distintas
fases de transformación.
Como puedes ver en la figura 19.1, hay dos partes para el sistema de registro.
Uno de ellos es la interfaz de red, que se ocupa de la conectividad de TCP y el
análisis de la información, y el otro es el servidor de registro. El servidor
mantiene la información acerca de todas las entidades de control y el acceso a
los mismos por los particulares.
El registro de servidor tiene una función: para controlar el acceso a las distintas
entidades. Todas las solicitudes de cambio de información requieren una
entidad ID y el nombre del usuario. Si el nombre de usuario determinado no
representa a los propietarios de la entidad, entonces el usuario no está
autorizado a cambiar las propiedades, y una condición de error se devuelve a
la que llama.
15
La Entidad de clase
paquete de registro;
clase de entidad
{
público int ID;
Cadena apodo privado;
Cadena URL privado;
private int flags = 0;
privado String [] public_info;
privado a largo registration_time = 0;
privado a largo last_update_time = 0;
boolean privado persistente;
boolean is_avatar privado;
Cadena propietario privado;
Cadena CNAME privado;
CNAME = cn;
propietario = ident_name;
= alias CNAME;
ID = new_d;
public_info = new_info;
last_update_time = System.currentTimeMillis ();
}
Desde el código, se puede ver que no hay público para las variables,
excepto la ID. La razón principal de esto es que tengo que seguir la pista
de la última vez que cualquiera de las entidades de las esferas se han
actualizado. Aunque he escrito a la entidad así como el manejo del
servidor, quiero garantizar que la actualización se update_time. Si yo
había dejado las variables públicas, podrían ser fácilmente cambiado sin
informar a la entidad de la nueva actualización del tiempo.
Nota: En un gran número de los métodos se han quedado fuera del código en el
Listado de 19.3. Mucho de la clase consta de varios métodos para ajustar los valores, y
que contiene un número de implementaciones método casi idéntica a la de
set_nickname. Ver el código fuente en el CD de la aplicación completa.
La clase RegistryServer
Aunque sólo hay dos listas que se le mantenga, tengo tres estructuras de datos
(véase el Listado de 19,3). Tablas hash se utilizan para búsquedas rápidas de la
cadena de información basado en el nombre de usuario / contraseña
(user_identities) y el nombre canónico de las entidades (CNAME). Un hash
cadena de búsqueda es mucho más rápido que realizar una completa
búsqueda lineal en la lista y hacer comparaciones de cadenas en cada nombre.
19
paquete de registro;
importación java.io. *;
importación java.util .*;
next_entity_id + +;
return (next_entity_id);
}
if (e.ID == entid)
{
if (! e.get_owner (). iguala (ident))
return false;
e.set_URL (url);
break;
}
}
return true;
}
Cadena new_name)
{
int i;
Entidad e;
if (e.ID == entid)
{
if (! e.get_owner (). iguala (ident))
return false;
e.set_nickname (new_name);
break;
}
}
return true;
}
{
int i;
Entidad e;
if (e.ID == entid)
{
if (! e.get_owner (). es igual a (old_owner))
return false;
e.set_owner (new_owner);
break;
}
}
22
return true;
}
if (e.ID == entid)
{
info = e.get_publicInfo ();
si (info == null)
break;
para (j = 0; j <info.length, j + +)
out.println (info [j]);
break;
}
}
}
público sincronizado vacío printEntityInfo (int entid,
PrintStream a cabo,
Cadena nick)
{
int i;
Entidad e;
if (curr_passwd == null)
{
user_identities.put (uname, passwd);
return true;
}
out.println (e.ID);
}
}
}
También debe notar que cada método se sincroniza, lo que nos impide
conseguir confundirse con múltiples solicitudes de cada método y tener
que tratar con los resultados. No sólo eso, sino que en muchos casos la
estructura de datos individuales está bloqueado con sincronizados. Y lo
que es peor aún, las estructuras de datos como Vector y Hashtable
también hilo de seguridad se realizan al declarar como sus métodos de
acceso sincronizado, también.
Como para el resto del código, debe ser razonablemente explica por sí
mismo el método de nombres. El nombre del método refleja el registro
de comando especial que Bernie ya ha esbozado en el capítulo anterior.
Una vez que haya creado la parte de atrás (de registro de servidor) y
frontal (servidor TCP) las partes del sistema de registro, usted deberá
llenar los espacios en blanco. Esto toma la forma del código en el
analizador RegistryMessage clase (presentado en el Listado 19,4).
25
paquete de registro;
importación java.net .*;
importación java.io. *;
importación java.util .*;
registry.RegistryServer de importación;
DataInputStream en privado;
PrintStream privado a cabo;
this.debug = debug;
_socket = s;
_SERVER = server;
System.out.print ( "Nueva conexión");
System.out.println (_socket.getInetAddress (). GetHostName ());
entity_list = new Vector (10, 5);
alias = "Invitado";
identidad = "Invitado";
}
intentar
{
en = new DataInputStream (_socket.getInputStream ());
a = new PrintStream (_socket.getOutputStream ());
}
catch (IOException e)
{
System.err.println ( "Servidor de Registro:" +
"Error al obtener la E / S arroyos" +
e.getMessage ());
}
while (true)
{
intentar
{
input = in.readLine ();
if ((entrada! = null) & & (! processCommand (entrada)))
break;
}
catch (IOException e)
{
27
intentar
{
in.close ();
out.close ();
_socket.close ();
}
catch (IOException e)
{
System.err.println ( "Registro de mensajes de error" +
e.getMessage ());
}
System.out.print ( "cierre del Registro de conexión");
System.out.println (_socket.getInetAddress (). GetHostName ());
}
boolean processCommand privado (Cadena de mando)
lanza IOException
{
StringTokenizer strtok;
Cadena simbólica;
int entid;
int i;
set_ok boolean;
if (debug)
System.out.println ( "comando recibido:" + comando);
intentar
{
token = strtok.nextToken ();
}
de capturas (NoSuchElementException e1)
{
out.println ( "REHUSÓ carácter no válido en el comando");
return true;
}
if (token.equals ( "hola"))
28
out.println ( "BIENVENIDO");
else if (token.equals ( "LISTA"))
{
_server.printList (out);
out.println (".");
}
else if (token.equals ( "getinfo"))
{
intentar
{
entid = Integer.parseInt (strtok.nextToken ());
}
de capturas (NoSuchElementException e1)
}
out.println ( "comando REHUSÓ incompletos");
return true;
}
de capturas (NumberFormatException e2)
}
out.println ( "ID REHUSÓ entidad formato incorrecto");
return true;
}
{
intentar
{
entid = Integer.parseInt (strtok.nextToken ());
}
de capturas (NoSuchElementException e1)
{
out.println ( "REHUSÓ dado ninguna entidad ID");
return true;
}
de capturas (NumberFormatException e2)
{
out.println ( "ID REHUSÓ entidad formato incorrecto");
return true;
}
/ / Verificar la propiedad del objeto
if (! _server.isOwner (entid, identidad))
{
out.println ( "REHUSÓ no propietario");
return true;
}
/ / Bien, por lo que es propio, le permite continuar.
out.println ( "OK");
Info_list vector = new Vector (5,2);
Tmp cadena;
/ / Leer primero en todos los datos en un buffer.
while (true)
{
tmp = in.readLine ();
if (tmp.equals ("."))
break;
info_list.addElement (tmp);
}
/ / Ahora lo copia a una cadena matriz.
String [] str_list = new String [info_list.size ()];
for (i = 0; i <info_list.size (); i + +)
str_list [i] = (String) info_list.elementAt (i);
_server.set_info (entid, la identidad, la str_list);
}
else if (token.equals ( "Adiós"))
{
/ / En primer lugar, la limpieza de todas las entidades que
pertenecen a este
/ / Cliente. Bucle sólo el servidor y dar instrucciones para borrar
/ / Cada uno de ellos.
for (i = 0; i <entity_list.size (); i + +)
30
_server.delete (
(int) ((Integer) entity_list.elementAt (i)). intValue (),
identidad);
entity_list = null;
return false;
}
else if (token.equals ( "ALLOC"))
{
String name;
intentar
{
name = strtok.nextToken ();
}
de capturas (NoSuchElementException e1)
{
out.println ( "CNAME REHUSÓ entidad no se");
return true;
}
if (identity.equals ( "usuario"))
{
out.println ( "No REHUSÓ ident set");
return true;
}
/ / Ahora que hemos pasado, podemos lograr algo asignar
entid = _server.allocate (nombre, identidad);
switch (entid)
{
caso -1:
out.println ( "Entidad REHUSÓ CNAME en uso");
break;
caso -2:
out.println ( "REHUSÓ usuario no identificados");
break;
por defecto:
out.println (entid);
entity_list.addElement (nuevo Integer (entid));
}
}
else if (token.equals ( "URL"))
{
Url cadena;
intentar
{
entid = Integer.parseInt (strtok.nextToken ());
url = strtok.nextToken ();
}
31
return true;
}
de capturas (NumberFormatException e2)
{
out.println ( "ID REHUSÓ entidad formato incorrecto");
return true;
}
if (_server.delete (entid, identidad))
out.println ( "OK");
algo más
out.println ( "REHUSÓ razón no disponible");
}
else if (token.equals ( "persistentes"))
{
Cadena de persistir;
intentar
{
entid = Integer.parseInt (strtok.nextToken ());
persisten strtok.nextToken = ();
}
de capturas (NoSuchElementException e1)
{
out.println ( "comando REHUSÓ incompletos");
return true;
}
de capturas (NumberFormatException e2)
{
if (persist.equals ( "true"))
set_ok = _server.set_persistent (entid,
identidad,
true);
else if (persist.equals ( "false"))
set_ok = _server.set_persistent (entid,
identidad,
false);
else / / Bares
{
out.println ( "REHUSÓ formato incorrecto");
return true;
}
33
if (set_ok)
out.println ( "OK");
algo más
out.println ( "REHUSÓ razón no disponible");
}
else if (token.equals ( "Avatar"))
{
Av cadena;
intentar
{
entid = Integer.parseInt (strtok.nextToken ());
av = strtok.nextToken ();
}
de capturas (NoSuchElementException e1)
{
out.println ( "comando REHUSÓ incompletos");
return true;
}
de capturas (NumberFormatException e2)
{
out.println ( "ID REHUSÓ entidad formato incorrecto");
return true;
}
if (av.equals ( "true"))
set_ok = _server.set_avatar (entid, la identidad, la verdad);
else if (av.equals ( "false"))
set_ok = _server.set_avatar (entid, la identidad, false);
else / / Bares
{
out.println ( "REHUSÓ formato incorrecto");
return true;
}
if (set_ok)
out.println ( "OK");
algo más
out.println ( "REHUSÓ razón no disponible");
}
else if (token.equals ( "IDENT"))
{
Cadena de pasar;
Cadena = tmp_id identidad;
intentar
{
34
intentar
{
tiempo = Integer.parseInt (strtok.nextToken ());
}
de capturas (NoSuchElementException e1)
{
out.println ( "comando REHUSÓ requiere un" +
"tiempo que se especifica");
return true;
}
de capturas (NumberFormatException e2)
{
out.println ( "REHUSÓ incorrecta formato de hora");
return true;
}
_server.listNew (tiempo, a);
out.println (".");
}
else / / la captura todas las condiciones. Si envía un comando no
válido.
out.println ( "OK");
return true;
}
}
Procesamiento de un comando
El Usuario de Clase
paquete de filtro;
público usuario (actualización int, float ac, horz int, int [] reg, int [] prt)
= update_port actualización;
agudeza = ac;
horz = horizonte;
regiones = reg;
puertos = prt;
last_update = System.currentTimeMillis ();
posición = null;
}
}
Una diferencia que usted puede ser que ya han notado es que el tiempo
de actualización se establece por separado. Cuando un filtro nuevo
comando es recibido, tendremos que configurar muchas de las
propiedades a la vez. Es más fácil y más rápido para configurar todos a
la vez en el servidor, en lugar de gastar el tiempo en la función llamada.
Por desgracia, que también requiere que el servidor se encargará de la
actualización de la last_update campo.
39
La clase UserServer
UDP e Hilos
Gestión de Recursos
Envío de actualizaciones
user.last_seq_num = seq_num;
user.entid = src_entid;
user.last_update = System.currentTimeMillis ();
= user.current_region región;
user.acuity = agudeza;
user.position = posición;
= user.rotation rotación;
Una vez hecho esto, tenemos que crear un mensaje para enviar a los
clientes. Este es un enfoque en dos etapas. En primer lugar, crear un
ByteArrayOutputStream para escribir los datos a:
out.writeInt (src_entid);
/ / La posición
out.writeFloat (posición [0]);
out.writeFloat (posición [1]);
out.writeFloat (posición [2]);
/ / La posición
out.writeFloat (rotación [0]);
out.writeFloat (rotación [1]);
out.writeFloat (rotación [2]);
out.writeFloat (rotación [3]);
intentar
{
socket = DatagramSocket nuevo ();
}
catch (IOException e)
{
/ / ARGH, Barf!
System.out.println ( "UserServer: incapaz de" +
"saliente actualización crear socket");
retorno;
}
Filtrado de datos
sincronizadas (user_list)
{
for (i = 0; i <user_list.size (); i + +)
{
user = (Usuario) user_list.elementAt (i);
intentar
{
socket.send (paquete);
}
catch (IOException e)
{
System.out.println ( "usuario del servidor:" +
"error al enviar paquetes de actualización");
}
}
}
para (j = 0; j <user.regions.size, j + +)
if (user.regions [j] == región)
{
encontrado = true;
break;
}
if (! encontrados)
continuar;
DatagramPacket paquete;
DatagramSocket zócalo;
Usuario usuario;
Usuario me = (Usuario) address_list.get (dirección);
intentar
{
socket = DatagramSocket nuevo ();
}
catch (IOException e)
{
/ / ARGH, Barf!
System.out.println ( "UserServer:" +
"no se puede crear zócalo saliente de refresco");
retorno;
}
intentar
{
/ / Escribir los paquetes de datos para este mensaje
}
catch (IOException e)
47
{
System.out.println ( "usuario del servidor: error de actualización de
la escritura");
retorno;
}
intentar
{
socket.send (paquete);
}
catch (IOException e)
{
System.out.println ( "usuario del servidor:" +
"error al enviar paquetes de actualización");
}
}
}
de recuperar el mensaje.
for (i = 0; i <region_count; i + +)
{
region_list [i] = data.readUnsignedShort ();
}
El servidor de chat
El servidor de actualizaciones
Una vez que todas las piezas se han presentado hasta la fecha, es
necesario convertirlos en algo útil. Muserver la clase se encarga de ello.
Queremos que el servidor se ejecute como una aplicación
independiente, lo que implica que tenemos que poner en un método
principal en alguna parte.
Arrancar el servidor
if (args.length> 0)
{
for (i = 0; i <args.length; i + +)
{
if (args [i]. charAt (0) == '-')
{
if (args [i]. iguales ( "-d"))
_debug = true;
algo más
50
{
System.out.println ( "Uso: muserver [-d] [puerto]");
System.exit (1);
}
}
algo más
{
intentar
{
port = Integer.parseInt (args [i]);
}
de capturas (NumberFormatException e)
{
System.out.println ( "número de puerto no válido");
System.out.println ( "uso de puerto por defecto 3000");
/ / Ajuste de la paranoia
port = 3000;
}
break;
}
}
}
muserver nuevo (puerto, _debug);
Después de partir
Volver al principio de este capítulo he dicho que quiero a este servidor ser tan
robusto como sea posible. Esto incluye la recuperación de circunstancias no
habituales. Una de las mejores maneras en que podemos lograr esta capacidad
es comprobar continuamente sobre el estado de cada uno de los servidores de
un tercero.
Esta verificación de terceros puede ser alcanzado por nuestra clase y muserver.
Sin embargo, para ello, tenemos que tener también en funcionamiento todo el
tiempo. No hay problema: Vamos a hacer en otro hilo también.
if (! chat.isAlive ())
{
System.err.println ( "Reiniciar servidor de chat");
chat = new Chat (base_port 4, chatThreadGroup, userver);
chat.start ();
}
if (! rserver.isAlive ())
{
System.err.println ( "Registro del servidor ha muerto, de salir.");
System.exit (2);
}
que podemos hacer para hacerlo funcionar mejor. El primer lugar que hay que
ver es cuando nosotros estamos usando el Java incorporado en los tipos de
almacenamiento-es decir, Vector y Hashtable.
Manipuladores de datos personalizadas
Si sólo fue con este esquema básico, que muy pronto en problemas. ¿Qué pasa
si tenemos dos peticiones de escritura en cola, y una serie de solicitudes a que
se lea bien? Un escritor se permite el acceso, y luego viene otra petición en
escribir, dejando dos todavía en la cola. ¿Quién consigue el acceso al escribir la
primera libera su bloqueo?
Prevenir bloqueos de recursos
En este punto, tenemos que tomar una decisión acerca de los derechos de
acceso. Decidimos cuántos consecutivos escribe se permite antes de leer las
peticiones, es un honor. Una vez que el número de escrituras pasa por encima
de nuestro límite preestablecido, los lectores se les permite volver a los datos.
Una vez que el lector tiene acceso, se puede entonces definir el número de
escritores de vuelta a cero o decremento por 1 para cada lectura permite pulg
El uso de este guardia en el registro del servidor sería ideal. Hay muchas
lecturas, pero normalmente no son muchos los escribe. Cada uno de los
métodos puede ser sincronizado para evitar la entrada de los problemas, pero
después de que podemos tirar la intrínsecamente lento y tablas hash vectores,
reemplazarlos con nuestras propias implementaciones, y combinarlos con un
SWMRG para actuar como mecanismo de seguridad.
case 'G':
if (token.charAt (1) == 'O')
/ / Adiós
else if (token.length == 9)
/ / GetEntity
else if (token.length == 13)
/ / GetEntityInfo
break;
/ / Etc etc
por defecto:
out.println ( "OK");
}
Hay algunos problemas que surgen con este diseño. No suele asegurar
55
java muserver
para iniciar el servidor. Esto lleva a los parámetros por defecto del
servidor en ejecución. La base se fija en el puerto 3000, y la información
de depuración está desactivada.
Ahora ya está listo para las conexiones de los clientes. No hay nada más
que lo que tiene que hacer.
56
Resumen