Escrito antes:
Ayer grabé el diseño inicial de un programa de chat de socket que me tomé el tiempo para escribir en mi blog. Era el diseño general de este programa. Para completar, hoy registraré el diseño del lado del servidor en detalle. La página de inicio publicará el diagrama de diseño general del programa de chat de socket, como se muestra a continuación:
Descripción de la función:
El servidor tiene dos operaciones principales: una es bloquear el zócalo del cliente receptor y realizar el procesamiento de respuesta, y el otro es detectar el latido del cliente. Si el cliente no envía un latido por un período de tiempo, elimine el cliente, cree el SerververSocket y luego inicie dos grupos de subprocesos para manejar estas dos cosas (NewFixedThreadPool, NewsCheduledThreadPool). Las clases de procesamiento correspondientes son SocketDispatcher y SocketsChedule. El SocketDispatcher se distribuye a diferentes asquetadores de acuerdo con diferentes solicitudes de socket. Socketwrapper agrega un envoltorio de shell al socket y registra el último tiempo de interacción del socket con el scocketholder almacena la colección de socket que actualmente interactúa con el servidor.
Implementación específica:
[Server.java]
El servidor es la entrada al servidor. Serversocket se inicia mediante el método Start () del servidor, y luego bloquea la solicitud del cliente receptor y se entrega al SocketDispatcher para su distribución. El SocketDispatcher se inicia con un grupo de hilo de NewFixedThread. Cuando el número de conexiones excede los datos máximos, la cola procesará. ScheduleAtFixedRate se usa para iniciar el bucle de sincronización de SocketsChedule para escuchar el paquete Heartbeat del cliente. Ambos tipos implementan la interfaz ejecutable. El siguiente es el código para el servidor:
paquete yaolin.chat.server; import java.io.ioException; import java.net.serversocket; import java.util.date; import java.util.concurrent.executorservice; import java.util.concurrent.executors; import java.util.concurrent.scheduledexecutorservice; import; java.util.concurrent.timeunit; import yaolin.chat.common.constantValue; import yaolin.chat.util.loggerutil;/*** servidor* @author yaolin*/servidor de clase pública {servidor de servidor final privado; Grupo de servicio de ejecutores finales privados; Public Server () lanza ioexception {servidor = new ServerSocket (constantValue.server_port); Pool = Ejecutors.NewFixedThreadPool (constantValue.max_pool_size); } public void start () {try {ProgramedExecutorService programar = Ejecutors.NeWSCheduledThreadPool (1); // Mira perro. ¿¿Excepción?? shiteb.scheduleatFixedRate (nuevo SocketSchedule (), 10, ConstantValue.time_out, TimeUnit.seconds); while (true) {Pool.execute (nuevo SocketDisPatcher (server.accept ())); LoggerUtil.info ("Aceptar un cliente en" + nueva fecha ()); }} Catch (IOException e) {Pool.shutdown (); }} public static void main (string [] args) {try {new Server (). Start (); } Catch (ioException e) {loggerUtil.error ("Servidor Start falló! ->" + e.getMessage (), e); }}}[Socketdispatcher.java]
El servidor es solo la entrada al servidor y el centro de comando. SocketDispatcher es el centro de comando del servidor. Distribuye diferentes solicitudes de tipos de mensajes del cliente, lo que permite que los diferentes calcetines procesen las solicitudes de mensajes correspondientes. Aquí, la interacción de mensajes entre el servidor y el cliente usa datos JSON. Todas las clases de mensajes heredan BaseMessage, por lo que los datos recibidos se convierten en BaseMessage, y luego se juzga el tipo. (El módulo de tipo de datos pertenece al módulo común). Debe mencionarse aquí que cuando el tipo de mensaje es un tipo de archivo, dormirá para configurar el intervalo de ejecución, de modo que FileHandler pueda tener tiempo para leer y reenviar la secuencia de archivo al cliente especificado, sin ingresar inmediatamente el siguiente bucle para juzgar el tipo de mensaje (el diseño puede ser un poco problemático aquí, pero hacer esto por el tiempo). El siguiente es el código de SocketDispatcher:
/** * SocketDispatcher * * @author Yaolin */public class SocketDispatcher implementa Runnable {SCOCKE SOCKECTING FINAL PRIVADO; public SocketDispatcher (socket de socket) {this.socket = socket; } @Override public void run () {if (socket! = Null) {while (! Socket.isClosed ()) {try {inputStream is = Socket.getInputStream (); Línea de cadena = nulo; Stringbuffer sb = null; if (is.available ()> 0) {bufferedReader bufr = new BufferedReader (new InputStreamReader (is)); sb = new StringBuffer (); while (is.available ()> 0 && (line = bufr.readline ())! = null) {sb.append (línea); } Loggerutil.trach ("recibir [" + sb.toString () + "] en" + nueva fecha ()); Mensaje BaseMessage = JSON.PARSEOBJECT (SB.ToString (), BaseMessage.Class); switch (message.gettype ()) {case messageType.alive: handlerFactory.gethandler (messageType.alive) .handle (socket, sb.ToString ()); romper; Case MessageType.Chat: HandlerFactory.Gethandler (MessageType.Chat) .Handle (Socket, SB.ToString ()); romper; Case MessageType.file: HandlerFactory.gethandler (MessageType.file) .Handle (Socket, SB.ToString ()); romper; Case MessageType.file: HandlerFactory.gethandler (MessageType.file) .Handle (Socket, SB.ToString ()); Loggerutil.trach ("Sever: pausa para recibir el archivo"); Thread.sleep (constantValue.message_period); romper; Case MessageType.login: handlerFactory.gethandler (MessageType.login) .Handle (Socket, SB.ToString ()); romper; Case MessageType.logout: Break; Case MessageType.Register: HandlerFactory.Gethandler (MessageType.Register) .Handle (Socket, SB.ToString ()); romper; }} else {thread.sleep (constantValue.message_period); }} Catch (Exception e) {// Catch All Handler Exception loggerUtil.error ("¡Error de SocketDispatcher!" + E.getMessage (), e); }}}}}}[Socketschedule.java]
Otra clase (componente) que está directamente relacionada con el servidor es SocketsChedule. SocketsChedule es el principal responsable de detectar si el último tiempo de interacción entre el cliente y el servidor excede el tiempo máximo permitido en la configuración del sistema. Si excede, el socket del cliente se eliminará del servidor, de lo contrario, se actualizará el último tiempo de interacción entre el cliente y el servidor. Las siguientes son las implementaciones específicas:
/** * Eliminar el socket de Socketholder si lastaliveTime> time_out * @author yaolin * */public class SocketsChedule implementa runnable {@Override public void run () {for (string key: socketholder.keyset ()) {socketwrapper wrapper = socketholder.get (key); if (wrapper! = null && wrapper.getLastaliveTime ()! = null) {if (((new date (). gettime ()) - wrapper.getLastaliveTime (). gettime ()) / 1000)> constantValue.time_out) {// elimina el paso de tiempo de tiempo de tiempo de tiempo. }}}}}}[Socketholder.java, Socketwrapper.java]
En el código anterior, podemos ver que SocketsChedule#run () es solo un simple juicio de tiempo. Lo que es realmente significativo es Socketholder y Socketwrapper. Socketwrapper agrega un envoltorio de carcasa al enchufe. Socketholder almacena a todos los clientes que interactúan con el servidor durante el tiempo válido actual. Socketholder es identificado de forma única por el cliente (nombre de usuario aquí). Como clave, el socket donde se encuentra el cliente se almacena como un par de valor de valor clave. La lógica de procesamiento de Socketholder#FlushClientStatus () se utiliza para notificar a otros clientes el estado en línea/fuera de línea del cliente actual. La implementación específica de estas dos clases se proporciona a continuación:
/** * Envuello Socket, SocketsChedule Eliminar el socket si Lastalivetime> Time_out * @author Yaolin * */public class Socketwrapper {privado socket socket; Fecha privada Lastalivetime; // Constructor completo Public SocketWrapper (Socket Socket, fecha LastAliveTime) {this.socket = socket; this.lastaliveTime = LITALIVETIME; } public Socket getSocket () {return Socket; } public void setSocket (socket socket) {this.socket = socket; } fecha pública getLastaliveTime () {return LastaliveTime; } public void setLastaliveTime (date lastaliveTime) {this.lastaliveTime = lastaliveTime; }} /** * Socketholder * @author Yaolin */public class Socketholder {private static concurrentMap <String, Socketwrapper> listSocketWrap = new ConcurrentHashMap <String, Socketwrapper> (); Public static set <String> keySet () {return listSocketwrap.KeySet (); } public static socketwraperper get (tecla de cadena) {return listSocketwrap.get (clave); } public static void put (clave de cadena, valor de enchufe) {listsocketwrap.put (clave, valor); flushClientStatus (clave, verdadero); } public static socketwrapers remove (tecla de cadena) {flushClientStatus (clave, falso); return listSocketwrap.remove (clave); } public static void clear () {listsocketwrap.clear (); } /** * <pre> content: {username: "", flag: false} </ pre> * @param flag VERDAD */ private static void flushClientStatus (tecla de cadena, bandera booleana) {clientNotifyDTO dto = new ClientNotifyDTO (bandera, clave); ReturnMessage rm = new ReturnMessage (). SetKey (Key.Notify) .SetSuccess (true) .setContent (dto); rm.setFrom (constantValue.server_name); for (string toKey: listSocketwrap.KeySet ()) {if (! tokey.equals (key)) {// no se envía a self RM.Setto (toKey); Socketwrapper wrap = listsocketwrap.get (tokey); if (wrap! = null) {sendHelper.send (wrap.getSocket (), rm); }}}}}}[Sockethandler.java, HandlerFactory.java, OtherHandlerImpl.java]
SocketDispatcher permite que los diferentes calcetines manejen las solicitudes de mensajes correspondientes. El diseño de Sockethandler es en realidad un conjunto simple de componentes de fábrica (el retorno de SendHelper transmite temporalmente, pero no se usa por el momento. Se ha sido @Depreced y todavía se da aquí). El diagrama de clase completo es el siguiente:
El código para esta sección se proporciona a continuación. Para reducir el espacio, se recopila todo el código implementado por el controlador.
/** * SOCKETHANDLER * @Author Yaolin */Public Interface SocketHandler {/** * manejar el socket de cliente */Mango de objeto público (cliente de socket, datos de objetos);} /** * sockethandlerFactory * @author yaolin */public class handlerFactory {// no puede crear instancia privada handlerFactory () {} public static sockethandler gethandler (int type) {switch (type) {case messageType.alive: // generalmente usa return New ViveHandler (); case MessageType.chat: return New Chathandler (); Case MessageType.login: return New LoginHandler (); // Case MessageType.return: // return New ReturnHandler (); Case MessageType.logout: return New Logouthandler (); caso MessageType.Register: return New RegisterHandler (); case MessageType.file: devuelve nuevo fileHandler (); } return null; // NullPointException}} /** * Alivesockethandler * @author Yaolin */public class LiveHandler implementa SocketHandler {/** * @return null */@Override public Object Handle (Socket Client, Object data) {if (data! = Null) {baseMessage Message = json.parseObject (data.toString (), basemessage.class); if (StringUtil.ISNotEmpty (Message.getFrom ())) {SocketWrapper WRAPPER = SocketHolder.get (Message.getFrom ()); if (wrapper! = null) {wrapper.setLastaliveTime (nueva fecha ()); // Mantenga el socket ... socketholder.put (message.getFrom (), wrapper); }}} return null; }} /** * Chathandler * * @author Yaolin */public class Chathandler implementa Sockethandler {@Override public Object Handle (Socket Client, Object Data) {if (data! = Null) {chatMessage Message = json.parseObject (data.ToString (), chatmessage.classs); if (StringUtil.IsNotEmpty (mensaje.getFrom ()) && StringUtil.ISNotEmpty (mensaje.getto ())) {// exist y enviar if (socketholder.keySet (). contiene (message.getFrom ())) {string propietario = message.getFrom (); Message.setOwner (propietario); // El propietario se mostrará if (constantValue.to_all.equals (message.getto ())) {// One-to-all // to_all se seleccionará; Message.setFrom (constantValue.to_all); for (tecla de cadena: socketholder.keyset ()) {// también envía a auto -socketwrapper wrapper = socketholder.get (key); if (wrapper! = null) {sendHelper.send (wrapper.getSocket (), mensaje); }}} else {// One a-One Socketwrapper wrapper = socketholder.get (mensaje.getto ()); if (wrapper! = null) {// propietario = de sendHelper.send (wrapper.getSocket (), mensaje); // también se seleccionará a self // a la pestaña; Message.setFrom (Message.getto ()). Setto (propietario); SendHelper.send (cliente, mensaje); }}}}} return null; }} Public Class FileHandler implementa SocketHandler {@Override public Public Object Handle (Socket Client, Object Data) {if (Client! = NULL) {FileMessage Message = JSON.PARSEOBJECT (data.ToString (), fileMessage.class); if (stringUtil.isNotEmpty (mensaje.getFrom ()) && stringUtil.isNotEmpty (mensaje.getto ())) {// exist y enviar if (socketholder.keySet (). contiene (mensaje.getFrom ())) {if (! ConstantValue.to_all.equals (message.getto ())) Socketholder.get (message.getto ()); if (wrapper! = null) {sendHelper.send (wrapper.getSocket (), mensaje); Pruebe {if (Client! = NULL && wrapper.getSocket ()! = NULL && Message.getSize ()> 0) {inputStream is = Client.getInputStream (); OutputStream OS = wrapper.getSocket (). GetOutputStream (); int total = 0; while (! Client.isClosed () &&! wrapper.getSocket (). isClosed ()) {if (is.available ()> 0) {byte [] buff = new byte [constantValue.buff_size]; int len = -1; while (is.available ()> 0 && (len = is.read (buff))! = -1) {os.write (buff, 0, len); total += len; Loggerutil.debug ("enviar buff [" + len + "]"); } os.flush (); if (total> = message.getSize ()) {loggerUtil.info ("Enviar buff [ok]"); romper; }}} // Después de enviar archivo // enviar corrientes de retorno correctamente = new ReturnMessage (). SetKey (Key.tip) .SetSuccess (true) .setContent (i18n.info_file_send_successfy); result.setFrom (Message.getto ()). SetTo (Message.getFrom ()) .setowner (constantValue.server_name); SendHelper.send (cliente, resultado); // Recibe con éxito resultado.setContent (i18n.info_file_receive_successfy) .setFrom (message.getFrom ()) .setto (message.getto ()); SendHelper.send (wrapper.getSocket (), resultado); }} catch (Exception e) {loggerUtil.error ("¡Manejar el archivo fallido!" + E.getMessage (), e); }}}}}}} return null; }} /** * LoginHandler * * @author Yaolin * */public class LoginHandler implementa SocketHandler {private USRService USRService = new USRService (); @Override Public Object Handle (Socket Client, Object Data) {returnMessage result = new returnMessage (); resultado.setsuccess (falso); if (data! = null) {LoginMessage Message = json.parseObject (data.ToString (), LoginMessage.Class); if (StringUtil.ISNotEmpty (Message.getUsername ()) && StringUtil.ISNotEmpty (message.getPassword ())) {if (usrservice.login (message.getUsername (), message.getPassword ())! = NULL) {result.setSuccess (true); } else {result.setMessage (i18n.info_login_error_data); } result.setFrom (constantValue.server_name) .setto (mensaje.getusername ()); } else {result.setMessage (i18n.info_login_empty_data); } // después de inicio de sesión result.setKey (key.login); if (result.issuccess ()) {// Hold Socket socketholder.put (result.getto (), new SocketWrapper (Cliente, New Date ())); } SendHelper.send (cliente, resultado); if (result.issuccess ()) {// Enviar la lista de usuarios ClientListUserDTO dto = new ClientListUserDTO (); dto.setListuser (Socketholder.KeySet ()); result.setContent (dto) .setkey (key.listuser); SendHelper.send (cliente, resultado); }} return null; }} Public Class Logouthandler implementa SocketHandler {@Override public Public Object Handle (Socket Client, Object Data) {if (data! = NULL) {logroutMessage Message = json.parseObject (data.ToString (), logroutMessage.class); if (mensaje! = null && stringUtil.isnotempty (mensaje.getFrom ())) {Socketwrapper wrapper = socketholder.get (message.getFrom ()); Socket socket = wrapper.getSocket (); if (socket! = null) {try {socket.close (); socket = nulo; } capt (excepción ignore) {}} socketholder.remove (mensaje.getFrom ()); }} return null; }} Public Class RegisterHandler implementa Sockethandler {private USRService USRService = new USRService (); @Override Public Object Handle (Socket Client, Object Data) {returnMessage result = new returnMessage (); resultado.setsuccess (falso) .setFrom (constantValue.server_name); if (data! = null) {registroMessage Message = json.parseObject (data.ToString (), registreMessage.class); if (StringUtil.ISNotEmpty (Message.getUsername ()) && StringUtil.ISNotEmpty (mensaje.getPassword ())) {if (usrservice.register (message.getUsername (), mensaje.getPassword ())! = null) {resultado.setSuccess (true) .setContent (i18n.info_regerTernernerTernerTernerTernerTernerTernernerio); } else {result.setMessage (i18n.info_register_client_exist); }} else {result.setMessage (i18n.info_register_empty_data); } if (StringUtil.ISNotEmpty (mensaje.getUsername ())) {result.setto (message.getUsername ()); } // después del registro resultado.setkey (key.register); SendHelper.send (cliente, resultado); } return null; }} /** * use sendHelper para enviar returnMessage, * @see yaolin.chat.server.socketdispatcher#run () * @author yaolin */ @deprecated public class returnHandler implementa sockethandler {/** * @param data returnMessage */@Override public object Handle (socket sockine cliente, objetos de objetos) {if ((data (neblina) (ReturnMessage) datos; if (StringUtil.ISNotEmpty (mensaje.getFrom ()) && StringUtil.ISNotEmpty (mensaje.getto ())) {Socketwrapper wrap = socketholder.get (message.getto ()); if (wrap! = null) {sendHelper.send (wrap.getSocket (), mensaje); }}} return null; }}Negocio de usuarios:
Además de los sockets, el servidor también tiene un pequeño negocio específico, es decir, registro del usuario, inicio de sesión, etc. Aquí simplemente enumeramos las dos clases de USR y USRService. Estas empresas no se han implementado por el momento. No tengo la intención de introducir un marco de ORM en este programa, por lo que escribí un conjunto de dbutil (para mejorar) y lo publiqué aquí.
Solo se realiza una verificación simple aquí, y no se persiste para almacenarla en el DB. Aquí están USR y USRService:
Clase pública USR {ID de largo privado; nombre de usuario de cadena privada; contraseña de cadena privada; public Long getId () {return id; } public void setid (ID long) {this.id = id; } public String getUsername () {return UserName; } public void setUsername (String UserName) {this.Username = username; } public String getPassword () {return Password; } public void setPassword (String Password) {this.password = contraseña; }} /** * // tODO * @see yaolin.chat.server.usr.Repository.usrRepository * @author yaolin * */public class usrService {// tODO db private static map <string, usr> db = new hachmap <String, usr> (); registro público USR (String UserName, String Password) {if (StringUtil.isEmpty (UserName) || StringUtil.isEmpty (Password)) {return null; } if (db.containskey (nombre de usuario)) {return null; // existir; } Usr usr = new usr (); USR.SetUsername (nombre de usuario); usr.setPassword (md5util.getmd5code (contraseña)); db.put (nombre de usuario, USR); regresar USR; } public USR Login (String UserName, String Password) {if (StringUtil.IsEmpty (UserName) || StringUtil.IsEmpty (Password)) {return null; } if (db.containskey (nombre de usuario)) {usr usr = db.get (nombre de usuario); if (md5util.getMd5code (contraseña) .equals (usr.getPassword ())) {return usr; }} return null; }} Aquí está la herramienta dButil:
/*** dButils // ¡TODO necesita ser ajustado y optimizado! * @author Yaolin */public class dButil {// Haga la conexión de la conexión de la conexión estática repetida <Enection> Cache = new LinkedList <Enection> (); URL de cadena estática privada; controlador de cadena estática privada; Usuario privado de cadena estática; contraseña de cadena estática privada; depuración booleana estática privada; static {inputStream is = dbutil.class.getResourceasstream ("/db.properties"); intente {propiedades p = new Properties (); p.load (is); url = p.getProperty ("url"); controlador = p.getProperty ("controlador"); usuario = P.getProperty ("usuario"); contraseña = p.getProperty ("contraseña"); // solo para debug intento {debug = boolean.valueOf (p.getProperty ("debug")); } catch (excepción ignore) {debug = false; }} Catch (Exception e) {Throw New RuntimeException (e); } finalmente {if (is! = null) {try {is.close (); es = nulo; } capt (excepción ignore) {}}}} public sincronizado conexión estática getConnection () {if (cache.isEmpty ()) {cache.add (makeconnection ()); } Conexión conn = null; int i = 0; intente {do {conn = cache.remove (i); } while (conn! = null && conn.isClosed () && i <cache.size ()); } capt (excepción ignore) {} try {if (conn == null || conn.isClosed ()) {cache.add (makeconnection ()); conn = cache.remove (0); } return Conn; } catch (Exception e) {Throw New RuntimeException (e); }} public sincronizado static void Close (conexión de conexión) {try {if (conexión! = NULL &&! Connection.isClosed ()) {if (Debug) Debug ("Libere Connection!"); caché.add (conexión); }} Catch (SQLException ignore) {}} consulta de objeto estático público (string sql, resultsetMapper mapper, objeto ... args) {if (debug) debug (sql); Conexión conn = getConnection (); Preparado PS = NULL; ResultSet rs = null; Resultado del objeto = nulo; intente {ps = conn.preparestatement (sql); int i = 1; for (objeto objeto: args) {ps.setObject (i ++, objeto); } rs = ps.ExecuteQuery (); resultado = mapper.mapper (RS); } catch (Exception e) {Throw New RuntimeException (e); } finalmente {try {if (rs! = null) {rs.close (); rs = nulo; } if (ps! = null) {ps.close (); ps = nulo; }} catch (excepción ignore) {}} Close (Conn); resultado de retorno; } public static int modify (string sql, object ... args) {if (debug) debug (sql); Conexión conn = getConnection (); Preparado PS = NULL; int fila = 0; intente {ps = conn.preparestatement (sql); int i = 1; for (objeto objeto: args) {ps.setObject (i ++, objeto); } fila = ps.ExecuteUpdate (); } catch (Exception e) {Throw New RuntimeException (e); } finalmente {try {if (ps! = null) {ps.close (); ps = nulo; }} catch (excepción ignore) {}} Close (Conn); Fila de retorno; } public static int [] Batch (List <String> SQLS) {if (Debug) Debug (SQLS.ToString ()); Conexión conn = getConnection (); Instrucción stmt = null; int [] fila; intente {stmt = conn.createStatement (); for (string sql: sqls) {stmt.addbatch (sql); } fila = stmt.executeBatch (); } catch (Exception e) {Throw New RuntimeException (e); } finalmente {try {if (stmt! = null) {stmt.close (); stmt = nulo; }} catch (excepción ignore) {}} Close (Conn); Fila de retorno; } public static int [] Batch (String SQL, PrepareStatementSetter setter) {if (debug) debug (sql); Conexión conn = getConnection (); Preparado PS = NULL; int [] fila; intente {ps = conn.preparestatement (sql); setter.setter (ps); fila = ps.executeBatch (); } catch (Exception e) {Throw New RuntimeException (e); } finalmente {try {if (ps! = null) {ps.close (); ps = nulo; }} catch (excepción ignore) {}} Close (Conn); Fila de retorno; } Conexión estática privada makeconnection () {try {class.forname (controlador) .newinstance (); Conexión conn = drivermanager.getConnection (url, usuario, contraseña); if (debug) debug ("Crear conexión!"); devolver Conn; } catch (Exception e) {Throw New RuntimeException (e); }} depuración void static privada (string sqls) {SimpleDateFormat sdf = new SimpleDateFormat ("yyyyy-mm-dd hh: mm: ss"); System.out.println (sdf.format (new Date ()) + "debug" + thread.currentThread (). GetId () + "--- [" + thread.currentThread (). GetName () + "]" + "Excuto sqls:" + sqls); }} /** * PrepareStatementSetter * @author Yaolin */Public Interface PrepareStatementETter {public void setter (preparado PS);} /** * ResultSetMapper * @Author Yaolin */Public Interface ResultSetMapper {public Object Mapper (ResultSet RS);} Descarga del código fuente: demostración
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.