Écrit avant:
Hier, j'ai enregistré la conception initiale d'un programme de chat à socket, j'ai pris le temps d'écrire sur mon blog. C'était la conception globale de ce programme. Pour l'exhaustivité, aujourd'hui, j'enregistrerai la conception du côté serveur en détail. La page d'accueil affichera le schéma de conception générale du programme de chat à socket, comme indiqué ci-dessous:
Description de la fonction:
Le serveur a deux opérations principales: l'une consiste à bloquer la prise du client récepteur et à effectuer un traitement de réponse, et l'autre est de détecter le rythme cardiaque du client. Si le client n'envoie pas de rythme cardiaque pendant une période de temps, supprimez le client, créez le Serversocket, puis démarrez deux pools de threads pour gérer ces deux choses (newFixEdThreadPool, NewscheduleDThreadPool). Les classes de traitement correspondantes sont SocketDispatcher et Socketschedule. Le socketdispatcher est distribué à différents anciens de sockethandlers en fonction de différentes demandes de socket. SocketWrapper ajoute un wrapper à coque à la prise et enregistre le dernier temps d'interaction de la prise avec le socketholder stocke la collection de socket qui interagit actuellement avec le serveur.
Implémentation spécifique:
[Server.java]
Le serveur est l'entrée du serveur. SERVERSOCKET est démarré par la méthode START () du serveur, puis bloque la demande du client récepteur et remis au socketdispatcher pour distribution. Le socketdispatcher est démarré par un pool de threads de type NEWFixEdThread. Lorsque le nombre de connexions dépasse les données maximales, elle sera traitée par la file d'attente. ScheduleAtFixeDrate est utilisé pour démarrer la boucle de synchronisation Socketschedule pour écouter le package de battements cardiaques du client. Les deux types implémentent l'interface Runnable. Ce qui suit est le code du serveur:
package yaolin.chat.server; import java.io.ioException; import java.net.serversocket; import java.util.date; import java.util.concurrent.executervice; import java.util.concurrent.executors; importer java.util.concurrent.scheduledExExEx java.util.concurrent.timeUnit; import yaolin.chat.common.constantValue; import yaolin.chat.util.loggerutil; / ** * server * @author yaolin * / public class server {private final Serversocket Server; Pool de service d'exécution final privé; public Server () lève ioException {server = new Serversocket (constantValue.server_port); Pool = exécutoires.NewFixEdThreadPool (constantValue.max_pool_size); } public void start () {try {ScheduleDexeCutOrService schedule = exécutors.newscheduledThreadpool (1); // Regardez le chien. Exception?? schedule.scheduleAtFixeDrate (new socketschedule (), 10, constanteValue.time_out, timeunit.seconds); while (true) {pool.execute (new socketdispatcher (server.accept ())); LoggerUtil.info ("Acceptez un client à" + new Date ()); }} catch (ioException e) {pool.shutdown (); }} public static void main (string [] args) {try {new server (). start (); } catch (ioException e) {LoggerUtil.Error ("Le démarrage du serveur a échoué! ->" + e.getMessage (), e); }}}[Socketdispatcher.java]
Le serveur n'est que l'entrée du serveur et du centre de commande. SocketDispatcher est le centre de commande du serveur. Il distribue différentes demandes de types de messages du client, permettant à différents Sockethandlers de traiter les demandes de messages correspondantes. Ici, l'interaction du message entre le serveur et le client utilise des données JSON. Toutes les classes de messages héritent de BasEnsage, de sorte que les données reçues sont converties en type de base de base, puis le type est jugé. (Le module de type de données appartient au module commun). Il convient de mentionner ici que lorsque le type de message est un type de fichier, il dormira pour configurer l'intervalle d'exécution, afin que FileHandler puisse avoir le temps de lire et de renvoyer le flux de fichiers au client spécifié, sans entrer immédiatement dans la boucle suivante pour juger le type de message (la conception peut être un peu problématique ici, mais faites-le pour le temps). Ce qui suit est le code de SocketDispatcher:
/ ** * socketdispatcher * * @author yaolin * / classe publique socketdispatcher implémente runnable {socket final private socket; SocketDispatcher public (socket socket) {this.socket = socket; } @Override public void run () {if (socket! = Null) {while (! Socket.isclosed ()) {try {inputStream is = socket.getInputStream (); Chaîne line = null; 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 (line); } LoggerUtil.trach ("reçoivent [" + sb.toString () + "] à" + new Date ()); Message BasEnsage = JSON.ParseObject (SB.TOSTRING (), BasEMessage.class); switch (message.getType ()) {case messageType.alive: handlerfactory.gethandler (messageType.alive) .handle (socket, sb.toString ()); casser; Case MessageType.Chat: HandlerFactory.Gethandler (MessageType.Chat) .Handle (Socket, sb.ToString ()); casser; Case MessageType.File: HandlerFactory.Gethandler (MessageType.File) .Handle (socket, sb.toString ()); casser; Case MessageType.File: HandlerFactory.Gethandler (MessageType.File) .Handle (socket, sb.toString ()); LoggerUtil.trach ("Sever: pause pour recevoir le fichier"); Thread.sleep (constantevalue.message_period); casser; Case MessageType.Login: HandlerFactory.Gethandler (MessageType.login) .Handle (Socket, sb.ToString ()); casser; Case MessageType.logout: Break; Case MessageType.Register: HandlerFactory.Gethandler (MessageType.Register) .Handle (socket, sb.toString ()); casser; }} else {thread.sleep (constantValue.Message_period); }} catch (exception e) {// catch tout le gestionnaire d'exception LoggerUtil.Error ("Erreur de socketdispatcher!" + e.getMessage (), e); }}}}}}[Socketschedule.java]
Une autre classe (composante) qui est directement liée au serveur est Socketschedule. SOCKETSCHEDULE est principalement responsable de la détection si le dernier temps d'interaction entre le client et le serveur dépasse le temps maximum autorisé dans la configuration du système. S'il dépasse, le socket client sera supprimé du serveur, sinon le dernier temps d'interaction entre le client et le serveur sera mis à jour. Voici les implémentations spécifiques:
/ ** * Supprimer la prise de socketholder si LastaliveTime> time_out * @author yaolin * * / public class socketschedule implémente runnable {@override public void run () {for (string key: socketholder.keyset ()) {socketwrapper wrapper = socketholder (key); if (wrapper! = null && wrapper.getLastaliveTime ()! = null) {if (((new Date (). gettime ()) - wrapper.getlastalivetime (). gettime ()) / 1000)> constantValue.Time_out) {// supprimer le socket if timeoutholder.remove (key); }}}}}}[Socketholder.java, socketwrapper.java]
D'après le code ci-dessus, nous pouvons voir que Socketschedule # run () n'est qu'un simple jugement du temps. Ce qui est vraiment significatif, c'est Socketholder et Socketwrapper. SocketWrapper ajoute un emballage de coquille à la prise. Socketholder stocke tous les clients qui interagissent avec le serveur pendant la durée valide actuelle. Socketholder est identifié de manière unique par le client (nom d'utilisateur ici). En tant que clé, le socket où se trouve le client est stocké en tant que valeur de valeur clé. La logique de traitement de Socketholder # FlushClientStatus () est utilisée pour informer les autres clients de l'état en ligne / hors ligne du client actuel. La mise en œuvre spécifique de ces deux classes est donnée ci-dessous:
/ ** * enveloppe de socket, socketschedule retirez la prise si LastaliveTime> time_out * @author yaolin * * / classe publique socketwrapper {socket privé socket; Date privée Lastalivetime; // Constructeur complet socketwrapper public (socket socket, date lastalivetime) {this.socket = socket; this.lastalivetime = lastalivetime; } public socket getSocket () {return socket; } public void setsocket (socket socket) {this.socket = socket; } Public Date 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 socketwrapper get (string key) {return listSocketWrap.get (key); } public static void put (clé de chaîne, valeur socketwrapper) {listSocketWrap.put (key, valeur); FlushClientStatus (Key, True); } public static socketwrapper re Support (string key) {flushClientStatus (key, false); return listSocketWrap.Remove (key); } public static void clear () {listSocketWrap.Clear (); } / ** * <pre> Contenu: {nom d'utilisateur: "", drapeau: false} </ pre> * @param Flag True: put, false: retire; * / private static void flushClientStatus (clé de chaîne, booléen drapeau) {clientNotifyDto dto = new ClientNotifyDto (Flag, key); 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)) {// pas envoyer à self rm.setto (tokey); Socketwrapper wrap = listSocketwrap.get (tokey); if (wrap! = null) {sendHelper.send (wrap.getSocket (), rm); }}}}}}[Sockethandler.java, handlerfactory.java, autrehandlerimpl.java]
SocketDispatcher permet à différents Sockethandlers de gérer les demandes de messages correspondantes. La conception de Sockethandler est en fait un ensemble simple de composants d'usine (le returnhandler est temporairement transmis par Sendhelper, mais il n'est pas utilisé pour le moment. Il a été @deprecated, et il est toujours donné ici). Le diagramme de classe complet est le suivant:
Le code de cette section est donné ci-dessous. Afin de réduire l'espace, tout le code implémenté par Handler est collecté.
/ ** * sockethandler * @author yaolin * / interface publique sockethandler {/ ** * manipuler la prise du client * / poignée d'objet public (client de socket, données d'objet);} / ** * sockethandlerfactory * @author yaolin * / public class handlerfactory {// ne peut pas créer d'instance private handlerfactory () {} public static sockethandler gethandler (int type) {switch (type) {case messageType.alive: // utilise généralement return newandler (); Case MessageType.Chat: Retour New Chathandler (); Case MessageType.Login: Return new LoginHandler (); // Case MessageType.return: // return new ReturnHandler (); Case MessageType.logout: Retour New Logouthandler (); Case MessageType.Register: Return New RegisterHandler (); Case MessageType.File: renvoyer un nouveau filehandler (); } return null; // NullPointException}} / ** * AliveSockethandler * @author yaolin * / public class AliveHandler implémente sockethandler {/ ** * @return null * / @Override Public Object Handle (Socket Client, Object DataObject (if (data.tostring), Basemessage.Class); if (stringUtil.isnotempty (message.getFrom ())) {socketwrapper wrapper = socketholder.get (message.getFrom ()); if (wrapper! = null) {wrapper.setlastalivetime (new Date ()); // Keep socket ... socketholder.put (message.getFrom (), wrapper); }}} return null; }} / ** * chathandler * * @author yaolin * / public class chathandler implémente 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 ())) {// existialiser et envoyer if (socketholder.keyset (). contient (message.getFrom ())) {string propriétaire = message.getFrom (); Message.Setowner (propriétaire); // Le propriétaire sera affiché si (constantValue.to_all.equals (message.getto ())) {// onglet one-all // to_all sera sélectionné; message.setFrom (constantValue.to_all); for (String Key: socketholder.KeySet ()) {// Envoi également à Self SocketWrapper Wrapper = socketholder.get (key); if (wrapper! = null) {sendHelper.send (wrapper.getSocket (), message); }}} else {// un à un à un à un socketwrapper wrapper = socketholder.get (message.getto ()); if (wrapper! = null) {// propriétaire = de SendHelper.Send (wrapper.getSocket (), message); // Envoi également à l'onglet Self // sera sélectionné; Message.setFrom (message.getTo ()). SetTo (propriétaire); SendHelper.send (client, message); }}}}} return null; }} classe publique FileHandler implémente sockethandler {@Override Public Object Handle (Socket Client, Object Data) {if (client! = NULL) {fileMessage Message = JSON.ParseObject (data.ToString (), fileMessage.class); if (stringUtil.isnotempty (message.getFrom ()) && stringUtil.isnotempty (message.getto ())) {// existialiser et envoyer if (socketholder.keyset (). contient (message.getFrom ())) {if (! ConstantValue.to_all.equetwe Socketholder.get (message.getTo ()); if (wrapper! = null) {sendHelper.send (wrapper.getSocket (), message); essayez {if (client! = null && wrapper.getSocket ()! = null && message.getSize ()> 0) {inputStream est = client.getInputStream (); OutputStream os = wrapper.getSocket (). GetOutputStream (); int total = 0; while (! client.isclosed () &&! wrapper.getSocket (). isClosed ()) {if (is.available ()> 0) {byte [] buff = new byte [constantevalue.buff_size]; int len = -1; while (is.available ()> 0 && (len = is.read (buff))! = -1) {os.write (buff, 0, len); Total + = len; LoggerUtil.debug ("Envoyer Buff [" + len + "]"); } os.flush (); if (total> = message.getSize ()) {loggerUtil.info ("Envoyer buff [ok]"); casser; }}} // Après envoyer un fichier // Envoyer avec succès RetourMessage result = new returnMessage (). SetKey (key.tip) .setsuccess (true) .setContent (i18n.info_file_send_successly); result.setFrom (message.getTo ()). setTo (message.getFrom ()) .setOwner (constantValue.server_name); SendHelper.send (client, résultat); // reçoit avec succès le résultat.setContent (i18n.info_file_receive_sucCessully) .setFrom (message.getFrom ()) .setTo (message.getTo ()); SendHelper.send (wrapper.getSocket (), résultat); }} catch (exception e) {loggerUtil.error ("Fichier de manche a échoué!" + e.getMessage (), e); }}}}}}} return null; }} / ** * LoginHandler * * @Author Yaolin * * / classe publique LoginHandler implémente sockethandler {private usrService usrService = new USRService (); @Override Public Object Handle (Socket Client, Object Data) {returnMessage result = new returnMessage (); résultat.setsuccess (false); if (data! = null) {LoginMessage Message = JSON.PaSeObject (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 (message.getUserName ()); } else {result.setMessage (i18n.info_login_empty_data); } // After Login result.setKey (key.login); if (result.issucCess ()) {// Hold socket socketholder.put (result.getTo (), new socketwrapper (client, new Date ())); } Sendhelper.send (client, résultat); if (result.issucCess ()) {// Envoyer la liste utilisateur ClientListUserdTo dto = new ClientListUserDTO (); dto.setListUser (socketholder.KeySet ()); result.setContent (dto) .setKey (key.listUser); SendHelper.send (client, résultat); }} return null; }} classe publique Logouthandler implémente sockethandler {@Override Public Object Handle (Socket Client, Object Data) {if (data! = null) {LogoutMessage Message = JSON.PaSeObject (data.ToString (), LogoutMessage.Class); if (message! = null && stringUtil.isnotempty (message.getFrom ())) {socketwrapper wrapper = socketholder.get (message.getFrom ()); Socket socket = wrapper.getSocket (); if (socket! = null) {try {socket.close (); socket = null; } catch (exception ignore) {}} socketholder.remove (message.getFrom ()); }} return null; }} classe publique RegisterHandler implémente sockethandler {private usrService usrService = new USRService (); @Override Public Object Handle (Socket Client, Object Data) {returnMessage result = new returnMessage (); result.SetSuccess (false) .setFrom (constantValue.server_name); if (data! = null) {registerMessage Message = json.PaSeObject (data.toString (), registerMessage.class); if (stringUtil.isnotempty (message.getUserName ()) && stringUtil.isnotempty (message.getPassword ())) {if (usrService.register (message.geturname (), message.getPassword ())! = null) {result.setsuccess (true) .setContent (i18n.info_register_ok). } else {result.setMessage (i18n.info_register_client_exist); }} else {result.setMessage (i18n.info_register_empty_data); } if (stringUtil.isnotempty (message.getUserName ())) {result.setto (message.getUserName ()); } // après le registre result.setKey (key.register); SendHelper.send (client, résultat); } return null; }} / ** * Utilisez Sendhelper pour envoyer ReturnMessage, * @see yaolin.chat.server.socketdispatcher # run () * @author yaolin * / @ Donvated public class returnhandler implémente SOCKETHANDLER {/ ** * @Param Data ReturnMessage * / @Override Public Message (Socketh (ReturnMessage) Données; if (stringUtil.isnotempty (message.getFrom ()) && stringUtil.isnotempty (message.getTo ())) {socketwrapper wrap = socketholder.get (message.getTo ()); if (wrap! = null) {sendHelper.send (wrap.getSocket (), message); }}} return null; }}Entreprise d'utilisateurs:
En plus des sockets, le serveur a également une petite entreprise spécifique, c'est-à-dire l'enregistrement des utilisateurs, la connexion, etc. Ici, nous énumérons simplement les deux classes d'USR et USRService. Ces entreprises n'ont pas été mises en œuvre pour le moment. Je n'ai pas l'intention de présenter un cadre ORM dans ce programme, j'ai donc écrit un ensemble de DButil (à améliorer) et je l'ai publié ici.
Seule une vérification simple est effectuée ici, et il n'est pas persisté de le stocker dans la base de données. Voici USR et USRSERVICE:
classe publique usr {private long id; Nom d'utilisateur de chaîne privée; mot de passe de chaîne privé; public long getID () {return id; } public void setid (long id) {this.id = id; } public String getUserName () {return username; } public void setUsername (String username) {this.userName = username; } public String getPassword () {return mot de passe; } public void setPassword (String Motword) {this.password = mot de passe; }} / ** * // todo * @see yaolin.chat.server.usr.repository.usrrepository * @author yaolin * * / public class usrService {// too db map private static <string, usr> db = new hashmap <string, usr> (); Public USR Register (String Username, String Motword) {if (stringUtil.iSempty (nom d'utilisateur) || StringUtil.iSempty (mot de passe)) {return null; } if (db.containsKey (nom d'utilisateur)) {return null; // exister; } Usr usr = new USR (); usr.setUsername (nom d'utilisateur); usr.setpassword (md5util.getmd5code (mot de passe)); db.put (nom d'utilisateur, usr); retourner usr; } public Usr Login (String Username, String Motword) {if (stringUtil.iSempty (nom d'utilisateur) || StringUtil.iSempty (mot de passe)) {return null; } if (db.containsKey (nom d'utilisateur)) {usr usr = db.get (nom d'utilisateur); if (md5util.getmd5code (mot de passe) .equals (usr.getpassword ())) {return usr; }} return null; }} Voici l'outil DBUTIL:
/ ** * DBUTILS // TODO doit être ajusté et optimisé !! * @author yaolin * / classe publique dButil {// faire de la connexion utilisée à plusieurs reprises la liste finale statique privée <Connexion> cache = new LinkedList <Connexion> (); URL de chaîne statique privée; pilote de chaîne statique privé; Utilisateur de chaîne statique privée; Mot de passe de chaîne statique privé; débogage booléen statique privé; static {inputStream est = dbutil.class.getResourceSstream ("/ db.properties"); essayez {Properties p = new Properties (); P.Load (IS); url = p.getProperty ("url"); Driver = P.GetProperty ("Driver"); user = p.getProperty ("utilisateur"); mot de passe = p.getProperty ("mot de passe"); // Juste pour Debug Try {Debug = boolean.valueof (p.getProperty ("Debug")); } catch (exception ignore) {debug = false; }} catch (exception e) {lancer une nouvelle RuntimeException (e); } enfin {if (is! = null) {try {is.close (); est = null; } catch (exception ignore) {}}}} Connexion statique synchronisée publique getConnection () {if (cache.isempty ()) {cache.add (makeConnection ()); } Connexion conn = null; int i = 0; essayez {do {conn = cache.remove (i); } while (conn! = null && conn.isclosed () && i <cache.size ()); } catch (exception ignore) {} try {if (conn == null || Conn.isclosed ()) {cache.add (makeConnection ()); Conn = Cache.Remove (0); } return conn; } catch (exception e) {lancer une nouvelle RuntimeException (e); }} public synchronisé statique void close (connexion connexion) {try {if (connection! = null &&! connection.isclosed ()) {if (debug) debug ("release connection!"); cache.add (connexion); }} catch (sqlexception ignore) {}} public static objet requête (String sql, resultSetMapper mappeur, objet ... args) {if (debug) debug (sql); Connexion conn = getConnection (); PréparedStatement PS = null; ResultSet rs = null; Résultat de l'objet = null; essayez {ps = conn.preparestatement (sql); int i = 1; pour (objet objet: args) {ps.SetObject (i ++, objet); } rs = ps.ExecuteQuery (); résultat = mapper.mapper (RS); } catch (exception e) {lancer une nouvelle RuntimeException (e); } enfin {try {if (rs! = null) {Rs.close (); rs = null; } if (ps! = null) {ps.close (); ps = null; }} catch (exception ignore) {}} close (Conn); Résultat de retour; } public static int modify (String sql, objet ... args) {if (debug) debug (sql); Connexion conn = getConnection (); PréparedStatement PS = null; int row = 0; essayez {ps = conn.preparestatement (sql); int i = 1; pour (objet objet: args) {ps.SetObject (i ++, objet); } row = ps.ExecuteUpdate (); } catch (exception e) {lancer une nouvelle RuntimeException (e); } enfin {try {if (ps! = null) {ps.close (); ps = null; }} catch (exception ignore) {}} close (Conn); ligne de retour; } public static int [] Batch (list <string> sqls) {if (debug) debug (sqls.toString ()); Connexion conn = getConnection (); Déclaration stmt = null; int [] row; essayez {stmt = conn.createStatement (); pour (String SQL: SQLS) {stmt.addbatch (sql); } row = stmt.executebatch (); } catch (exception e) {lancer une nouvelle RuntimeException (e); } enfin {try {if (stmt! = null) {stmt.close (); stmt = null; }} catch (exception ignore) {}} close (Conn); ligne de retour; } public static int [] Batch (String SQL, PreadStatementsEtter setter) {if (debug) Debug (sql); Connexion conn = getConnection (); PréparedStatement PS = null; int [] row; essayez {ps = conn.preparestatement (sql); seter.setter (ps); row = ps.ExecuteBatch (); } catch (exception e) {lancer une nouvelle RuntimeException (e); } enfin {try {if (ps! = null) {ps.close (); ps = null; }} catch (exception ignore) {}} close (Conn); ligne de retour; } connexion statique privée makeConnection () {try {class.forname (driver) .newInstance (); Connection Conn = driverManager.getConnection (URL, utilisateur, mot de passe); si (débogage) débogue ("créer une connexion!"); Retourne Conn; } catch (exception e) {lancer une nouvelle RuntimeException (e); }} private static 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 () + "]" + "excuse sqls:" + sqls); }} / ** * préparéStatementsEtter * @author yaolin * / interface publique préparée PreadStatementsSet {public void setter (préparé PS);} / ** * ResultSetMapper * @author yaolin * / interface publique ResultimeSetMapper {Mappeur d'objet public (ResultSet RS);} Téléchargement du code source: démo
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.