Написано ранее:
Вчера я записал первоначальный дизайн программы чата сокета, чтобы написать в своем блоге. Это был общий дизайн этой программы. Для полноты, сегодня я подробно расскажу о дизайне сервера. Домашняя страница опубликует общую схему проектирования программы чата сокета, как показано ниже:
Описание функции:
Сервер имеет две основные операции: одна - заблокировать сокет приемного клиента и выполнить обработку ответов, а другая - обнаружить сердцебиение клиента. Если клиент не отправляет сердцебиение в течение определенного периода времени, удалите клиента, создайте Serversocket, а затем запустите два пула потоков, чтобы справиться с этими двумя вещами (NewFixedThreadpool, NewschedThreadpool). Соответствующие классы обработки являются SocketDispatcher и SocketSchedule. SocketDispatcher распределяется по разным носкам в соответствии с различными запросами сокетов. SocketWrapper добавляет оболочку в розетку и записывает последнее время взаимодействия сокета, а Socketholder сохраняет коллекцию сокетов, которая в настоящее время взаимодействует с сервером.
Конкретная реализация:
[Server.java]
Сервер - это вход на сервер. Serversocket запускается методом START () сервера, а затем блокирует запрос принимающего клиента и передает SocketDispatcher для распространения. SocketDispatcher запускается пулом потоков типа NewFixedThread. Когда количество подключений превышает максимальные данные, оно будет обработано в очереди. PradeuLeatFixEdRate используется для запуска петли времени SocketSchedule Timing, чтобы прослушать пакет HeartBeat клиента. Оба типа реализуют запускаемый интерфейс. Ниже приведен код для сервера:
Пакет yaolin.chat.server; импорт java.io.ioexception; import java.net.serversocket; import java.util.date; import java.util.concurrent.executorservice; import java.util.concurrent.executors; import.concurrent.schurentexececececurentexecurente.schurrentexecurente.schurrentexecurrent.shurrentexecurrent.schurrentexecr java.util.concurrent.timeUnit; import yaolin.chat.common.constantValue; импорт yaolin.chat.util.loggerutil;/*** Сервер* @author yaolin*/public class server {private serversocket server; Частный окончательный бассейн исполнителей; public server () бросает ioException {server = new Serversocket (constantValue.server_port); pool = executors.newfixedthreadpool (constantValue.max_pool_size); } public void start () {try {warededExeCutorService grade = executors.newschedledThreadpool (1); // Смотреть собаку. Исключение?? grade.scheduleatfixedrate (new socketschedule (), 10, constantvalue.time_out, timeunit.seconds); while (true) {pool.execute (new SocketDispatcher (server.accep ())); Loggerutil.info ("принять клиента в" + new Date ()); }} catch (ioException e) {pool.shutdown (); }} public static void main (string [] args) {try {new Server (). start (); } catch (ioException e) {loggerUtil.error ("Запуск сервера не удастся! ->" + e.getMessage (), e); }}}[SocketDispatcher.java]
Сервер - это просто вход на сервер и командный центр. SocketDispatcher - это командный центр сервера. Он распространяет различные запросы типов сообщений от клиента, позволяя различным носкам для обработки соответствующих запросов сообщений. Здесь взаимодействие сообщения между сервером и клиентом использует данные JSON. Все классы сообщений наследуют BaseMessage, поэтому полученные данные преобразуются в тип BaseMessage, а затем тип оценивается. (Модуль типа данных относится к общему модулю). Здесь следует упомянуть, что, когда тип сообщения является типом файла, он будет спать для настройки интервала выполнения, так что FileHandler может успеть прочитать и отправлять потоковой поток файла указанному клиенту, без немедленного ввода следующего цикла, чтобы судить тип сообщения (дизайн может быть немного проблематичным здесь, но сделайте это на время). Ниже приведен код SocketDispatcher:
/** * socketDispatcher * * @author yaolin */public class socketDispatcher реализует runnable {private final Socket Socket; public socketDispatcher (сокет сокета) {this.socket = socket; } @Override public void run () {if (socket! = Null) {while (! Socket.isclosed ()) {try {inputStream IS = socket.getInputStream (); String 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 ("eceat [" + sb.tostring () + "] at" + new Date ()); BaseMessage message = json.parseobject (sb.tostring (), basemessage.class); Switch (message.getType ()) {case messageType.alive: handlerfactory.gethandler (messageType.alive) .handle (сокет, sb.toString ()); перерыв; case messageType.chat: HandlerFactory.gethandler (messagetype.chat) .handle (сокет, sb.tostring ()); перерыв; case messageType.file: HandlerFactory.gethandler (messageType.file) .handle (Socket, sb.toString ()); перерыв; case messageType.file: HandlerFactory.gethandler (messageType.file) .handle (Socket, sb.toString ()); Loggerutil.trach ("sever: пауза для получения файла"); Thread.sleep (constantValue.message_period); перерыв; case messageType.login: handlerfactory.gethandler (messagetype.login) .handle (сокет, sb.tostring ()); перерыв; case messageType.logout: break; case messageType.register: HandlerFactory.gethandler (messageType.register) .handle (Socket, sb.toString ()); перерыв; }} else {thread.sleep (constantValue.message_period); }} catch (Exception e) {// поймать все исключения обработчика loggerUtil.error ("Ошибка SocketDispatcher!" + e.getMessage (), e); }}}}}}[Socketschedule.java]
Другим классом (компонентом), который напрямую связан с сервером, является Socketschedule. Socketschedule в основном отвечает за обнаружение того, превышает ли последнее время взаимодействия между клиентом и сервером максимально допустимое время в конфигурации системы. Если он превышает, клиентский сокет будет удален с сервера, в противном случае последнее время взаимодействия между клиентом и сервером будет обновлено. Ниже приведены конкретные реализации:
/** * Удалить сокет из Socketholder, если LastaliveTime> time_out * @author yaolin * */public class socketschedule реализует {@override public void run () {for (string key: socketholder.keyset ()) {socketwrapper rupper = socketholder.get (key); if (warper! = null && warpper.getLastaLiveTime ()! = null) {if (((new date (). getTime ()) - warpper.getLastiVetime (). getTime ()) / 1000)> constantValue.time_out) {// Удалить сокет, если timeout ocketholder.Remove (key); }}}}}}[Socketholder.java, socketwrapper.java]
Из приведенного выше кода мы видим, что SocketSchedule#run () - это просто простое суждение времени. На самом деле значимо, это Socketholder и SocketWrapper. SocketWrapper добавляет оболочку в розетку. Socketholder хранит всех клиентов, которые взаимодействуют с сервером в текущее достоверное время. Socketholder уникально идентифицируется клиентом (имя пользователя здесь). В качестве ключа, розетка, в которой находится клиент, хранится как пара значений ключей. Логика обработки Socketholder#flushClientStatus () используется для уведомления других клиентов о статусе онлайн/офлайн текущего клиента. Конкретная реализация этих двух классов приведена ниже:
/** * Переверните, SocketSchedule Удалите сокет, если LastaliVetime> time_out * @author yaolin * */public class socketwrapper {private сокет; частная дата LastaliveTime; // Полный конструктор Public SocketWrapper (сокет, дата LastAliveTime) {this.socket = socket; это. } public stocket getSocket () {return Socket; } public void setSocket (сокет сокета) {this.socket = socket; } public Date getLastAliveTime () {return LastaliVetime; } public void setLastAliveTime (дата LastAliveTime) {this.lastaliveTime = LastaliVetime; }} /** * socketholder * @author yaolin */public class socketholder {private static countrentmap <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 (String Key, значение SocketWrapper) {listSocketWrap.put (key, value); FlushClientStatus (Key, True); } public Static SocketWrapper Remove (String Key) {flushClientStatus (ключ, false); return listsocketWrap.remove (key); } public static void clear () {listSocketWrap.clear (); } /** * <pre> Контент: {username: "", flag: false} < /pre> * @param flag true: put, false: удалить; */ private static void flushClientStatus (String Key, Boolean Flag) {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)) {// не отправлять в Self rm.setto (tokey); SocketWrapper wrap = listSocketWrap.get (tokey); if (wrap! = null) {sendhelper.send (wrap.getSocket (), rm); }}}}}}[Sockethandler.java, Handlerfactory.java, offehandhandlerimpl.java]
SocketDispatcher позволяет различным носкам для обработки соответствующих запросов сообщений. Дизайн Sockethandler на самом деле является простым набором заводских компонентов (returnhandler временно передается Sendhelper, но он пока не используется. Полная классная диаграмма заключается в следующем:
Код для этого раздела приведен ниже. Чтобы сократить пространство, собирается весь код, реализованный Handler.
/** * Sockethandler * @author yaolin */public interface sockethandler {/** * обрабатывать клиентский сокет */ /** * sockethandlerfactory * @author yaolin */public class handlerfactory {// не может создать экземпляр private handlerfactory () {} public static sockethandler gethandler (int type) {switch (type) {case messageType.alive: // обычно используйте return newAlfyHandler (); case messageType.chat: вернуть новый Chathandler (); case messageType.login: return new LoginHandler (); // case messageType.return: // return new returnhandler (); case messagetype.logout: вернуть новый logouthandler (); case messageType.register: return new RegisterHandler (); case messageType.file: return new FileHandler (); } return null; // nullpointexception}} /** * alivesockethandler * @author yaolin */public Class AliveHandler реализует Sockethandler {/** * @return null */@override public object (клиент сокета, данные объекта) {if (data! if (stringUtil.isnotempty (message.getfrom ())) {socketWrapper warper = socketholder.get (message.getfrom ()); if (warper! = null) {warper.setLastaliveTime (new Date ()); // сохранить сокет ... socketholder.put (message.getfrom (), warpper); }}} return null; }} /** * chathandler * * @author yaolin */public class chathandler реализует Sockethandler {@override public heckember (клиент сокета, данные объекта) {if (data! if (stringUtil.isnotempty (message.getfrom ()) && stringutil.isnotempty (message.getto ())) {// существует и отправить if (socketholder.keyset (). Содержит (message.getfrom ())) {string ansual = message.getfrom (); Message.Setowner (владелец); // Владелец будет отображаться, если (constantValue.to_all.equals (message.getto ())) {// one-to-all // to_all вкладка будет выбрать; message.setfrom (constantValue.to_all); для (String Key: socketholder.keyset ()) {// также отправить в Self SocketWrapper wrapper = socketholder.get (key); if (warper! = null) {sendhelper.send (warper.getSocket (), message); }}} else {// one-to-one socketWrapper warper = socketholder.get (message.getto ()); if (warper! = null) {// anker = from sendhelper.send (warper.getsocket (), message); // также отправить на Self // вкладку будет выбран; message.setfrom (message.getto ()). setto (владелец); Sendhelper.send (клиент, сообщение); }}}}} return null; }} public class fileHandler реализует Sockethandler {@Override public объект Handle (клиент сокета, данные объекта) {if (client! = null) {falemessage message = json.parseobject (data.toString (), filemessage.class); if (stringutil.isnotempty (message.getfrom ()) && stringutil.isnotempty (message.getto ())) {// существует и отправить if (socketholder.keyset (). Socketholder.get (message.getto ()); if (warper! = null) {sendhelper.send (warper.getSocket (), message); try {if (client! = null && warper.getSocket ()! = null && message.getSize ()> 0) {inputStream IS = client.getInputStream (); OutputStream OS = warper.getSocket (). GetOutputStream (); int total = 0; while (! client.isclosed () &&! int len = -1; while (is.available ()> 0 && (len = is.read (buff))! = -1) {os.write (buff, 0, len); Всего += Лен; Loggerutil.debug ("Отправить бафф [" + len + "]"); } os.flush (); if (total> = message.getSize ()) {loggerutil.info ("Send Buff [OK]"); перерыв; }}} // после отправки файла // Отправить успешно returnmessage result = new returnmessage (). SetKey (key.tip) .setsuccess (true) .setContent (i18n.info_file_send_successely); result.setfrom (message.getto ()). setto (message.getfrom ()) .setowner (constantvalue.server_name); Sendhelper.send (клиент, результат); // получение успешного result.setContent (i18n.info_file_receive_successfy) .setFrom (message.getFrom ()) .setto (message.getto ()); Sendhelper.send (warper.getsocket (), result); }} catch (Exception e) {loggerUtil.error ("Ошибка файла обработки!" + e.getMessage (), e); }}}}}}} return null; }} /** * LoginHandler * * @author yaolin * */public class loginhandler реализует Sockethandler {private usrservice usrservice = new usrservice (); @Override public object handle (клиент сокета, данные объекта) {returnmessage result = new returnmessage (); result.setSuccess (false); 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 (message.getUsername ()); } else {result.setMessage (i18n.info_login_empty_data); } // после входа в систему result.setkey (key.login); if (result.issuccess ()) {// удерживать сокет socketholder.put (result.getto (), new SocketWrapper (Client, New Date ())); } SendHelper.send (клиент, результат); if (result.issuccess ()) {// Отправить список пользователя clientlisluserdto dto = new ClientLidSerDto (); DTO.SetStureSer (socketholder.keyset ()); result.setContent (DTO) .setKey (key.listuser); Sendhelper.send (клиент, результат); }} return null; }} открытый класс Logouthandler реализует sockethandler {@override public heart object (клиент сокета, данные объекта) {if (data! = null) {logoutmessage message = json.parseobject (data.tostring (), logoutmessage.class); if (message! = null && stringutil.isnotempty (message.getfrom ())) {socketWrapper warper = socketholder.get (message.getfrom ()); Сокет сокет = warper.getSocket (); if (socket! = null) {try {socket.close (); сокет = null; } catch (исключение игнорировать) {}} socketholder.remove (message.getfrom ()); }} return null; }} Общественный класс Registerhandler реализует Sockethandler {private usrservice usrservice = new usrservice (); @Override public object handle (клиент сокета, данные объекта) {returnmessage result = new returnmessage (); result.setSuccess (false) .setFrom (constantValue.server_name); if (data! = null) {RegisterMessage message = json.parseObject (data.tostring (), registermessage.class); if (stringutil.isnotempty (message.getusername ()) && stringutil.isnotempty (message.getpassword ())) {if (usrservice.register (message.getusername (), message.getpassword ())! = null) {result.setSuccess (true) .setContent (i18n.inf_register_sere_register_sere_register } 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 ()); } // после регистра result.setkey (key.register); Sendhelper.send (клиент, результат); } return null; }} /** * Use SendHelper to send ReturnMessage, * @see yaolin.chat.server.SocketDispatcher#run() * @author yaolin */@Deprecated public class ReturnHandler implements SocketHandler { /** * @param data ReturnMessage */ @Override public Object handle(Socket client, Object data) { if (data != null) { ReturnMessage message = (Returnmessage) данные; 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; }}Пользовательский бизнес:
В дополнение к гнездам, сервер также имеет небольшой конкретный бизнес, то есть регистрация пользователей, вход в систему и т. Д. Здесь мы просто перечислим два класса USR и USRService. Эти предприятия не были реализованы на данный момент. Я не намерен вводить структуру ORM в этой программе, поэтому я написал набор dbutil (чтобы улучшить) и опубликовал ее здесь.
Здесь выполняется только простая проверка, и она не сохраняется для хранения ее в БД. Вот USR и USRService:
открытый класс USR {Private Long ID; частное имя пользователя; Private String Password; 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 пароль; } public void setPassword (String password) {this.password = password; }} /** * // todo * @see yaolin.chat.server.usr.repository.usrrepository * @author yaolin * */public class usrservice {// todo db частная статическая карта <String, usr> db = new hashmap <string, usr> (); public usr Register (String username, String password) {if (stringutil.isempty (username) || stringutil.isempty (пароль)) {return null; } if (db.containskey (username)) {return null; // существовать; } Usr usr = new usr (); usr.setusername (имя пользователя); usr.setpassword (md5util.getmd5code (пароль)); DB.Put (имя пользователя, USR); вернуть USR; } public usr login (string username, string password) {if (stringutil.isempty (username) || stringutil.isempty (пароль)) {return null; } if (db.containskey (username)) {usr usr = db.get (username); if (md5util.getmd5code (пароль) .equals (usr.getpassword ())) {return usr; }} return null; }} Вот инструмент dbutil:
/*** dbutils // Todo должен быть скорректирован и оптимизирован !! * @author yaolin */public class dbutil {// Сделать подключение, используемое неоднократно частным статическим окончательным списком <necection> cache = new LinkedList <neplysity> (); частная статическая строковая URL; частный статический драйвер строки; частная статическая строковая пользователь; частная статическая строка пароль; частный статический логический отладь; static {inputStream is = dbutil.class.getResourceasStream ("/db.properties"); try {свойства p = new Properties (); P.Load (IS); url = p.getproperty ("url"); Driver = P.GetProperty ("Driver"); user = p.getProperty ("Пользователь"); пароль = p.getProperty ("пароль"); // просто для отладка try {debug = boolean.valueof (p.getproperty ("Debug")); } catch (исключение игнорировать) {debug = false; }} catch (Exception e) {бросить новое runtimeexception (e); } наконец {if (is! = null) {try {as.close (); is = null; } catch (исключение игнорировать) {}}}} публичное синхронизированное статическое соединение getConnection () {if (cache.isempty ()) {cache.add (makeConnection ()); } Соединение conn = null; int i = 0; try {do {conn = cache.remove (i); } while (conn! = null && conn.isclosed () && i <cache.size ()); } catch (исключение игнорировать) {} try {if (conn == null || conn.isclosed ()) {cache.add (makeConnection ()); conn = cache.remove (0); } return conn; } catch (Exception e) {бросить новое runtimeexception (e); }} public synchronized static void close (соединение соединения) {try {if (connection! = null &&! connection.isclosed ()) {if (debug) Debug ("Connection!"); cache.add (соединение); }} catch (SQLexception Ignore) {}} public Static Object Query (String SQL, ResultMapper Mapper, Object ... args) {if (Debug) Debug (SQL); Соединение conn = getConnection (); Подготовленное предприятие PS = NULL; Результат RS = NULL; Объект результат = null; try {ps = conn.preprestatement (sql); int i = 1; for (объект объекта: args) {ps.setObject (i ++, объект); } rs = ps.executequery (); result = mapper.mapper (rs); } catch (Exception e) {бросить новое runtimeexception (e); } наконец {try {if (rs! = null) {rs.close (); rs = null; } if (ps! = null) {ps.close (); ps = null; }} catch (исключение игнорировать) {}} close (conn); результат возврата; } public static int modify (String SQL, Object ... args) {if (DEBUG) DEBUG (SQL); Соединение conn = getConnection (); Подготовленное предприятие PS = NULL; int row = 0; try {ps = conn.preprestatement (sql); int i = 1; for (объект объекта: args) {ps.setObject (i ++, объект); } row = ps.executeUpdate (); } catch (Exception e) {бросить новое runtimeexception (e); } наконец {try {if (ps! = null) {ps.close (); ps = null; }} catch (исключение игнорировать) {}} close (conn); вернуть ряд; } public static int [] batch (list <string> sqls) {if (debug) debug (sqls.tostring ()); Соединение conn = getConnection (); Утверждение stmt = null; int [] row; try {stmt = conn.createStatement (); for (string sql: sqls) {stmt.addbatch (sql); } row = stmt.executebatch (); } catch (Exception e) {бросить новое runtimeexception (e); } наконец {try {if (stmt! = null) {stmt.close (); stmt = null; }} catch (исключение игнорировать) {}} close (conn); вернуть ряд; } public static int [] pactor (string sql, подготовленная установка Setter) {if (Debug) Debug (SQL); Соединение conn = getConnection (); Подготовленное предприятие PS = NULL; int [] row; try {ps = conn.preprestatement (sql); setter.setter (ps); row = ps.executebatch (); } catch (Exception e) {бросить новое runtimeexception (e); } наконец {try {if (ps! = null) {ps.close (); ps = null; }} catch (исключение игнорировать) {}} close (conn); вернуть ряд; } частное статическое соединение makeConnection () {try {class.forname (Driver) .newinStance (); Connection conn = drivermanager.getConnection (URL, пользователь, пароль); if (debug) Debug («Создать соединение!»); вернуть Конн; } catch (Exception e) {бросить новое 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 () + "]" + "Excute Sqls:" + sqls); }} /** * PrecateStatementsetter * @author yaolin */public interface PrediveStatementsetter {public void setter (ProfectStatement PS);} /** * ResultsetMapper * @Author Yaolin */Public Interface ResultsetMapper {public Object Mapper (ResultSet rs);} Скачать исходный код: демонстрация
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.