以前に書かれた:
昨日、ソケットチャットプログラムの最初のデザインを記録しました。ブログに書くのに時間をかけました。このプログラムの全体的な設計でした。完全性のために、今日はサーバー側の設計を詳細に記録します。ホームページは、以下に示すように、ソケットチャットプログラムの一般的なデザイン図を投稿します。
機能の説明:
サーバーには2つの主要な操作があります。1つは受信クライアントのソケットをブロックして応答処理を実行することです。もう1つはクライアントのハートビートを検出することです。クライアントが一定期間ハートビートを送信しない場合は、クライアントを削除し、Serversocketを作成し、2つのスレッドプールを起動してこれらの2つのことを処理します(NewFixedThreadPool、NewScheduledThreadPool)。対応する処理クラスは、SocketDispatcherとSocketscheduleです。 SocketDispatcherは、さまざまなソケットリクエストに従って、さまざまなSockethandlersに配布されます。 SocketWrapperはシェルラッパーをソケットに追加し、ソケットの最新のインタラクション時間をSocketholderが現在サーバーと対話しているソケットコレクションを保存します。
特定の実装:
[server.java]
サーバーはサーバーの入り口です。 Serversocketはサーバーのstart()メソッドによって開始され、受信クライアントの要求をブロックし、配布のためにSocketDispatcherに引き渡します。 SocketDispatcherは、NewFixedThreadタイプのスレッドプールによって開始されます。接続の数が最大データを超えると、キューによって処理されます。 ScheduleAteAtFixEdrateは、SocketScheduleタイミングループを起動して、クライアントのハートビートパッケージを聴くために使用されます。どちらのタイプも実行可能なインターフェイスを実装します。以下はサーバーのコードです。
パッケージyaolin.chat.server; import java.io.ioexception; import java.net.serversocket; import java.util.date; Import java.util.concurrent.executorservice; Import java.util.util.concurrent.executors; Import java.util.util.concurrelent.schedureRervice java.util.concurrent.timeunit; Import yaolin.chat.common.constantValue; import yaolin.chat.util.loggerutil;/*** server* @author yaolin*/public class server {private final serversocket server;プライベート最終executorserviceプール。 public server()throws ioexception {server = new Serversocket(construmentValue.server_port); pool = executors.newfixedthreadpool(custrantValue.max_pool_size); } public void start(){try {scheduledexecutorservice schedule = executors.newscheduledthreadpool(1); //犬を見る。例外?? schedule.scheduleatfixedrate(new socketschedule()、10、custrantValue.time_out、timeunit.seconds); while(true){pool.execute(new SocketDispatcher(server.accept())); 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データを使用します。すべてのメッセージクラスはベースメッジを継承するため、受信したデータはベースメッジタイプに変換され、そのタイプが審査されます。 (データ型モジュールは共通モジュールに属します)。ここでは、メッセージタイプがファイルタイプである場合、実行間隔を構成するためにスリープされ、ファイルハンドラーが指定されたクライアントにファイルストリームを読み取り、再送信することができることに言及する必要があります。すぐに次のループを入力してメッセージタイプを判断することなく(ここでは少し問題があるかもしれませんが、これはしばらくの間これを行います)。以下は、SocketDispatcherのコードです。
/** * socketdispatcher * * @author yaolin */public class socketdispatcher runnable {private final socket socket; public socketdispatcher(socket socket){this.socket = socket; } @Override public void run(){if(socket!= null){while(!socket.isclosed()){try {inputstream is = socket.getinputStream();文字列line = null; stringbuffer sb = null; if(is.abaible()> 0){bufferedreader bufr = new BufferedReader(new inputstreamReader(is)); sb = new StringBuffer(); while(is.abailable()> 0 &&(line = bufr.readline())!= null){sb.append(line); } loggerutil.trach( "receive [" + sb.toString() + "] at" + new date()); basemessage message = json.parseobject(sb.tostring()、basemessage.class); switch(message.getType()){case messagetype.alive:handlerfactory.gethandler(messageType.alive).Handle(socket、sb.tostring());壊す; Case MessageType.Chat:HandlerFactory.Gethandler(MessageType.Chat).Handle(Socket、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:Pause fours file"); thread.sleep(custrentValue.message_period);壊す; case messageType.login:handleractory.gethandler(messageType.login).handle(socket、sb.tostring());壊す; case messagetype.logout:break; Case MessageType.register:HandlerFactory.Gethandler(MessageType.register).Handle(Socket、sb.tostring());壊す; }} else {thread.sleep(custrentValue.message_period); }} catch(Exception E){//すべてのハンドラー例外をキャッチしてくださいloggerutil.error( "socketdispatcherエラー!" + e.getmessage()、e); }}}}}}[socketschedule.java]
サーバーに直接関連する別のクラス(コンポーネント)はSocketScheduleです。 SocketScheduleは、主に、クライアントとサーバー間の最新の相互作用時間がシステム構成の最大許容時間を超えるかどうかを検出する責任があります。それを超えると、クライアントソケットがサーバーから削除されます。そうしないと、クライアントとサーバーの間の最新の相互作用時間が更新されます。以下は特定の実装です。
/** * lastAlivetime> time_outの場合はソケットからソケットを取り外します * @author yaolin * */public class socketschedule runnable {@override public void run(){for(string key:socketholder.keyset()){sockethrapper wrapper = socketholder.get(key); if(wrapper!= null && wrapper.getlastalivetime()!= null){if((new date()。gettime()) - wrapper.getlastalivetime()。 }}}}}}[socketholder.java、socketwrapper.java]
上記のコードから、socketschedule#run()は時間の単純な判断であることがわかります。本当に意味のあるのは、ソックソルダーとソケットワッパーです。 SocketWrapperは、シェルラッパーをソケットに追加します。 Socketholderは、現在の有効な時間中にサーバーと対話するすべてのクライアントを保存します。 Socketholderはクライアントによって独自に識別されます(ユーザー名はこちら)。キーとして、クライアントが配置されているソケットは、値のキー価値ペアとして保存されます。 Socketholder#flushclientStatus()の処理ロジックは、現在のクライアントのオンライン/オフラインステータスを他のクライアントに通知するために使用されます。これら2つのクラスの特定の実装を以下に示します。
/** *ラップソケット、socketschedule lastalivetimeの場合はソケットを削除> time_out * @author yaolin * */public class socketwrapper {private socket socket; Private Date lastAlivetime; //フルコンストラクターパブリックソケットワッパー(ソケットソケット、日付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(string key、socketwrapper value){listsocketwrap.put(key、value); flushclientstatus(key、true); } public static socketwrapper remove(string key){flushclientstatus(key、false); Return listsocketwrap.remove(key); } public static void clear(){listsocketwrap.clear(); } /** * <pre> content:{username: ""、fals: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(custrantValue.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、handleractory.java、otherhandlerimpl.java]
SocketDispatcherを使用すると、さまざまなソックスンドラーが対応するメッセージリクエストを処理できます。 Sockethandlerの設計は、実際には工場のコンポーネントの単純なセットです(ReturnhandlerはSendhelperによって一時的に送信されますが、当面は使用されていません。 @deprecatedで、まだここに与えられています)。完全なクラス図は次のとおりです。
このセクションのコードを以下に示します。スペースを減らすために、ハンドラーによって実装されたすべてのコードが収集されます。
/** * sockethandler * @author yaolin */public interface sockethandler {/** *ハンドルクライアントソケット */パブリックオブジェクトハンドル(ソケットクライアント、オブジェクトデータ);} /** * sockethandlerfactory * @author yaolin */public class handlerfactory {// Instance private handleractory(){} public static sockethler gethandler(int type){switch(type){case messageType.Alive://は通常、新しいbealivehandler(); case messageType.chat:new Chathandler()を返します。 case messageType.login:return new loginhandler(); // case messageType.return:// return new ReturnHandler(); case messageType.logout:new logouthandler()を返します。 case messageType.register:return new RegisterHandler(); case messageType.file:new fileHandler()を返します。 } nullを返します。 // nullpointexception}} /** * Alivesockethandler * @Author Yaolin */Public Class AliveHandlerはSockethandler {/** * @return null */@Override public Objectハンドル(ソケットクライアント、オブジェクトデータ){(data!= null){(data!= null){basemessage message = json.parseobject(data.tostring()、basemessage.class); if(stringutil.isnotempty(message.getfrom())){socketwrapper wrapper = socketholder.get(message.getfrom()); if(wrapper!= null){wrapper.setlastalivetime(new date()); // socket ... socketholder.put(message.getfrom()、wrapper); }}} nullを返します。 }} /** * chathandler * * @author yaolin */public class chathandlerは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())){//存在&send if(socketholder.keyset()。 message.setowner(所有者); //所有者は表示されます(constantValue.to_all.equals(message.getto())){// one-all // to_allタブがselect; message.setfrom(custrentValue.to_all); for(string key:socketholder.keyset()){// self socketwrapper wrapper = socketholder.get(key); if(wrapper!= null){sendhelper.send(wrapper.getSocket()、message); }}} else {// One-to-One SocketWrapper wrapper = socketholder.get(message.getto()); if(wrapper!= null){// owner = from sendhelper.send(wrapper.getsocket()、message); //また、selfに送信してください//タブに選択されます。 message.setfrom(message.getto())。setto(所有者); sendhelper.send(クライアント、メッセージ); }}}}} nullを返します。 }} public class filehandlerは、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())){//存在&send if(socketholder.keyset()。 socketholder.get(message.getto()); if(wrapper!= null){sendhelper.send(wrapper.getSocket()、message); try {if(client!= null && wrapper.getSocket()!= null && message.getSize()> 0){inputstream is = client.getInputStream(); outputStream os = wrapper.getSocket()。getOutputStream(); int total = 0; while(!client.isclosed()&&!wrapper.getSocket()。isclosed()){if(is.abailable()> 0){byte [] buff = new byte [custranceValue.buff_size]; int len = -1; while(is.abailable()> 0 &&(len = is.read(buff))!= -1){os.write(buff、0、len);合計 += len; loggerutil.debug( "send buff [" + 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_succescessely); result.setfrom(message.getto())。setto(message.getfrom()).setowner(custrantValue.server_name); sendhelper.send(client、result); // resulcefuls.setContent(i18n.info_file_receive_successfull).setfrom(message.getfrom()).setto(message.getto()); sendhelper.send(wrapper.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パブリックオブジェクトハンドル(ソケットクライアント、オブジェクトデータ){returnMessage result = new returnMessage(); result.setsuccess(false); if(data!= null){loglmessage 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(construmentValue.server_name).setto(message.getUsername()); } else {result.setMessage(i18n.info_login_empty_data); } // login result.setKey(key.login); if(result.issuccess()){// socket socketholder.put(result.getto()、new SocketWrapper(client、new date())); } sendhelper.send(client、result); if(result.issuccess()){// send list user clientlistuserdto dto = new ClientListuserdto(); dto.setlistuser(socketholder.keyset()); result.setContent(dto).setkey(key.listuser); sendhelper.send(client、result); }} nullを返します。 }}パブリッククラスのlogouthandlerは、sockethandler {@override public object handle(socket client、object data){if(data!= null){logoutmessage message = json.parseobject(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(例外無視){}} socketholder.remove(message.getfrom()); }} nullを返します。 }} Public Class RegisterHandlerはSockethandlerを実装しています{private usrservice usrservice = new usrservice(); @Overrideパブリックオブジェクトハンドル(ソケットクライアント、オブジェクトデータ){returnMessage result = new returnMessage(); result.setsuccess(false).setfrom(custrantValue.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)。 } 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()); } // register result.setKey(key.register); sendhelper.send(client、result); } nullを返します。 }} /** * sendhelperを使用してreturnmessageを送信します。 =(returnMessage)データ; if(stringutil.isnotempty(message.getfrom())&& stringutil.isnotempty(message.getto())){socketwrapper wrap = socketholder.get(message.getto()); if(wrap!= null){sendhelper.send(wrap.getSocket()、message); }}} nullを返します。 }}ユーザービジネス:
ソケットに加えて、サーバーには少し特定のビジネス、つまりユーザー登録、ログインなどもあります。ここでは、USRとUSRServiceの2つのクラスをリストするだけです。これらのビジネスは当面の間実装されていません。このプログラムでORMフレームワークを導入するつもりはないので、dbutilのセット(改善する)を書き、ここに投稿しました。
ここでは簡単な検証のみが実行され、DBに保存することは持続しません。ここにusrとusrserviceがあります:
パブリッククラスusr {private long id;プライベート文字列ユーザー名;プライベート文字列パスワード。 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(){パスワードを返します。 } public void setPassword(string password){this.password = password; }} /** * // todo * @see yaolin.chat.server.usr.repository.usrrepository * @author yaolin * */public class usrservice {// todo db private static map <string、usr> db = new hashmap <string、usr>(); public usr register(string username、string password){if(stringutil.isempty(username)|| stringutil.isempty(password)){return null; } if(db.containskey(username)){return null; // 存在する; } usr usr = new usr(); usr.setusername(username); usr.setpassword(md5util.getmd5code(パスワード)); db.put(username、usr); usrを返します。 } public usr login(string username、string password){if(stringutil.isempty(username)|| stringutil.isempty(password)){return null; } if(db.containskey(username)){usr usr = db.get(username); if(md5util.getmd5code(password).equals(usr.getpassword())){return usr; }} nullを返します。 }}これがdbutilツールです:
/*** dbutils // todoを調整して最適化する必要があります!! * @author yaolin */public class dbutil {//接続を使用して繰り返しプライベートな静的最終リスト<connection> cache = new linkedlist <connection>();プライベート静的文字列URL;プライベート静的文字列ドライバー。プライベート静的文字列ユーザー。プライベート静的文字列パスワード。プライベート静的ブールデバッグ。 static {inputstream is = dbutil.class.getResourceasStream( "/db.properties"); try {Properties P = new Properties(); p.load(is); url = p.getProperty( "url");ドライバー= p.getProperty( "driver"); user = p.getProperty( "user");パスワード= p.getProperty( "password"); // DEBUGのためだけにtry {debug = boolean.valueof(p.getProperty( "debug")); } catch(例外無視){debug = false; }} catch(Exception e){throw new runtimeException(e); }最後に{if(is!= null){try {is.close(); IS = null; } catch(Exception Ingrore){}}}} public synchronized static connection 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(connection connection){try {if(connection!= null &&!connection.isclosed()){if(debug)debug( "release connection!"); cache.add(接続); }} catch(sqlexception Ingrore){}} public static object query(string sql、resultsetmapper mapper、object ... args){if(debug)debug(sql);接続conn = getConnection(); represedStatement PS = null;結果rs = null;オブジェクト結果= null; try {ps = conn.preparestatement(sql); int i = 1; for(オブジェクトオブジェクト:args){ps.setObject(i ++、object); } 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(); represedStatement PS = null; int row = 0; try {ps = conn.preparestatement(sql); int i = 1; for(オブジェクトオブジェクト:args){ps.setObject(i ++、object); } row = ps.executeUpdate(); } catch(Exception e){新しいruntimeException(e); }最後に{try {if(ps!= null){ps.close(); ps = null; }} catch(例外無視){}} close(conn); return行; } 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); return行; } public static int [] batch(string sql、preatedstatementetter setter){if(debug)debug(sql);接続conn = getConnection(); represedStatement PS = null; int [] row; try {ps = conn.preparestatement(sql); setter.setter(ps); row = ps.executeBatch(); } catch(Exception e){新しいruntimeException(e); }最後に{try {if(ps!= null){ps.close(); ps = null; }} catch(例外無視){}} close(conn); return行; } private static connection makeconnection(){try {class.forname(driver).newinstance();接続conn = drivermanager.getConnection(url、user、password); if(debug)debug( "Create connection!"); conn; } 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 sql:" + sqls); }} /** * preatedStatementSetter * @Author Yaolin */public Interface preateStatementSetter {public void setter(preatedStatement PS);} /** * resultSetmapper * @author yaolin */public interface resultSetmapper {public object mapper(resultset rs);}ソースコードのダウンロード:デモ
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。