Escrito antes:
Ontem gravei o design inicial de um programa de bate -papo de soquete, dediquei um tempo para escrever no meu blog. Foi o design geral deste programa. Para completar, hoje gravarei o design do lado do servidor em detalhes. A página inicial publicará o diagrama de design geral do programa de bate -papo de soquete, como mostrado abaixo:
Descrição da função:
O servidor possui duas operações principais: uma é bloquear o soquete do cliente receptor e executar o processamento de respostas, e o outro é detectar o batimento cardíaco do cliente. Se o cliente não enviar um batimento cardíaco por um período de tempo, remova o cliente, crie o ServerSocket e inicie dois pools de threads para lidar com essas duas coisas (newFixedThreadpool, NewsCheduledthreadpool). As classes de processamento correspondentes são SocketDispatcher e SocketSchedule. O SocketDispatcher é distribuído a diferentes suportes de acordo com diferentes solicitações de soquete. O SocketWrapper adiciona um invólucro de shell ao soquete e registra o tempo de interação mais recente do soquete com o Socketholder armazena a coleção de soquete que atualmente interage com o servidor.
Implementação específica:
[Server.java]
O servidor é a entrada do servidor. O ServerSocket é iniciado pelo método START () do servidor e, em seguida, bloqueia a solicitação do cliente receptor e entregue ao SocketDispatcher para distribuição. O SocketDispatcher é iniciado por um pool de threads do tipo NewFixedThread. Quando o número de conexões exceder os dados máximos, ele será processado pela fila. ScheduleAtFixedrate é usado para iniciar o laço de tempo do soqueschedule para ouvir o pacote de batimentos cardíacos do cliente. Ambos os tipos implementam a interface executável. A seguir, o código do servidor:
pacote yaolin.chat.server; importar java.io.ioException; importar java.net.serversocket; importar java.util.date; importar java.util.concurrent.executorService; import java.util.Concurrent.Executores; importação java.util.Concurntice; java.util.concurrent.timeunit; importar yaolin.chat.common.constantValue; importar yaolin.chat.util.loggerutil;/*** servidor* @author yaolin*/public class Server {private Final ServerSocket Server; Pool de ExecutoraService Final Service privado; public server () lança ioexception {server = new ServerSocket (constantValue.server_port); pool = executores.newfixedThreadpool (constantValue.max_pool_size); } public void start () {try {scheduledExecutorService cronograma = executores.newscheduledthreadpool (1); // Assista a cachorro. Exceção?? Schedule.scheduleatfixedrate (new socketschedule (), 10, constantValue.time_out, timeUnit.Seconds); while (true) {pool.execute (new SocketDispatcher (server.accept ())); Loggerutil.info ("Aceite um cliente em" + new Date ()); }} catch (ioexception e) {pool.shutdown (); }} public static void main (string [] args) {try {new server (). start (); } catch (ioexception e) {loggerutil.error ("Start do servidor falhou! ->" + e.getMessage (), e); }}}[SocketDispatcher.java]
O servidor é apenas a entrada do servidor e do centro de comando. SocketDispatcher é o centro de comando do servidor. Ele distribui diferentes solicitações de tipos de mensagens do cliente, permitindo que diferentes sochethandlers processem as solicitações de mensagem correspondentes. Aqui, a interação da mensagem entre o servidor e o cliente usa dados JSON. Todas as classes de mensagens herdam basessessage, para que os dados recebidos sejam convertidos em tipo Basemessage e, em seguida, o tipo é julgado. (O módulo de tipo de dados pertence ao módulo comum). Deve -se mencionar aqui que, quando o tipo de mensagem for um tipo de arquivo, ele dormirá para configurar o intervalo de execução, para que o FileHandler possa ter tempo para ler e reenviar o fluxo de arquivos ao cliente especificado, sem inserir imediatamente o próximo loop para julgar o tipo de mensagem (o projeto pode ser um pouco problemático aqui, mas faça isso para o tempo). A seguir, o código do SocketDispatcher:
/** * SocketDispatcher * * @author yaolin */classe pública soketdispatcher implementa Runnable {Setor de soquete final privado; public SocketDispatcher (soquete) {this.socket = soket; } @Override public void run () {if (soquete! Linha de string = null; Stringbuffer sb = null; if (is.Available ()> 0) {BufferReader bufr = new BufferredReader (novo InputStreamReader (IS)); sb = new StringBuffer (); while (is.Available ()> 0 && (linha = bufr.readline ())! = null) {sb.append (line); } Loggerutil.trach ("Receba [" + sb.toString () + "] em" + new Date ()); BasEmessage Message = json.parseObject (sb.toString (), basemessage.class); switch (message.getType ()) {case messageType.alive: handlerFactory.gethandler (messageType.alive) .Handle (soquete, sb.toString ()); quebrar; Case MessageType.chat: HandlerFactory.gethandler (MessageType.chat) .Handle (Socket, sb.toString ()); quebrar; Case MessageType.File: HandlerFactory.gethandler (MessageType.File) .Handle (Socket, SB.ToString ()); quebrar; Case MessageType.File: HandlerFactory.gethandler (MessageType.File) .Handle (Socket, SB.ToString ()); Loggerutil.Trach ("Sever: Pause para receber o arquivo"); Thread.sleep (constantValue.message_period); quebrar; Case MessageType.login: HandlerFactory.gethandler (MessageType.login) .Handle (Socket, sb.toString ()); quebrar; case messageType.logout: break; case messageType.register: handlerFactory.gethandler (messageType.register) .Handle (soquete, sb.toString ()); quebrar; }} else {thread.sleep (constantValue.message_period); }} catch (Exceção e) {// Pegue todos os manipuladores de exceção loggerutil.error ("Erro do SocketDispatcher!" + E.GetMessage (), e); }}}}}}[SocketSchedule.java]
Outra classe (componente) diretamente relacionada ao servidor é o SocketSchedule. O SocketSchedule é o principal responsável por detectar se o tempo de interação mais recente entre o cliente e o servidor excede o tempo máximo permitido na configuração do sistema. Se exceder, o soquete do cliente será removido do servidor, caso contrário, o tempo de interação mais recente entre o cliente e o servidor será atualizado. A seguir, são apresentadas as implementações específicas:
/** * Remova o soquete do Socketholder Se LastaliveTime> time_out * @author yaolin * */public class SocketsChedule implementa Runnable {@Override public void run () {para (chave de string: socketholder.keyset ()) {SockapPripper = socketholder.get.get.get.get.keyset ()) {SockperWrapper = socketholder.get.get.get.get.KeySet ()) if (wrapper! = null && wrapper.getLasTaliveTime ()! = null) {if (((new date (). gettime ()) - wrapper.getLastalivETime (). gettime ()) / 1000)> constantValue.time_out) {// remova o timeout socketholder.lemove; }}}}}}[Socketholder.java, Socketwrapper.java]
A partir do código acima, podemos ver que o SocketSchedule#run () é apenas um julgamento simples do tempo. O que é realmente significativo é o Socketherder e o Socketwrapper. O SocketWrapper adiciona um invólucro ao soquete. O Socketholder armazena todos os clientes que interagem com o servidor durante o horário válido atual. O Socketholder é identificado exclusivamente pelo cliente (nome de usuário aqui). Como chave, o soquete em que o cliente está localizado é armazenado como um par de valor de valor-chave. A lógica de processamento do Socketholder#FlushClientStatus () é usada para notificar outros clientes sobre o status online/offline do cliente atual. A implementação específica dessas duas classes é dada abaixo:
/** * Soquete de embrulho, SocketSchedule Remova o soquete Se LastaliveTime> time_out * @Author yaolin * */public class SocketWrapper {Setor de soquete privado; data privada lubrificante; // Full Constructor Public SocketWrapper (soquete, data LastalivETime) {this.socket = soquete; this.LASTALIVETime = LastaliveTime; } Public Socket getSocket () {return Socket; } public void setSocket (soquete) {this.socket = soket; } data pública getLastaliveTime () {return lastaliveTime; } public void SetLASTALIVETIME (DATA LUSTALIVETIME) {this.LASTALIVETime = LastaliveTime; }} /** * Socketholder * @Author yaolin */public class socketholder {private static concurrentmap <string, soketwrapper> listsocketwrap = new concurrenthashmap <string, soketwrapper> (); Public Static Set <String> keySet () {return listsocketwrap.keyset (); } public static socketwrapper get (string key) {return listsocketwrap.get (key); } public static void put (tecla String, SocketWrapper Value) {listsocketwrap.put (chave, valor); flushclientstatus (chave, true); } public static -socketwrapper Remover (tecla String) {flushClientStatus (chave, false); retorno listsocketwrap.remove (chave); } public static void clear () {listsocketwrap.clear (); } /** * <pre> content: {nome de usuário: "", sinalizador: false} </pre> * @param sinalizador true: put, false: remover; */ private estático void flushclientStatus (chave da string, sinalizador booleano) {clientNotifyDto dto = new clientNotifyDto (sinalizador, chave); ReturnMessage rm = new ReturnMessage (). SetKey (key.Notify) .SetSuccess (true) .SetContent (DTO); rm.setFrom (constantValue.Server_Name); para (String tokey: listsocketwrap.keyset ()) {if (! Tokey.equals (key)) {// não é enviado para o auto rm.setto (tokey); SocketWrapper WRAP = listsocketwrap.get (tokey); if (wrap! = null) {sendHelper.send (wrap.getSocket (), rm); }}}}}}[Sockethandler.java, HandlerFactory.java, outrosHandlerimpl.java]
O SocketDispatcher permite que diferentes sochathandlers lidem com solicitações de mensagem correspondentes. O design do Sockethandler é na verdade um conjunto simples de componentes de fábrica (o ReturnHandler é temporariamente transmitido pelo SendHelper, mas não é usado por enquanto. Ele foi @Deprecated e ainda é fornecido aqui). O diagrama de aulas completo é o seguinte:
O código para esta seção é fornecido abaixo. Para reduzir o espaço, todo o código implementado pelo manipulador é coletado.
/** * Sockethandler * @Author Yaolin */Public Interface Sockethandler {/** * Lidar com o soquete do cliente */alça de objeto público (cliente de soquete, dados do objeto);} /** * SockethandlerFactory * @Author yaolin */public Class HandlerFactory {// não pode criar instância private HandlerFactory () {} public static sockethandler gethandler (int type) {switch (type) {case messagetype.alive: // geralmente use retorno vivaHandler (); case messageType.chat: retorna new Chathandler (); case messageType.login: retorna new LoginHandler (); // case messageType.return: // return new ReturnHandler (); case messageType.logout: retorna new LogoutHandler (); case messageType.register: retornar new RegisterHandler (); case messageType.File: retorna new FileHandler (); } retornar nulo; // nullpointException}} /** * Alivesockethandler * @Author yaolin */public class AliveHandler implementa Sockethandler {/** * @return null */@Override Public Object Handle (Socket Client, Object Data) {if (data! if (stringUtil.isnotEmpty (message.getFrom ())) {SocketWrapper wrapper = socketholder.get (message.getFrom ()); if (wrapper! = null) {wrapper.setLastaliveTime (new Date ()); // Mantenha o soquete ... Socketholder.put (message.getFrom (), wrapper); }}} retornar nulo; }} /** * 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.class; if (stringutil.isnotEmpty (message.getFrom ()) && stringutil.isnotEmpty (message.getto ())) {// existir e enviar if (socketholder.keyset (). message.setwner (proprietário); // O proprietário será exibido se (constantValue.to_all.equals (message.getTo ())) {// One-to-all // to_all guia será selecionado; message.setFrom (constantValue.to_all); para (chave da string: Socketholder.KeySet ()) {// também envie para o Self Socketwrapper wrapper = Socketholder.get (key); if (wrapper! = null) {sendHelper.send (wrapper.getSocket (), mensagem); }}} else {// One-one SocketWrapper wrapper = socketholder.get (message.getto ()); if (wrapper! = null) {// proprietário = de sendHelper.send (wrapper.getSocket (), mensagem); // também enviado para si // para a guia será selecionado; message.setFrom (message.getto ()). Setto (proprietário); SendHelper.send (cliente, mensagem); }}}}} retornar null; }} classe public FileHandler implementa o Sockethandler {@Override Public Object Handle (cliente do soquete, dados do objeto) {if (client! = null) {filemessage message = json.parseObject (data.tostring (), filemessage.class); if (stringutil.isnotEmpty (message.getFrom ()) && stringutil.isnotEmpty (message.getto ())) {// existir e enviar if (socketholder.keyset (). Socketholder.get (message.getto ()); if (wrapper! = null) {sendHelper.send (wrapper.getSocket (), mensagem); tente {if (client! 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 ("Send Buff [" + len + "]"); } os.flush (); if (total> = message.getSize ()) {loggerutil.info ("send buff [ok]"); quebrar; }}} // Após o envio do arquivo //, envie com êxito ReturnMessage Result = new ReturnMessage (). SetKey (key.tip) .SetSUCCESS (true) .SetContent (i18n.info_file_send_sucksly); resultado.setFrom (message.getto ()). Setto (message.getFrom ()) .Setwner (constantValue.server_name); SendHelper.send (cliente, resultado); // Receba com sucesso o resultado.SetContent (i18n.info_file_receive_successly) .SetFrom (message.getFrom ()) .Setto (message.getto ()); SendHelper.send (wrapper.getSocket (), resultado); }} Catch (Exceção e) {LoggerUtil.error ("Handle File Falt!" + E.GetMessage (), e); }}}}}}} retorna null; }} /** * LogInHandler * * @author yaolin * */public class LoginHandler implementa Sockethandler {private usrservice usrservice = new usrService (); @Override Public Object Handle (cliente do soquete, dados do objeto) {returnMessage resultado = new ReturnMessage (); resultado.SetSuccess (false); if (dados! if (stringutil.isnotEmpty (message.getUserName ()) && stringutil.isnotEmpty (message.getpassword ())) {if (usrservice.login (message.getUserName (), message.getpassword ())! } else {result.setMessage (i18n.info_login_error_data); } resultado.setFrom (constantValue.Server_Name) .Setto (message.getUserName ()); } else {resultado.setMessage (i18n.info_login_empty_data); } // após o login result.setKey (key.login); if (result.issuccess ()) {// segure o soquete socketholder.put (resultado.getto (), novo soquete (cliente, new Date ())); } SendHelper.send (cliente, resultado); if (result.issuccess ()) {// envia a lista de usuários clientListUserdto dto = new clientListUserdto (); dto.setListUser (Socketholder.KeySet ()); resultado.setContent (DTO) .setKey (key.ListUser); SendHelper.send (cliente, resultado); }} retornar nulo; }} classe pública LogoutHandler implementa o Sockethandler {@Override Public Object Handle (Socket Client, Object Data) {if (data! = null) {LogoutMessage message = json.parseObject (data.tostring (), logoutMessage.class); if (mensagem! Soquete soquete = wrapper.getSocket (); if (soquete! = null) {tente {socket.close (); soquete = nulo; } catch (exceção ignorar) {}} socketholder.remove (message.getFrom ()); }} retornar nulo; }} classe pública RegisterHandler implementa o Sockethandler {private usrservice usrservice = new usrservice (); @Override Public Object Handle (cliente do soquete, dados do objeto) {returnMessage resultado = new ReturnMessage (); resultado.SetSuccess (false) .SetFrom (constantValue.Server_Name); if (dados! if (stringUtil.isnotEmpty (message.getUserName ()) && stringUtil.isnotEmpty (message.getPassword ())) {if (usrservice.register (message.getUsername (). } else {resultado.setMessage (i18n.info_register_client_exist); }} else {resultado.setMessage (i18n.info_register_empty_data); } if (stringutil.isnotEmpty (message.getUserName ())) {result.setto (message.getUserName ()); } // após o registro result.setKey (key.register); SendHelper.send (cliente, resultado); } retornar nulo; }} /** * Use sendHelper para enviar ReturnMessage, * @see yaolin.chat.server.socketdispatcher#run () * @author yaolin */ @classe pública depreciada if ( @ @@override handle (objeto sockethandler {/** ** @param retornmessage */@override handle (objeto sockethler {/** **param DataMessage */@Override handle (objeto sockethler {** ** * (ReturnMessage) dados; if (stringUtil.isnotEmpty (message.getFrom ()) && stringUtil.isnotEmpty (message.getto ())) {socketwrapper wrap = socketholder.get (message.getto ()); if (wrap! = null) {sendHelper.send (wrap.getSocket (), mensagem); }}} retornar nulo; }}Negócio de Usuário:
Além dos soquetes, o servidor também possui um pequeno negócio específico, ou seja, registro de usuário, login etc. Aqui, simplesmente listamos as duas classes de USR e USRSERVICE. Essas empresas não foram implementadas por enquanto. Não pretendo introduzir uma estrutura ORM neste programa, então escrevi um conjunto de dbutil (a ser melhorado) e o publiquei aqui.
Somente uma verificação simples é realizada aqui e não é persistente para armazená -la no banco de dados. Aqui estão USR e USRSERVICE:
classe pública usr {private longo id; Nome de usuário privado de string; senha de sequência privada; public Long getId () {return id; } public void setId (longo id) {this.id = id; } public string getUserName () {return userName; } public void setUserName (string userName) {this.username = nome de usuário; } public string getPassword () {return senha; } public void setPassword (string senha) {this.password = senha; }} /** * // TODO * @see yaolin.chat.server.usr.repository.usrrepository * @author yaolin * */public classe usrService {// TODO db mapa estático privado <string, usr> db = novo hashmap <tring, usr> (); public USR Register (String UserName, String senha) {if (stringutil.isempty (nome de usuário) || stringutil.isEmpty (senha)) {return null; } if (db.containsKey (nome de usuário)) {return null; // existe; } Usr = novo usr (); usr.setUsername (nome de usuário); usr.setpassword (md5util.getmd5code (senha)); db.put (nome de usuário, USR); retornar USR; } public USR Login (String UserName, String senha) {if (stringutil.isempty (nome de usuário) || stringUtil.isEmpty (senha)) {return null; } if (db.containsKey (nome de usuário)) {usr usr = db.get (nome de usuário); if (md5util.getmd5code (senha) .equals (usr.getpassword ())) {return usr; }} retornar nulo; }} Aqui está a ferramenta Dbutil:
/*** dbutils // TODO precisa ser ajustado e otimizado !! * @author yaolin */public class dbutil {// Faça a conexão usada Lista final estática repetidamente privada <Cnectagem> cache = new LinkedList <neconoming> (); URL de corda estática privada; driver de sequência estática privada; Usuário de String estática privada; senha privada de string estática; Debug booleano estático privado; estático {inputStream is = dbutil.class.getResourceasStream ("/db.properties"); tente {propriedades p = new Properties (); p.load (IS); url = p.getProperty ("url"); driver = p.getProperty ("driver"); usuário = P.GetProperty ("Usuário"); senha = p.getProperty ("senha"); // apenas para Debug Try {Debug = boolean.valueOf (P.GetProperty ("Debug")); } catch (exceção ignorar) {debug = false; }} Catch (Exceção e) {lança nova RunTimeException (e); } finalmente {if (is! = null) {try {is.close (); é = nulo; } catch (exceção ignorar) {}}}} public sincronizada conexão estática getConnection () {if (cache.isEmpty ()) {cache.add (makeconnection ()); } Conexão conn = null; int i = 0; tente {do {conn = cache.remove (i); } while (conn! = null && conn.iscosed () && i <cache.size ()); } catch (exceção ignorar) {} tente {if (conn == null || conn.iscLosed ()) {cache.add (makeconnection ()); Conn = cache.Remove (0); } retornar Conn; } catch (Exceção e) {lança nova RunTimeException (e); }} public sincronizado estático void fechado (conexão de conexão) {try {if (conexão! cache.add (conexão); }} catch (sqLexception ignore) {}} public static objeto consulta (string sql, resultSetMapper mapper, objeto ... args) {if (depury) debug (sql); Conexão conn = getConnection (); Preparado estatement ps = null; ResultSet rs = null; Resultado do objeto = nulo; tente {ps = Conn.Preparestatement (SQL); int i = 1; for (objeto objeto: args) {ps.setObject (i ++, objeto); } rs = ps.executeQuery (); resultado = mapper.mapper (rs); } catch (Exceção e) {lança nova RunTimeException (e); } finalmente {tente {if (rs! = null) {rs.close (); rs = nulo; } if (ps! = null) {ps.close (); ps = null; }} catch (exceção ignorar) {}} close (conn); resultado de retorno; } public static int modify (string sql, objeto ... args) {if (debug) debug (sql); Conexão conn = getConnection (); Preparado estatement ps = null; int linha = 0; tente {ps = Conn.Preparestatement (SQL); int i = 1; for (objeto objeto: args) {ps.setObject (i ++, objeto); } linha = ps.executeUpdate (); } catch (Exceção e) {lança nova RunTimeException (e); } finalmente {tente {if (ps! = null) {ps.close (); ps = null; }} catch (exceção ignorar) {}} close (conn); linha de retorno; } public static int [] lote (list <string> sqls) {if (depury) debug (sqls.toString ()); Conexão conn = getConnection (); Instrução stmt = null; int [] linha; tente {stmt = conn.createstatement (); para (String sql: sqls) {stmt.addbatch (sql); } linha = stmt.executeBatch (); } catch (Exceção e) {lança nova RunTimeException (e); } finalmente {try {if (stmt! = null) {stmt.close (); stmt = nulo; }} catch (exceção ignorar) {}} close (conn); linha de retorno; } public static int [] lote (string sql, preparado estatementsetter setter) {if (depury) depuração (sql); Conexão conn = getConnection (); Preparado estatement ps = null; int [] linha; tente {ps = Conn.Preparestatement (SQL); setter.setter (ps); linha = ps.executeBatch (); } catch (Exceção e) {lança nova RunTimeException (e); } finalmente {tente {if (ps! = null) {ps.close (); ps = null; }} catch (exceção ignorar) {}} close (conn); linha de retorno; } conexão estática privada makeconnection () {try {class.ForName (driver) .NewInstance (); Conexão conn = driverManager.getConnection (URL, usuário, senha); if (Debug) Debug ("Criar conexão!"); retornar Conn; } catch (Exceção e) {lança nova RunTimeException (e); }} private estático void debug (string sqls) {simpledateFormat sdf = new SimpleDateFormat ("yyyy-mm-dd hh: mm: ss"); System.out.println (sdf.format (new Date ()) + "Debug" + Thread.currentThread (). GetId () + "--- [" + thread.currentThread (). GetName () + "]"] + "escutas sqls:" + sqls); }} /** * PreparadostatementSetter * @author yaolin */interface pública preparada estatementSetter {public void setter (preparado estatement ps);} /** * ResultSetMapper * @Author Yaolin */Public Interface ResultSetMapper {public Object Mapper (ResultSet RS);} Download do código -fonte: demonstração
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.