Ditulis sebelumnya:
Kemarin saya merekam desain awal program obrolan soket yang saya luangkan waktu untuk menulis di blog saya. Itu adalah desain keseluruhan dari program ini. Untuk kelengkapan, hari ini saya akan merekam desain sisi server secara detail. Beranda akan memposting diagram desain umum dari program obrolan soket, seperti yang ditunjukkan di bawah ini:
Deskripsi Fungsi:
Server memiliki dua operasi utama: satu adalah untuk memblokir soket klien penerima dan melakukan pemrosesan respons, dan yang lainnya adalah mendeteksi detak jantung klien. Jika klien tidak mengirim detak jantung untuk jangka waktu tertentu, lepaskan klien, buat server, dan kemudian mulai dua kumpulan utas untuk menangani dua hal ini (newfixedthreadpool, newscheduledthreadpool). Kelas pemrosesan yang sesuai adalah SocketDispatcher dan SocketsChedule. SocketDispatcher didistribusikan ke Sockethandlers yang berbeda sesuai dengan permintaan soket yang berbeda. SocketWrapper menambahkan pembungkus shell ke soket, dan merekam waktu interaksi terbaru soket dengan soket menyimpan koleksi soket yang saat ini berinteraksi dengan server.
Implementasi spesifik:
[Server.java]
Server adalah pintu masuk ke server. ServerSocket dimulai dengan metode start () server, dan kemudian memblokir permintaan klien penerima dan diserahkan ke SocketDispatcher untuk distribusi. SocketDispatcher dimulai dengan kumpulan benang tipe newfixedthread. Ketika jumlah koneksi melebihi data maksimum, itu akan diproses oleh antrian. SCRECTULEATFIXEDRATE digunakan untuk memulai loop timing socketschedule untuk mendengarkan paket detak jantung klien. Kedua tipe mengimplementasikan antarmuka runnable. Berikut ini adalah kode untuk server:
Paket Yaolin.chat.server; impor java.io.ioexception; impor java.net.serversocket; impor java.util.date; impor java.util.concurrent.executorservice; import java.util.concurrent.executors; impor java.util.util.utrentsecutorsecutorscutor; java.util.concurrent.timeunit; impor yaolin.chat.common.constantValue; impor yaolin.chat.util.loggerutil;/*** server* @author yaolin*/server kelas publik {server server final swasta; Pool Final ExecutorService Pribadi; server publik () melempar ioException {server = server baru (constantValue.server_port); pool = executors.newfixedThreadPool (constantValue.max_pool_size); } public void start () {try {scheduledExecutorService jadwal = executors.newsChedulEdThreadPool (1); // tonton anjing. Pengecualian?? jadwal. while (true) {pool.execute (SocketDispatcher baru (server.accept ())); Loggerutil.info ("Terima klien di" + tanggal baru ()); }} catch (ioException e) {pool.shutdown (); }} public static void main (string [] args) {coba {baru server (). start (); } catch (ioException e) {loggerutil.error ("Server Start gagal! ->" + e.getMessage (), e); }}}[Socketdispatcher.java]
Server hanyalah pintu masuk ke server dan pusat perintah. SocketDispatcher adalah pusat perintah server. Ini mendistribusikan berbagai jenis permintaan pesan dari klien, memungkinkan sockethandlers yang berbeda untuk memproses permintaan pesan yang sesuai. Di sini, interaksi pesan antara server dan klien menggunakan data JSON. Semua kelas pesan mewarisi Basemessage, sehingga data yang diterima dikonversi menjadi tipe Basemessage, dan kemudian jenisnya dinilai. (Modul tipe data termasuk modul umum). Harus disebutkan di sini bahwa ketika jenis pesan adalah jenis file, itu akan tidur untuk mengonfigurasi interval eksekusi, sehingga FileHandler dapat memiliki waktu untuk membaca dan mengirim ulang aliran file ke klien yang ditentukan, tanpa segera memasukkan loop berikutnya untuk menilai jenis pesan (desain mungkin agak bermasalah di sini, tetapi lakukan ini untuk saat ini). Berikut ini adalah kode SocketDispatcher:
/** * SocketDispatcher * * @Author Yaolin */kelas publik SocketDispatcher mengimplementasikan runnable {soket soket akhir pribadi; socketdispatcher publik (soket soket) {this.socket = socket; } @Override public void run () {if (socket! = Null) {while (! Socket.isclosed ()) {coba {inputStream adalah = socket.getInputStream (); Garis string = null; StringBuffer SB = null; if (is.available ()> 0) {bufferedReader bufr = new buferedReader (inputStreamReader baru (IS)); SB = StringBuffer baru (); while (is.available ()> 0 && (line = bufr.readline ())! = null) {sb.append (line); } Loggerutil.trach ("terima [" + sb.tostring () + "] di" + tanggal baru ()); Basemessage Message = json.parseObject (sb.tostring (), Basemessage.class); switch (message.getType ()) {case messageType.Alive: handlerfactory.getHandler (messageType.Alive) .handle (soket, sb.tostring ()); merusak; case messageType.chat: handlerfactory.getHandler (messageType.chat) .handle (soket, sb.tostring ()); merusak; case messageType.file: handlerfactory.getHandler (messageType.file) .handle (soket, sb.tostring ()); merusak; case messageType.file: handlerfactory.getHandler (messageType.file) .handle (soket, sb.tostring ()); LogGerutil.trach ("Sever: Jeda untuk menerima file"); Thread.sleep (constantValue.Message_period); merusak; case messageType.login: handlerfactory.getHandler (messageType.login) .handle (soket, sb.tostring ()); merusak; case messagetype.logout: break; case messageType.register: handlerfactory.getHandler (messageType.register) .handle (soket, sb.tostring ()); merusak; }} else {thread.sleep (constantValue.message_period); }} catch (Exception e) {// Tangkap semua handler Exception Loggerutil.Error ("Kesalahan SocketDispatcher!" + E.GetMessage (), e); }}}}}}[SocketSchedule.java]
Kelas lain (komponen) yang terkait langsung dengan server adalah socketschedule. SocketSchedule terutama bertanggung jawab untuk mendeteksi apakah waktu interaksi terbaru antara klien dan server melebihi waktu maksimum yang diizinkan dalam konfigurasi sistem. Jika melebihi, soket klien akan dihapus dari server, jika tidak waktu interaksi terbaru antara klien dan server akan diperbarui. Berikut ini adalah implementasi spesifik:
/** * Lepaskan soket dari socketholder jika lastalivetime> time_out * @author yaolin * */kelas publik socketschedule mengimplementasikan runnable {@Override public void run () {for (string key: socketholder.keyset ()) {socketwrapper wrapper = socketholder.get (key); if (wrapper! = null && wrapper.getLastalivetime ()! = null) {if (((tanggal baru (). GetTime ()) - wrapper.getLastalivetime (). GetTime ()) / 1000)> constantValue.time_out) {// hapus socket jika socketholder. }}}}}}[Socketholder.java, socketwrapper.java]
Dari kode di atas, kita dapat melihat bahwa SocketSchedule#run () hanyalah penilaian waktu yang sederhana. Yang benar -benar bermakna adalah socketholder dan socketwrapper. Socketwrapper menambahkan pembungkus shell ke soket. SocketHolder menyimpan semua klien yang berinteraksi dengan server selama waktu yang valid saat ini. SocketHolder diidentifikasi secara unik oleh klien (nama pengguna di sini). Sebagai kunci, soket tempat klien berada disimpan sebagai sepasang nilai nilai kunci. Logika pemrosesan SocketHolder#flushClientStatus () digunakan untuk memberi tahu klien lain tentang status online/offline dari klien saat ini. Implementasi spesifik dari kedua kelas ini diberikan di bawah ini:
/** * Bungkus soket, socketschedule lepaskan soket jika lastalivetime> time_out * @author yaolin * */socketwrapper kelas publik {soket soket pribadi; tanggal pribadi lastalivetime; // konstruktor penuh socketwrapper publik (soket soket, tanggal lastalivetime) {this.socket = socket; this.lastalivetime = lastalivetime; } public socket getSocket () {return socket; } public void setSocket (soket soket) {this.socket = socket; } tanggal publik getLastalivetime () {return lastalivetime; } public void setLastalivetime (tanggal lastalivetime) {this.lastalivetime = lastalivetime; }} /** * SocketHolder * @Author Yaolin */SocketHolder kelas publik {private static concurrentMap <string, socketwrapper> listsocketwrap = concurrenthashMap baru <string, socketwrapper> (); set statis public <string> keyset () {return listsocketwrap.keyset (); } public static socketwrapper get (tombol string) {return listsocketwrap.get (key); } public static void put (tombol string, nilai socketwrapper) {listsocketwrap.put (tombol, nilai); flushClientStatus (kunci, benar); } public static SocketWrapper lepas (tombol string) {flushClientStatus (key, false); return listsocketwrap.remove (kunci); } public static void clear () {listsocketwrap.clear (); } /** * <pr Pre> konten: {username: "", flag: false} </pri> * @param flag true: put, false: hapus; */ private static void flushClientStatus (tombol string, bendera boolean) {clientnotifyDto dto = clientNotifyDto baru (flag, key); ReturnMessage rm = returnMessage baru (). SetKey (key.notify) .setsuccess (true) .setContent (DTO); rm.setfrom (constantValue.Server_name); untuk (string toKey: listsocketwrap.keyset ()) {if (! tokey.equals (key)) {// tidak mengirim ke self rm.setto (tokey); SocketWrapper wrap = listsocketwrap.get (toKey); if (wrap! = null) {sendhelper.send (wrap.getsocket (), rm); }}}}}}[Sockethandler.java, handlerfactory.java, OtherHandlerImpl.java]
SocketDispatcher memungkinkan Sockethandlers yang berbeda untuk menangani permintaan pesan yang sesuai. Desain Sockethandler sebenarnya adalah satu set komponen pabrik sederhana (Returnhandler untuk sementara ditransmisikan oleh Sendhelper, tetapi tidak digunakan untuk saat ini. Sudah @Deprecated, dan masih diberikan di sini). Diagram kelas lengkap adalah sebagai berikut:
Kode untuk bagian ini diberikan di bawah ini. Untuk mengurangi ruang, semua kode yang diterapkan oleh Handler dikumpulkan.
/** * sockethandler * @author yaolin */antarmuka publik sockethandler {/** * menangani soket klien */pegangan objek publik (klien soket, data objek);} /** * sockethandlerfactory * @author yaolin */public class handlerfactory {// tidak dapat membuat instance private handlerfactory () {} public static sockethandler getHandler (int type) {swite (type) {case messagetype.Alive: // biasanya menggunakan return baru hidup baru (); case messagetype.chat: return new chathandler (); case messageType.login: return new LoginHandler (); // case messageType.return: // return returnHandler baru (); case messageType.logout: return new LoGouthandler (); case messageType.register: return new registingHandler (); case messageType.file: return new FileHandler (); } return null; // nullpointException}} /** * alivesockethandler * @author yaolin */kelas publik AliveHandler mengimplementasikan sockethandler {/** * @return null */@Override Publik Pegangan (klien soket, data objek) {if (data! = Null) {Basemessage Pesan = json.parseobject (data.tostring () {Basemessage = json.parseobject (data.tostring () {Basemessage = json.parseobject (data.tostring () {BASEMESSAGE = JSON.PARSEOBJECT (data.TOSRING () 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 */kelas publik Chathandler mengimplementasikan sockethandler {@Override public handle (klien soket, data objek) {if (data! = Null) {chatmessage pesan = json.parseObject (data.tostring (), chatmessage.class); if (stringutil.isnotempty (message.getfrom ()) && stringutil.isnotempty (message.getto ())) {// ada & kirim if (socketholder.keyset (). contains (message.getFrom ())) {string owner = message.getFrom (); pesan.setowner (pemilik); // pemilik akan ditampilkan jika (constantValue.to_all.equals (message.getto ())) {// tab satu-to-all // to_all akan dipilih; message.setFrom (constantValue.to_all); untuk (tombol string: socketholder.keyset ()) {// Juga kirim ke wrapper socketwrapper = socketHolder.get (key); if (wrapper! = null) {sendhelper.send (wrapper.getsocket (), pesan); }}} else {// wrapper socketwrapper satu-ke-satu = socketHolder.get (message.getto ()); if (wrapper! = null) {// pemilik = dari sendhelper.send (wrapper.getSocket (), pesan); // Kirim juga ke self // ke tab akan dipilih; message.setFrom (message.getto ()). Setto (pemilik); Sendhelper.send (klien, pesan); }}}}} return null; }} Kelas publik FileHandler mengimplementasikan sockethandler {@Override public handle (klien soket, data objek) {if (klien! = null) {filemessage pesan = json.parseObject (data.toString (), filemessage.class); if (stringutil.isnotempty (message.getFrom ()) && stringutil.isnotempty (message.getto ())) {// ada & kirim if (socketholder.keyset (). contains (message.getFrom ()) {if (! constantValue.to_all.sequals (message. SocketHolder.get (message.getto ()); if (wrapper! = null) {sendhelper.send (wrapper.getsocket (), pesan); coba {if (klien! = null && wrapper.getSocket ()! = null && message.getSize ()> 0) {inputStream adalah = client.getInputStream (); OutputStream os = wrapper.getSocket (). GetoutputStream (); int total = 0; while (! client.isclosed () &&! wrapper.getSocket (). isClosed ()) {if (is.available ()> 0) {byte [] buff = byte baru [constantValue.buff_size]; int len = -1; while (is.available ()> 0 && (len = is.read (buff))! = -1) {os.write (buff, 0, len); Total += len; Loggerutil.debug ("Kirim buff [" + len + "]"); } os.flush (); if (total> = message.getSize ()) {loggerutil.info ("Kirim buff [ok]"); merusak; }}} // Setelah mengirim file // kirim berhasil returnMessage hasil = returnMessage baru (). SetKey (key.tip) .setsuccess (true) .setContent (i18n.info_file_send_successly); result.setfrom (message.getto ()). setto (message.getFrom ()) .setOwner (constantValue.server_name); Sendhelper.send (klien, hasil); // menerima hasil yang berhasil.setContent (i18n.info_file_receive_successly) .setFrom (message.getFrom ()) .setto (message.getto ()); Sendhelper.send (wrapper.getsocket (), hasil); }} catch (exception e) {loggerutil.error ("Tangani file gagal!" + e.getMessage (), e); }}}}}}} return null; }} /** * LoginHandler * * @author yaolin * */kelas publik LoginHandler mengimplementasikan sockethandler {private usrservice usrservice = new usrservice (); @Override Objek Publik Pegangan (klien soket, data objek) {returnMessage hasil = returnMessage baru (); result.setsuccess (false); if (data! = null) {LoginMessage pesan = json.parseObject (data.toString (), loginMessage.class); if (stringutil.isnotempty (message.getUserName ()) && stringutil.isnotempty (message.getPassword ())) {if (usrservice.login (message.getUserName (), message.getpassword ())! = null) {hasil. } else {result.setMessage (i18n.info_login_error_data); } result.setFrom (constantValue.server_name) .setto (message.getUserName ()); } else {result.setMessage (i18n.info_login_empty_data); } // setelah login result.setKey (key.login); if (result.issuccess ()) {// tahan socket socketholder.put (result.getto (), socketwrapper baru (klien, tanggal baru ())); } Sendhelper.send (klien, hasil); if (result.issuccess ()) {// kirim daftar pengguna clientlistuserdto dto = clientlistuserDto baru (); dto.setListuser (socketholder.keyset ()); result.setContent (DTO) .setkey (key.listuser); Sendhelper.send (klien, hasil); }} return null; }} Logouthandler kelas publik mengimplementasikan Sockethandler {@Override Public Handle (Socket Client, Object Data) {if (Data! = NULL) {LOGOUTMESSAGE Pesan = json.parseObject (data.toString (), logoutMessage.class); if (message! = null && stringutil.isnotempty (message.getFrom ())) {socketwrapper wrapper = socketHolder.get (message.getFrom ()); Soket soket = wrapper.getSocket (); if (socket! = null) {coba {socket.close (); soket = null; } catch (pengecualian abaikan) {}} socketholder.remove (message.getFrom ()); }} return null; }} kelas publik Registerhandler mengimplementasikan sockethandler {private usrservice usrservice = new usrservice (); @Override Objek Publik Pegangan (klien soket, data objek) {returnMessage hasil = returnMessage baru (); hasil. 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 (), messagepassword ())! = null) {result.setsuccess (true) .setcword ())! = null) {hasil. } 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 ()); } // Setelah register result.setKey (key.register); Sendhelper.send (klien, hasil); } return null; }} /** * Gunakan sendhelper untuk mengirim returnMessage, * @Lihat yaolin.chat.server.socketdispatcher#run () * @author yaolin */ @class public returnHandler {/** @param data nol {/** @param, data public {/** @param, klien kembali {/** @param, Data (returnMessage); if (stringutil.isnotempty (message.getFrom ()) && stringutil.isnotempty (message.getto ())) {socketwrapper wrap = socketHolder.get (message.getto ()); if (wrap! = null) {sendhelper.send (wrap.getsocket (), pesan); }}} return null; }}Bisnis Pengguna:
Selain soket, server juga memiliki sedikit bisnis spesifik, yaitu, pendaftaran pengguna, login, dll. Di sini kami cukup mencantumkan dua kelas USR dan USRService. Bisnis ini belum diterapkan untuk saat ini. Saya tidak bermaksud memperkenalkan kerangka kerja ORM dalam program ini, jadi saya menulis satu set dbutil (untuk ditingkatkan) dan mempostingnya di sini.
Hanya verifikasi sederhana yang dilakukan di sini, dan tidak bertahan untuk menyimpannya di DB. Inilah USR dan USRService:
kelas publik usr {private long id; nama pengguna string pribadi; kata sandi string pribadi; publik 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 kata sandi; } public void setPassword (kata sandi string) {this.password = kata sandi; }} /** * // TODO * @See Yaolin.chat.server.usr.repository.usrrepository * @Author Yaolin * */Kelas Publik USRService {// TODO DB Private Static Map <String, USR> DB = HASHMAP baru <string, USR> (); PUBLIK USR REGISTER (String UserName, String Password) {if (Stringutil.isempty (username) || stringutil.isempty (kata sandi)) {return null; } if (db.containskey (nama pengguna)) {return null; // ada; } USR USR = USR baru (); usr.setusername (nama pengguna); usr.setpassword (md5util.getMd5code (kata sandi)); db.put (nama pengguna, usr); mengembalikan usr; } PUBLIK USR LOGIN (String username, string password) {if (stringutil.isempty (username) || stringutil.isempty (kata sandi)) {return null; } if (db.containskey (nama pengguna)) {usr usr = db.get (nama pengguna); if (md5util.getMd5code (kata sandi) .equals (usr.getPassword ())) {return usr; }} return null; }} Ini alat dbutil:
/*** dbutils // todo perlu disesuaikan & dioptimalkan !! * @Author yaolin */kelas publik dbutil {// membuat koneksi yang digunakan berulang kali daftar akhir statis privat <nection> cache = new LinkedList <nection> (); URL string statis pribadi; driver string statis pribadi; Pengguna String Statis Pribadi; kata sandi string statis pribadi; debug boolean statis pribadi; static {inputStream adalah = dbutil.class.getResourCeasStream ("/db.properties"); coba {properti p = properti baru (); p.load (IS); url = p.getProperty ("url"); driver = p.getProperty ("driver"); user = p.getProperty ("user"); kata sandi = p.getProperty ("kata sandi"); // Just for debug coba {debug = boolean.valueof (p.getProperty ("debug")); } catch (pengecualian abaikan) {debug = false; }} catch (Exception e) {lempar runtimeException baru (e); } akhirnya {if (is! = null) {coba {isClose (); IS = NULL; } catch (pengecualian abaikan) {}}}} koneksi statis sinkronisasi publik getConnection () {if (cache.isempty ()) {cache.add (makeConnection ()); } Koneksi conn = null; int i = 0; coba {do {conn = cache.remove (i); } while (conn! = null && conn.isclosed () && i <cache.size ()); } catch (pengecualian abaikan) {} coba {if (conn == null || conn.isclosed ()) {cache.add (makeConnection ()); conn = cache.remove (0); } return conn; } catch (Exception e) {lempar runtimeException baru (e); }} public static void tutup publik (koneksi koneksi) {coba {if (koneksi! = null &&! connection.isclosed ()) {if (debug) debug ("Release Connection!"); cache.add (koneksi); }} catch (sqlexception abaikan) {}} kueri objek statis public (string sql, hasil mappermapper, objek ... args) {if (debug) debug (sql); Koneksi conn = getConnection (); Disiapkan ps = null; Hasil rs = null; Hasil Objek = NULL; coba {ps = conn.prepareStatement (sql); int i = 1; untuk (objek objek: args) {ps.setObject (i ++, objek); } rs = ps.executeQuery (); hasil = mapper.mapper (rs); } catch (Exception e) {lempar runtimeException baru (e); } akhirnya {coba {if (rs! = null) {rs.close (); rs = null; } if (ps! = null) {ps.close (); ps = null; }} catch (pengecualian abaikan) {}} tutup (conn); hasil pengembalian; } public static int modify (string sql, objek ... args) {if (debug) debug (sql); Koneksi conn = getConnection (); Disiapkan ps = null; int baris = 0; coba {ps = conn.prepareStatement (sql); int i = 1; untuk (objek objek: args) {ps.setObject (i ++, objek); } row = ps.executeUpdate (); } catch (Exception e) {lempar runtimeException baru (e); } akhirnya {coba {if (ps! = null) {ps.close (); ps = null; }} catch (pengecualian abaikan) {}} tutup (conn); Baris pengembalian; } public static int [] batch (Daftar <String> sqls) {if (debug) debug (sqls.toString ()); Koneksi conn = getConnection (); Pernyataan stmt = null; int [] baris; coba {stmt = conn.createStatement (); untuk (string sql: sqls) {stmt.addbatch (sql); } row = stmt.executeBatch (); } catch (Exception e) {lempar runtimeException baru (e); } akhirnya {coba {if (stmt! = null) {stmt.close (); stmt = null; }} catch (pengecualian abaikan) {}} tutup (conn); Baris pengembalian; } public static int [] batch (String SQL, PreparedStateMenSetter setter) {if (debug) debug (sql); Koneksi conn = getConnection (); Disiapkan ps = null; int [] baris; coba {ps = conn.prepareStatement (sql); setter.setter (ps); baris = ps.executebatch (); } catch (Exception e) {lempar runtimeException baru (e); } akhirnya {coba {if (ps! = null) {ps.close (); ps = null; }} catch (pengecualian abaikan) {}} tutup (conn); Baris pengembalian; } koneksi statis pribadi makeConnection () {coba {class.forname (driver) .newInstance (); Koneksi conn = driverManager.getConnection (url, pengguna, kata sandi); if (debug) debug ("Buat koneksi!"); return conn; } catch (Exception e) {lempar runtimeException baru (e); }} private static void debug (string SQLS) {SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-mm-dd hh: mm: ss"); System.out.println (sdf.format (date baru ()) + "debug" + thread.currentThread (). GetId () + "--- [" + thread.currentThread (). GetName () + "]" + "Excute SQLS:" + SQLS); }} /** * Persiapan Persiapan * @Author Yaolin */Antarmuka Publik Persiapan Persiapan {setter public void (disiapkan PS);} /** * resultsetmapper * @author yaolin */antarmuka publik resultsetmapper {public objek mapper (hasil rsetet);} Kode Sumber Unduh: Demo
Di atas adalah semua konten artikel ini. Saya berharap ini akan membantu untuk pembelajaran semua orang dan saya harap semua orang akan lebih mendukung wulin.com.