Membina sistem sembang Internet

Anda mungkin pernah melihat salah satu dari banyak sistem sembang berbasis Java yang muncul di Web. Setelah membaca artikel ini, anda akan memahami cara kerjanya - dan mengetahui cara membina sistem sembang ringkas anda sendiri.

Contoh ringkas sistem pelanggan / pelayan ini bertujuan untuk menunjukkan cara membuat aplikasi hanya menggunakan aliran yang tersedia dalam API standard. Sembang menggunakan soket TCP / IP untuk berkomunikasi, dan dapat disisipkan dengan mudah di laman Web. Sebagai rujukan, kami menyediakan bar sisi yang menerangkan komponen pengaturcaraan rangkaian Java yang relevan dengan aplikasi ini. Sekiranya anda masih mencapai kepantasan, lihat bar sisi terlebih dahulu. Sekiranya anda sudah mahir di Java, anda boleh melompat masuk dan hanya merujuk ke bar sisi untuk rujukan.

Membina pelanggan sembang

Kami mulakan dengan pelanggan sembang grafik sederhana. Ia memerlukan dua parameter baris perintah - nama pelayan dan nombor port untuk disambungkan. Ia membuat sambungan soket dan kemudian membuka tetingkap dengan kawasan keluaran yang besar dan kawasan input kecil.

Antara muka ChatClient

Setelah pengguna memasukkan teks ke kawasan input dan menekan Return, teks dihantar ke pelayan. Pelayan mengembalikan semua yang dihantar oleh pelanggan. Pelanggan menunjukkan semua yang diterima dari pelayan di kawasan keluaran. Apabila banyak pelanggan menyambung ke satu pelayan, kami mempunyai sistem sembang yang mudah.

Pelanggan Sembang Kelas

Kelas ini menerapkan klien sembang, seperti yang dijelaskan. Ini melibatkan penyediaan antara muka pengguna asas, menangani interaksi pengguna, dan menerima mesej dari pelayan.

