Vorher geschrieben:
Gestern habe ich das erste Design eines Socket -Chat -Programms aufgenommen, das ich mir die Zeit genommen habe, in meinem Blog zu schreiben. Es war das Gesamtdesign dieses Programms. Zur Vollständigkeit werde ich heute das Design der Serverseite im Detail aufzeichnen. Die Homepage wird das allgemeine Designdiagramm des Socket -Chat -Programms veröffentlichen, wie unten gezeigt:
Funktionsbeschreibung:
Der Server verfügt über zwei Hauptvorgänge: Einer besteht darin, den Socket des empfangenden Clients zu blockieren und die Antwortverarbeitung durchzuführen. Der andere ist die Erkennung des Herzschlags des Clients. Wenn der Kunde für einen bestimmten Zeitraum keinen Herzschlag sendet, entfernen Sie den Client, erstellen Sie das Serversocket und starten Sie dann zwei Thread -Pools, um diese beiden Dinge zu verarbeiten (Newfixed Threadpool, Newsschoned threadpool). Die entsprechenden Verarbeitungsklassen sind SocketDispatcher und Socketplane. Der SocketDispatcher wird gemäß verschiedenen Socket -Anfragen an verschiedene Sockhandler verteilt. SocketWrapper fügt dem Socket einen Shell -Wrapper hinzu und zeichnet die neueste Interaktionszeit des Socket mit dem Socketholder auf, der die Socket -Sammlung speichert, die derzeit mit dem Server interagiert.
Spezifische Implementierung:
[Server.java]
Server ist der Eingang zum Server. ServerSocket wird mit der Servermethode start () gestartet und blockiert dann die Anforderung des empfangenen Clients und übergeben an den SocketDispatcher zur Verteilung. Der SocketDispatcher wird mit einem Thread -Pool von NeufixedThread -Typ gestartet. Wenn die Anzahl der Verbindungen die maximalen Daten überschreitet, wird sie von der Warteschlange verarbeitet. ScheduleatFixedRate wird verwendet, um die Timing -Schleife mit Sockelschetten zu starten, um das Herzschlagpaket des Kunden anzuhören. Beide Typen implementieren die Runnable -Schnittstelle. Das Folgende ist der Code für den Server:
Paket yaolin.chat.server; import Java.io.ioxception; import java.net.serversocket; import Java.util.date; import Java.util.concurrent.executorservice; java.util.concurrent privater Final Executorservice Pool; public server () löscht IOException {Server = new ServerSocket (ConstantValue.server_port); pool = ausführende } public void start () {try {plantedexecutorService -Zeitplan = Executors.NewScheduledThreadpool (1); // Hunde beobachten. Ausnahme?? Schedule.ScheduleatFixedRate (New SocketSchedule (), 10, ConstantValue.time_out, TimeUnit.seconds); while (true) {pool.execute (neuer SocketDispatcher (server.accept ())); Loggerutil.info ("einen Client bei" + New Date ()); }} catch (ioException e) {pool.shutdown (); }} public static void main (String [] args) {try {new server (). start (); } catch (ioException e) {loggerutil.Error ("Serverstart fehlgeschlagen! ->" + e.getMessage (), e); }}}[SocketDispatcher.java]
Der Server ist nur der Eingang zum Server und zum Befehlszentrum. SocketDispatcher ist die Befehlszentrale des Servers. Es verteilt verschiedene Nachrichtentypenanfragen vom Client, sodass verschiedene Sockhandler die entsprechenden Nachrichtenanforderungen verarbeiten können. Hier verwendet die Nachrichteninteraktion zwischen dem Server und dem Client JSON -Daten. Alle Nachrichtenklassen erben die Basisemessage, sodass die empfangenen Daten in den Basemessage -Typ umgewandelt werden und dann der Typ beurteilt wird. (Das Datentypmodul gehört zum gemeinsamen Modul). Es sollte hier erwähnt werden, dass, wenn der Nachrichtentyp ein Dateityp ist, das Ausführungsintervall für den Konfigurieren des Ausführungsintervalls schläft, sodass FileHandler Zeit zum Lesen und Wiedergenden des Dateistroms an den angegebenen Client haben kann, ohne sofort die nächste Schleife zu beurteilen, um den Nachrichtentyp zu beurteilen (das Design kann hier etwas problematisch sein, aber dies zum Zeitpunkt der Zeit tun). Das Folgende ist der Code von SocketDispatcher:
/** * SocketDispatcher * * @author yaolin */Public Class SocketDispatcher implementiert Runnable {private endgültige Socket Socket; public socketDispatcher (Socket Socket) {this.socket = Socket; } @Override public void run () {if (socket! = Null) {while (! SocTet.isclosed ()) {try {InputStream is = socket.getInputStream (); String line = null; StringBuffer sb = null; if (is.available ()> 0) {bufferedReader buf = new bufferedReader (neuer InputStreamReader (IS)); sb = new StringBuffer (); while (is.available ()> 0 && (line = bufr.readline ())! = null) {sb.append (line); } Loggerutil.trach ("empfangen [" + sb.toString () + "] at" + New Date ()); Basemessage message = json.parseObject (sb.toString (), basemessage.class); Switch (message.gettType ()) {case MessAgetype.alive: HandlerFactory.getHandler (MessAgetype.alive) .Handle (Socket, sb.toString ()); brechen; case messagetype.chat: HandlerFactory.getHandler (MessAgetype.Chat) .Handle (Socket, sb.toString ()); brechen; case messagetype.file: HandlerFactory.getHandler (MessAgetype.file) .Handle (Socket, sb.toString ()); brechen; case messagetype.file: HandlerFactory.getHandler (MessAgetype.file) .Handle (Socket, sb.toString ()); Loggerutil.trach ("Sever: Pause, um Datei zu empfangen"); Thread.sleep (ConstantValue.Message_period); brechen; case messagetype.login: HandlerFactory.getHandler (MessAgetype.login) .Handle (Socket, sb.toString ()); brechen; case messagetype.logout: break; case messagetype.register: HandlerFactory.getHandler (MessAgetype.register) .Handle (Socket, sb.toString ()); brechen; }} else {thread.sleep (constantValue.message_period); }} catch (Ausnahme e) {// Catch alle Handler -Ausnahme loggerutil.Error ("SocketDispatcher -Fehler!" + e.getMessage (), e); }}}}}}[SocketSchedule.java]
Eine andere Klasse (Komponente), die in direktem Zusammenhang mit dem Server steht, ist Socketplane. SocketSchedule ist hauptsächlich dafür verantwortlich, festzustellen, ob die neueste Interaktionszeit zwischen dem Client und dem Server die maximal zulässige Zeit in der Systemkonfiguration überschreitet. Wenn es überschreitet, wird der Client -Socket vom Server entfernt, andernfalls wird die neueste Interaktionszeit zwischen dem Client und dem Server aktualisiert. Im Folgenden sind die spezifischen Implementierungen:
/** * Entfernen Sie Socket von SocketHolder, wenn lastalivetime> time_out * @author yaolin * * */public class SocketSchedule implementiert Runnable {@Override public void run () {für (String Key: SocketHolder.Keyset ()) {SocketWrapper Wrapper = SocKetholder.get. Key); if (Wrapper! = null && wrapper.getLastalivetime ()! }}}}}}[Socketholder.java, Socketwrapper.java]
Aus dem obigen Code können wir sehen, dass SocketSchedule#run () nur ein einfaches Urteilsvermögen ist. Was wirklich bedeutungsvoll ist, ist Sockeldholder und Socketwrapper. Socketwrapper fügt der Sockel einen Schalenverpackung hinzu. SocketHolder speichert alle Clients, die während der aktuellen gültigen Zeit mit dem Server interagieren. Socketholder wird vom Client eindeutig identifiziert (Benutzername hier). Als Schlüssel wird die Socket, in der sich der Kunde befindet, als Schlüsselwertpaar gespeichert. Die Verarbeitungslogik von Socketholder#flushClientStatus () wird verwendet, um andere Kunden über den Online-/Offline -Status des aktuellen Kunden zu informieren. Die spezifische Implementierung dieser beiden Klassen ist unten angegeben:
/** * Wrap Socket, Socketschedule Entfernen Sie Socket, wenn lastalivetime> time_out * @author yaolin * */öffentliche Klasse Socketwrapper {private Socket Socket; Privatdatum letztes Alivetime; // Full Constructor Public Socketwrapper (Socket Socket, Datum letztes Alivetime) {this.socket = Socket; this.lastalivetime = lastalivetime; } public Socket Getocket () {return Socket; } public void setSocket (Socket Socket) {this.socket = Socket; } public date getLastalivetime () {return lastalivetime; } public void setLastalivetime (Datum lastalivetime) {this.lastalivetime = lastalivetime; }} /** * SocketHolder * @author yaolin */public class SocketHolder {private statische ConcurrentMap <String, SocketWrapper> listSocketwrap = new ConcurrentHasMap <String, socketWrapper> (); public static set <string> keyset () {return listSocketwrap.keyset (); } public static SocketWrapper get (String -Schlüssel) {return listSocketwrap.get (Schlüssel); } public static void put (String -Schlüssel, SocketWrapper Value) {listSocketwrap.put (Schlüssel, Wert); FlushClientStatus (Schlüssel, wahr); } public static SocketWrapper entfernen (String -Schlüssel) {FlushClientStatus (Schlüssel, Falsch); return listSocketwrap.remove (Schlüssel); } public static void clear () {listSocketwrap.clear (); } /** * <pre> Inhalt: {Benutzername: "", Flag: False} < /pre> * @param Flag true: put, false: entfernen; */ private statische void flushclientStatus (String -Schlüssel, booleale Flag) {ClientNotifyDto dto = new ClientNotifyDTO (Flag, Schlüssel); ReturnMessage rm = new returnMessage (). SetKey (key.notify) .setsuSccess (true) .setContent (dto); rm.setfrom (constantValue.server_name); für (String tey: listSocketwrap.keyset ()) {if (! tey.equals (key)) {// nicht an self rm.setto (tey); SocketWrapper Wrap = ListSocketWrap.get (tey); if (Wrap! = null) {sendHelper.send (Wrap.getSocket (), RM); }}}}}}[SocketHandler.java, Handlerfactory.java, SonsHandlerImpl.java]
Mit SocketDispatcher können verschiedene Sockhandler entsprechende Nachrichtenanforderungen bearbeiten. Das Design von Sockhandler ist eigentlich eine einfache Reihe von Werkskomponenten (der Rückkehrer wird vorübergehend von Sendhelper übertragen, aber es wird vorerst nicht verwendet. Es wurde @deprecated und es wird hier immer noch angegeben). Das vollständige Klassendiagramm lautet wie folgt:
Der Code für diesen Abschnitt ist unten angegeben. Um den Raum zu reduzieren, wird der gesamte von Handler implementierte Code gesammelt.
/** * SocketHandler * @author yaolin */public interface Sockshandhandler {/** * Handle -Client -Socket */Public Object Handle (Socket -Client, Objektdaten);} /** * SocKetHandlerFactory * @Author yaolin */public class HandlerFactory {// kann keine Instanz private HandlerFactory () {} public static sockshandler GetHandler (int type) {switch (type) {case messagetype.alive: // verwenden normalerweise zurück, um neue lebende neue lebende zu nutzen. case messagetype.chat: return New ChatHandler (); case messagetype.login: return New LoginHandler (); // case messagetype.return: // return New ReturnHandler (); case messagetype.logout: return New Logouthandler (); case MessAgetype.register: Neue RegisterHandler () zurückgeben; case messagetype.file: return New FileHandler (); } return null; // nullpointException}} /** * alivesockethandler * @author yaolin */public class AliveHandler implementiert Sockshandler {/** * @return null */@Override public Object Handle (Socket Client, Objektdaten) {if (data! if (stringutil.isnotEmpty (message.getfrom ())) {SocketWrapper Wrapper = socketholder.get (message.getfrom ()); if (Wrapper! // Socket halten ... SocketHolder.put (message.getfrom (), Wrapper); }}} return null; }} /** * ChatHandler * * @author yaolin */public class chathandler implementiert socketHander {@Override public Object Handle (Socket -Client, Objektdaten) {if (data! = Null) {chatMessage message = json.parseobject (Data.toString (), ChatMessage.Class); if (stringutil.isnotEmpty (message.getfrom ()) && stringutil.isnotempty (message.getto ())) {// existieren & senden if (sockTholder.keyset (). enthält (message.getfrom ()) {String -Eigentümer = message.getfrom (); Message.Setowner (Eigentümer); // Eigentümer wird angezeigt, wenn (constantValue.to_all.equals (message.getto ()) {// Ein- und All // to_all Registerkarte ausgewählt wird; Message.SetFrom (constantValue.to_all); für (String -Schlüssel: SocketHolder.keyset ()) {// auch an self socketwrapper wrapper = socketholder.get (Schlüssel); if (Wrapper! }}} else {// Eins-zu-Eins-Socketwrapper-Wrapper = socketholder.get (message.getto ()); if (Wrapper! // auch an self // an die Registerkarte senden wird ausgewählt; message.setfrom (message.getto ()). setto (Eigentümer); SendHelper.send (Client, Nachricht); }}}}} return null; }} Public Class FileHandler implementiert SocketHandler {@Override Public Object Handle (Socket -Client, Objektdaten) {if (client! if (stringutil.isnotEmpty (message.getfrom ()) && stringutil.isnotempty (message.getto ()) {// existieren & senden if (SocketHolder.keyset (). containes (message.getfrom ()) {if (! constantValue.toAntOsTe). SocketHolder.get (message.getto ()); if (Wrapper! try {if (client! = null && wraper.getSocket ()! OutputStream os = wrapper.getSocket (). GetOutputStream (); int total = 0; while (! client.isclesed () &&! Wraper.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); Gesamt += Len; Loggerutil.debug ("Senden Sie Buff [" + len + "]"); } os.flush (); if (total> = message.getSize ()) {loggerutil.info ("Senden Buff [OK]"); brechen; }}} // Nach dem Senden von Datei // erfolgreich returnMessage result = new returnMessage (). SetKey (key.tip) .setsuSccess (true) .SetContent (i18n.info_file_send_successibiling); result.setfrom (message.getto ()). setTo (message.getfrom ()) .setowner (constantValue.server_name); Sendhelper.send (Client, Ergebnis); // erfolgreich ergebnis. Sendhelper.send (Wraper.getSocket (), Ergebnis); }} catch (Ausnahme e) {loggerutil.Error ("Handle fehlgeschlagen!" + e.getMessage (), e); }}}}}}} return null; }} /** * LoginHandler * * @author yaolin * */public class loginHandler implementiert sockshander {private usrService usrService = new usrService (); @Override Public Object Handle (Socket -Client, Objektdaten) {returnMessage result = new ReturnMessage (); result.setsuSccess (falsch); if (data! if (stringutil.isnotEmpty (message.getUnername ()) && stringutil.isnotempty (message.getPassword ()) {if (usrService.login (message.getUnername (), message.getPassword ())! } else {result.setMessage (i18n.info_login_error_data); } result.setFrom (constantValue.server_name) .setto (message.getUnername ()); } else {result.setMessage (i18n.info_login_empty_data); } // nach Anmeldergebnis.setKey (key.login); if (result.ISSUCCESS ()) {// Socket Socketholder.put (result.getto (), neuer SocketWrapper (Client, New Date ())); } SendHelper.send (Client, Ergebnis); if (result.issuccess ()) {// list user clientlistUserdto dto = new ClientListUserDto (); DTO.SetListUser (Socketholder.Keyset ()); result.setContent (dto) .setKey (key.listUser); Sendhelper.send (Client, Ergebnis); }} return null; }} öffentliche Klasse logouthandler implementiert SocketHandler {@Override Public Object Handle (Socket Client, Object Data) {if (data! if (message! Socket Socket = Wrapper.getSocket (); if (Socket! = null) {try {socket.close (); Socket = null; } catch (Ausnahme ignorieren) {}} Socketholder.remove (message.getfrom ()); }} return null; }} public class RegisterHandler implementiert Sockhandler {private usrservice usrservice = new usrservice (); @Override Public Object Handle (Socket -Client, Objektdaten) {returnMessage result = new ReturnMessage (); result.setSuSccess (false) .setFrom (ConstantValue.Server_Name); if (data! if (stringutil.isnotEmpty (message.getUnername ()) && stringutil.isnotempty (message.getPassword ()) {if (usrService.register (message.getUnername (), message.getPassword ())! } else {result.setMessage (i18n.info_register_client_exist); }} else {result.setMessage (i18n.info_register_Empty_data); } if (stringutil.isnotEmpty (message.getUnername ()) {result.setto (message.getUnername ()); } // nach Registerergebnis.setKey (key.register); Sendhelper.send (Client, Ergebnis); } return null; }} /** * Verwenden Sie SendHelper, um returnMessage zu senden. = (ReturnMessage) Daten; if (stringutil.isnotEmpty (message.getfrom ()) && stringutil.isnotEmpty (message.getto ())) {SocketWrapper Wrack = SocketHolder.get (message.getto ()); if (Wrap! = null) {sendHelper.send (Wrap.getSocket (), Nachricht); }}} return null; }}Benutzergeschäft:
Zusätzlich zu Sockets hat der Server auch ein kleines Geschäft, dh Benutzerregistrierung, Anmeldung usw. Hier listen wir einfach die beiden Klassen von USR und USRService auf. Diese Unternehmen wurden vorerst nicht implementiert. Ich beabsichtige nicht, in diesem Programm ein Orm -Framework vorzustellen, also habe ich eine Reihe von DButil (zu verbessert werden) geschrieben und hier gepostet.
Hier wird nur eine einfache Überprüfung durchgeführt, und es wird nicht bestehen, sie in der DB zu speichern. Hier sind USR und USRService:
öffentliche Klasse usr {private long id; privater String -Benutzername; privates Zeichenfolgenkennwort; public long getid () {return id; } public void setId (Long id) {this.id = id; } public String getUnername () {return userername; } public void setUnername (String -Benutzername) {this.username = userername; } public String getPassword () {Kennwort zurückgeben; } public void setPassword (String -Passwort) {this.password = password; }} /** * // todo * @see yaolin.chat.server.usr.repository.usrrepository * @author yaolin * */public class usrService {// todo db private statische Karte <String, usr> db = new Hashmap <string, usr> (); öffentliches USR -Register (String -Benutzername, String -Passwort) {if (stringutil.isempty (Benutzername) || StringUtil.isempty (Passwort)) {return null; } if (db.containsKey (Benutzername)) {return null; // existieren; } Usr usr = new usr (); usr.setusername (Benutzername); usr.setPassword (md5util.getMd5Code (Passwort)); DB.Put (Benutzername, usr); Rückkehr usr; } public usr login (String -Benutzername, String -Passwort) {if (stringutil.isempty (Benutzername) || Stringutil.isempty (Passwort)) {return null; } if (db.containSkey (Benutzername)) {usr usr = db.get (Benutzername); if (md5util.getMd5Code (Passwort) .Equals (usr.getPassword ())) {return usr; }} return null; }} Hier ist das DBUTIL -Tool:
/*** dButils // Todo muss angepasst und optimiert werden !! * @author yaolin */public class dbutil {// Verbindung verwendete wiederholt private statische endgültige Liste <Verbindung> cache = new LinkedList <Connection> (); private statische String -URL; privater statischer Stringfahrer; privater statischer String -Benutzer; privates statisches String -Passwort; privates statisches Boolean Debugug; static {InputStream ist = dbutil.class.getResourceAsStream ("/db.properties"); try {Eigenschaften p = neue Eigenschaften (); P.Load (ist); url = p.getProperty ("url"); Driver = P.GetProperty ("Treiber"); user = p.getProperty ("Benutzer"); password = p.getProperty ("Passwort"); // nur für Debug -Versuche {debug = boolean.Valueof (P.GetProperty ("Debug")); } catch (Ausnahme ignorieren) {debug = false; }} catch (Ausnahme E) {neue runTimeException (e) werfen; } endlich {if (ist! = null) {try {is.close (); ist = null; } catch (Exception Ignore) {}}}} public synchronisierte statische Verbindung getConnection () {if (cache.isempty ()) {cache.add (makeConnection ()); } Verbindung conn = null; int i = 0; Versuchen Sie {do {conn = cache.remove (i); } while (conn! = null && conn.isclosed () && i <cache.size ()); } catch (Ausnahme ignorieren) {} try {if (conn == null || conn.isclosed ()) {cache.add (makeConnection ()); conn = cache.remove (0); } return conn; } catch (Ausnahme e) {neue runTimeException (e) werfen; }} public synchronisierte statische void close (Verbindungsverbindung) {try {if (connection! = null &&! connection.isclosed ()) {if (debug) debug ("Release Connection!"); cache.add (Verbindung); }} catch (sqlexception ignorore) {}} öffentliche statische Objektabfrage (String SQL, resultSetMapper Mapper, Objekt ... args) {if (debug) debug (SQL); Verbindung conn = getConnection (); PrepedStatement ps = null; ResultSet rs = null; Objektergebnis = null; try {ps = conn.preparestatement (SQL); int i = 1; für (Objektobjekt: args) {ps.SetObject (i ++, Objekt); } rs = ps.executeQuery (); result = mapper.mapper (rs); } catch (Ausnahme e) {neue runTimeException (e) werfen; } endlich {try {if (rs! = null) {rs.close (); rs = null; } if (ps! = null) {ps.close (); ps = null; }} catch (Ausnahme ignorieren) {}} close (conn); Rückgabeergebnis; } public static int modify (String SQL, Objekt ... args) {if (debug) debug (SQL); Verbindung conn = getConnection (); PrepedStatement ps = null; int row = 0; try {ps = conn.preparestatement (SQL); int i = 1; für (Objektobjekt: args) {ps.SetObject (i ++, Objekt); } row = ps.executeUpdate (); } catch (Ausnahme e) {neue runTimeException (e) werfen; } endlich {try {if (ps! = null) {ps.close (); ps = null; }} catch (Ausnahme ignorieren) {}} close (conn); Rückreihe; } public static int [] batch (list <string> sqls) {if (debug) debug (sqls.toString ()); Verbindung conn = getConnection (); Aussage STMT = NULL; int [] row; try {stmt = conn.CreateStatement (); für (String SQL: SQLS) {stmt.addbatch (SQL); } row = stmt.executebatch (); } catch (Ausnahme e) {neue runTimeException (e) werfen; } endlich {try {if (stmt! = null) {stmt.close (); stmt = null; }} catch (Ausnahme ignorieren) {}} close (conn); Rückreihe; } public static int [] batch (String SQL, PreparedStatementStter Setter) {if (Debug) Debugug (SQL); Verbindung conn = getConnection (); PrepedStatement ps = null; int [] row; try {ps = conn.preparestatement (SQL); Setter.Setter (PS); row = ps.executebatch (); } catch (Ausnahme e) {neue runTimeException (e) werfen; } endlich {try {if (ps! = null) {ps.close (); ps = null; }} catch (Ausnahme ignorieren) {}} close (conn); Rückreihe; } private statische Verbindung makeConnection () {try {class.forname (Treiber) .NeWInstance (); Verbindung conn = driverManager.getConnection (URL, Benutzer, Passwort); if (debug) debug ("Connection!"); Conn zurückgeben; } catch (Ausnahme e) {neue runTimeException (e) werfen; }} private static void Debugug (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 () + "]" + "excute sqls:" + sqls); }} /** * PreparedStatementsSetter * @Author Yaolin */Public Interface PreparedStatementSetter {public void Setter (PrepedStatement PS);}; /** * resultSetMapper * @author yaolin */public interface resultSetMapper {public Object mapper (resultSet rs);}; Quellcode -Download: Demo
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, es wird für das Lernen aller hilfreich sein und ich hoffe, jeder wird Wulin.com mehr unterstützen.