import java.net. *; import java.io. *; import java.awt. *; kelas awam ChatClient memperluas pelaksanaan Frame Runnable {// public ChatClient (tajuk String, InputStream i, OutputStream o) ... // public void run () ... // public boolean handleEvent (Event e) ... // public statik kekosongan utama (String args []) melemparkan IOException ...}

The ChatClientkelas memanjangkan Frame; ini khas untuk aplikasi grafik. Kami melaksanakan Runnableantara muka sehingga kita dapat memulakan Threadyang menerima mesej dari pelayan. Konstruktor melakukan penyiapan asas GUI, run()kaedah menerima mesej dari pelayan, handleEvent()kaedah menangani interaksi pengguna, dan main()kaedah melakukan sambungan rangkaian awal.

DataInputStream i yang dilindungi; DataOutputStream dilindungi o; keluaran TextArea yang dilindungi; input TextField yang dilindungi; pendengar Thread yang dilindungi; ChatClient awam (tajuk String, InputStream i, OutputStream o) {super (tajuk); ini.i = DataInputStream baru (BufferedInputStream baru (i)); this.o = DataOutputStream baru (BufferedOutputStream baru (o)); setLayout (BorderLayout baru ()); tambah ("Pusat", output = TextArea baru ()); output.setEditable (palsu); tambah ("Selatan", input = Bidang Teks baru ()); pek (); tunjuk (); input.requestFocus (); pendengar = Thread baru (ini); pendengar.start (); }

Pembina mengambil tiga parameter: tajuk untuk tetingkap, aliran input, dan aliran output. The ChatClientberkomunikasi atas sungai yang telah ditetapkan; kami membuat aliran data buffer i dan o untuk menyediakan kemudahan komunikasi tahap tinggi yang efisien melalui aliran ini. Kami kemudian menyiapkan antara muka pengguna mudah kami, yang terdiri daripada TextAreaoutput dan TextFieldinput. Kami menyusun dan menunjukkan tetingkap, dan memulakan Threadpendengar yang menerima mesej dari pelayan.

larian kekosongan awam () {try {while (true) {String line = i.readUTF (); output.appendText (baris + "\ n"); }} tangkapan (IOException ex) {ex.printStackTrace (); } akhirnya {listener = null; input.hide (); mengesahkan (); cuba {o.close (); } tangkapan (IOException ex) {ex.printStackTrace (); }}}

Apabila utas pendengar memasuki kaedah larian, kita duduk dalam satu gelung tak terhingga yang membaca Stringdari aliran input. Apabila Stringtiba, kami menambahkannya ke kawasan output dan mengulangi gelung. Ini IOExceptionboleh berlaku sekiranya sambungan ke pelayan terputus. Sekiranya berlaku, kami mencetak pengecualian dan melakukan pembersihan. Nota ini akan memberi isyarat dengan satu EOFExceptiondaripada readUTF()kaedah.

Untuk membersihkan, kita memberikan rujukan pendengar kami kepada ini Threaduntuk null; ini menunjukkan kepada kod yang lain bahawa utas telah ditamatkan. Kami kemudian menyembunyikan medan input dan memanggil validate()sehingga antara muka ditata kembali, dan menutup OutputStreamo untuk memastikan bahawa sambungan ditutup.

Perhatikan bahawa kami melakukan semua pembersihan dalam finallyklausa, jadi ini akan berlaku sama ada IOExceptionberlaku di sini atau benang dihentikan secara paksa. Kami tidak menutup tingkap dengan segera; anggapannya ialah pengguna mungkin mahu membaca sesi tersebut walaupun sambungannya terputus.

public boolean handleEvent (Event e) {if ((e.target == input) && (e.id == Event.ACTION_EVENT)) {cuba {o.writeUTF ((String) e.arg); o.flush (); } tangkapan (IOException ex) {ex.printStackTrace (); pendengar.stop (); } input.setText (""); kembali benar; } lain jika ((e.target == ini) && (e.id == Peristiwa.WINDOW_DESTROY)) {if (listener! = null) listener.stop (); sembunyikan (); kembali benar; } kembali super.handleEvent (e); }

Dalam handleEvent()kaedah tersebut, kita perlu memeriksa dua peristiwa UI yang penting:

Yang pertama adalah peristiwa aksi di TextField, yang bermaksud bahawa pengguna telah menekan tombol Return. Apabila kami menangkap peristiwa ini, kami menuliskan pesan ke aliran output, kemudian menelepon flush()untuk memastikannya segera dihantar. Aliran output adalah DataOutputStream, jadi kita dapat gunakan writeUTF()untuk mengirim String. Sekiranya IOExceptionberlaku, sambungan mesti gagal, jadi kami menghentikan urutan pendengar; ini secara automatik akan melakukan semua pembersihan yang diperlukan.

Acara kedua adalah pengguna yang cuba menutup tingkap. Terserah kepada pengaturcara untuk menguruskan tugas ini; kita menghentikan urutan pendengar dan menyembunyikan Frame.

public static void main (String args []) melemparkan IOException {if (args.length! = 2) membuang RuntimeException baru ("Sintaks: ChatClient"); Socket s = Socket baru (args [0], Integer.parseInt (args [1])); ChatClient baru ("Chat" + args [0] + ":" + args [1], s.getInputStream (), s.getOutputStream ()); }

The main()kaedah bermula klien; kami memastikan bahawa jumlah argumen yang betul telah diberikan, kami membuka Socketke host dan port yang ditentukan, dan kami membuat ChatClientsambungan ke aliran soket. Membuat soket boleh membuang pengecualian yang akan keluar dari kaedah ini dan dipaparkan.

Membina pelayan multithread

Kami sekarang mengembangkan pelayan sembang yang dapat menerima banyak sambungan dan yang akan menyiarkan semua yang dibacanya dari mana-mana pelanggan. Ia adalah rajin membaca dan menulis Stringdalam format UTF.

Terdapat dua kelas dalam program ini: kelas utama ChatServer, adalah pelayan yang menerima sambungan dari klien dan memberikannya ke objek pengendali sambungan baru. The ChatHandlerkelas sebenarnya melaksanakan pekerjaan mendengar untuk mesej dan penyiaran mereka kepada semua pelanggan disambungkan. Satu utas (utas utama) mengendalikan sambungan baru, dan ada utas ( ChatHandlerkelas) untuk setiap pelanggan.

Setiap yang baru ChatClientakan bersambung ke ChatServer; ini ChatServerakan memberikan sambungan ke contoh baru ChatHandlerkelas yang akan menerima mesej dari pelanggan baru. Di dalam ChatHandlerkelas, senarai pengendali semasa disimpan; yang broadcast()kaedah menggunakan senarai ini untuk menghantar mesej kepada semua yang berkaitan ChatClients.

Pelayan Sembang Kelas

Kelas ini berkaitan dengan penerimaan sambungan dari pelanggan dan melancarkan utas pengendali untuk memprosesnya.

import java.net. *; import java.io. *; import java.util. *; kelas awam ChatServer {// public ChatServer (int port) melemparkan IOException ... // awam kosong statik utama (String args []) melemparkan IOException ...}

Kelas ini adalah aplikasi mandiri yang sederhana. Kami menyediakan konstruktor yang melaksanakan semua kerja sebenar untuk kelas, dan main()kaedah yang memulakannya.

ChatServer awam (int port) melemparkan IOException {ServerSocket server = ServerSocket baru (port); sementara (benar) {Socket client = server.accept (); System.out.println ("Diterima dari" + client.getInetAddress ()); ChatHandler c = ChatHandler baru (pelanggan); c. mulakan (); }}

Pembina ini, yang melakukan semua kerja pelayan, agak mudah. Kami membuat ServerSocketdan kemudian duduk dalam lingkaran menerima pelanggan dengan accept()kaedah ServerSocket. Untuk setiap sambungan, kami membuat contoh baru ChatHandlerkelas, meneruskan yang baru Socketsebagai parameter. Setelah kami mencipta pengendali ini, kami memulakannya dengan start()kaedahnya. Ini memulakan utas baru untuk mengendalikan sambungan sehingga gelung pelayan utama kami dapat terus menunggu sambungan baru.

main statik kekosongan awam (String args []) melemparkan IOException {if (args.length! = 1) membuang RuntimeException baru ("Sintaks: ChatServer"); ChatServer baru (Integer.parseInt (args [0])); }

The main()kaedah mewujudkan atas kehendak ChatServer, lulus pelabuhan baris arahan sebagai parameter. Ini adalah port yang akan dihubungkan oleh pelanggan.

Pengendali Sembang Kelas

Kelas ini berkaitan dengan pengendalian sambungan individu. Kita mesti menerima mesej dari pelanggan dan menghantarnya semula ke semua sambungan lain. Kami menyimpan senarai sambungan di a

static

Vector.

import java.net. *; import java.io. *; import java.util. *; kelas awam ChatHandler memanjangkan Thread {// ChatHandler awam (Socket s) melemparkan IOException ... // jalan keluar umum () ...

Kami memperluaskan Threadkelas untuk membolehkan utas terpisah untuk memproses klien yang berkaitan. Pembina menerima a Socketyang kami pasangkan; yang run()kaedah, yang dipanggil oleh thread baru, menjalankan pemprosesan pelanggan yang sebenar.

Socket dilindungi; DataInputStream i yang dilindungi; DataOutputStream dilindungi o; ChatHandler awam (Socket s) melemparkan IOException {this.s = s; i = DataInputStream baru (BufferedInputStream baru (s.getInputStream ())); o = DataOutputStream baru (BufferedOutputStream baru (s.getOutputStream ())); }

Pembina menyimpan rujukan ke soket pelanggan dan membuka input dan aliran output. Sekali lagi, kami menggunakan aliran data yang disangga; ini memberi kami I / O dan kaedah yang cekap untuk menyampaikan jenis data tahap tinggi - dalam kes ini, Strings.

pengendali Vektor statik terlindung = Vektor baru (); larian kekosongan awam () {cuba {handlers.addElement (ini); sementara (benar) {String msg = i.readUTF (); siaran (msg); }} tangkapan (IOException ex) {ex.printStackTrace (); } akhirnya {handlers.removeElement (ini); cubalah {s.close (); } tangkapan (IOException ex) {ex.printStackTrace (); }}} // siaran kekosongan statik terlindung (Mesej rentetan) ...

The run()kaedah adalah di mana thread kami masuk. Pertama kita menambah thread kita kepada Vectordaripada ChatHandlerPengendali s. Pengendali Vectormenyimpan senarai semua pengendali semasa. Ini adalah staticpemboleh ubah dan jadi ada satu contoh Vectoruntuk seluruh ChatHandlerkelas dan semua contohnya. Oleh itu, semua ChatHandlers dapat mengakses senarai sambungan semasa.

Perhatikan bahawa sangat penting bagi kita untuk membuang diri dari senarai ini selepas itu sekiranya hubungan kita gagal; jika tidak, semua pengendali lain akan berusaha menulis kepada kami semasa mereka menyiarkan maklumat. Jenis situasi ini, di mana sangat mustahak bahawa tindakan yang dilakukan setelah menyelesaikan bahagian kod, adalah penggunaan utama try ... finallykonstruk; oleh itu kami melaksanakan semua kerja kami dalam try ... catch ... finallykonstruk.

Bahagian kaedah ini menerima mesej dari pelanggan dan menyiarkan semula mereka kepada semua pelanggan lain menggunakan broadcast()kaedah tersebut. Apabila gelung keluar, sama ada kerana pengecualian membaca dari klien atau kerana utas ini dihentikan, finallyklausa dijamin akan dilaksanakan. Dalam klausa ini, kami mengeluarkan utas kami dari senarai pengendali dan menutup soket.

siaran kekosongan statik terlindung (String message) {synchronized (handlers) {Enumeration e = handlers.elements (); sementara (e.hasMoreElements ()) {ChatHandler c = (ChatHandler) e.nextElement (); cuba {synchronized (co) {cowriteUTF (mesej); } coflush (); } tangkapan (IOException ex) {c.stop (); }}}}

Kaedah ini menyiarkan mesej kepada semua pelanggan. Kami mula-mula menyegerakkan senarai pengendali. Kami tidak mahu orang-orang bergabung atau pergi semasa kami melakukan looping, sekiranya kami cuba menyiarkan kepada seseorang yang tidak lagi wujud; ini memaksa pelanggan untuk menunggu sehingga kita selesai menyegerakkan. Sekiranya pelayan mesti menangani beban yang sangat berat, maka kami mungkin menyediakan penyegerakan yang lebih baik.

Dalam blok yang disegerakkan ini, kita mendapat Enumerationpengendali semasa. The Enumerationkelas menyediakan cara yang mudah untuk melelar melalui semua unsur-unsur Vector. Gelung kami hanya menulis mesej ke setiap elemen Enumeration. Perhatikan bahawa jika pengecualian berlaku semasa menulis ke ChatClient, maka kami memanggil kaedah pelanggan stop(); ini menghentikan urutan pelanggan dan oleh itu melakukan pembersihan yang sesuai, termasuk mengeluarkan klien dari pengendali.