Java socket API: Bagaimana cara mengetahui apakah koneksi telah ditutup?

93

Saya mengalami beberapa masalah dengan Java socket API. Saya mencoba menampilkan jumlah pemain yang saat ini terhubung ke game saya. Mudah untuk menentukan kapan seorang pemain telah terhubung. Namun, tampaknya sulit untuk menentukan kapan seorang pemain telah memutus sambungan menggunakan API soket.

Memanggil isConnected()pada soket yang telah dicabut dari jarak jauh sepertinya selalu kembali true. Demikian pula, memanggil isClosed()soket yang telah ditutup dari jarak jauh sepertinya selalu kembali false. Saya telah membaca bahwa untuk benar-benar menentukan apakah soket telah ditutup atau tidak, data harus ditulis ke aliran keluaran dan pengecualian harus ditangkap. Ini sepertinya cara yang sangat tidak bersih untuk menangani situasi ini. Kami hanya harus terus-menerus mengirim pesan sampah melalui jaringan untuk mengetahui kapan soket telah ditutup.

Apakah ada solusi yang lain?

Dan Brouwer
sumber

Jawaban:

183

Tidak ada API TCP yang akan memberi tahu Anda status koneksi saat ini. isConnected()dan isClosed()memberi tahu Anda status soket Anda saat ini . Bukan hal yang sama.

  1. isConnected()memberi tahu Anda apakah Anda telah menghubungkan soket ini . Anda punya, jadi hasilnya benar.

  2. isClosed()memberi tahu Anda apakah Anda telah menutup soket ini. Sampai Anda memilikinya, hasilnya salah.

  3. Jika peer telah menutup koneksi secara tertib

    • read() mengembalikan -1
    • readLine() kembali null
    • readXXX()lemparan EOFExceptionuntuk XXX lainnya.

    • Tulisan akan memunculkan IOException: 'koneksi disetel ulang oleh rekan', pada akhirnya, tunduk pada penundaan buffering.

  4. Jika koneksi terputus karena alasan lain, penulisan akan menghasilkan IOException, pada akhirnya, seperti di atas, dan pembacaan dapat melakukan hal yang sama.

  5. Jika rekan masih terhubung tetapi tidak menggunakan koneksi, batas waktu baca dapat digunakan.

  6. Bertentangan dengan apa yang mungkin Anda baca di tempat lain, ClosedChannelExceptiontidak memberi tahu Anda hal ini. [Begitu pula SocketException: socket closed.] Ini hanya memberitahu Anda bahwa Anda menutup saluran, dan kemudian terus menggunakannya. Dengan kata lain, kesalahan pemrograman di pihak Anda. Ini tidak menunjukkan koneksi tertutup .

  7. Sebagai hasil dari beberapa percobaan dengan Java 7 pada Windows XP, juga tampak bahwa jika:

    • Anda memilih OP_READ
    • select() mengembalikan nilai lebih besar dari nol
    • yang terkait SelectionKeysudah tidak valid ( key.isValid() == false)

    itu berarti rekan telah mengatur ulang koneksi. Namun ini mungkin aneh baik untuk versi atau platform JRE.

Marquis dari Lorne
sumber
19
Sulit dipercaya bahwa protokol TCP, yang berorientasi pada koneksi, bahkan tidak dapat mengetahui status koneksinya ... Apakah orang-orang yang datang dengan protokol ini mengendarai mobil mereka dengan mata tertutup?
PedroD
33
@PedroD Sebaliknya: itu disengaja. Suite protokol sebelumnya seperti SNA memiliki 'nada panggil'. TCP dirancang untuk bertahan dari perang nuklir, dan, yang lebih remehnya, router turun dan naik: karena itu sama sekali tidak ada apa pun seperti nada panggil, status koneksi, dll .; dan itu juga mengapa TCP keepalive dijelaskan di RFC sebagai fitur kontroversial, dan mengapa itu selalu mati secara default. TCP masih bersama kami. SNA? IPX? ISO? Tidak. Mereka melakukannya dengan benar.
Marquis dari Lorne
4
Saya tidak percaya itu alasan yang baik untuk menyembunyikan informasi ini dari kami. Mengetahui bahwa koneksi terputus tidak berarti bahwa protokol kurang tahan terhadap kesalahan, itu selalu bergantung pada apa yang kita lakukan dengan pengetahuan itu ... Bagi saya metode isBound dan isConnected dari java adalah metode tiruan murni, mereka tidak ada gunanya , tetapi menyatakan kebutuhan untuk pendengar acara koneksi ... Tapi saya tegaskan: mengetahui bahwa koneksi terputus tidak membuat protokol lebih buruk. Sekarang jika protokol yang Anda katakan mematikan koneksi segera setelah mereka mendeteksi itu hilang, itu cerita yang berbeda.
PedroD
23
@Pedro Anda tidak mengerti. Ini bukan 'alasan'. Tidak ada informasi untuk ditahan. Tidak ada nada panggil. TCP tidak tahu apakah koneksi gagal sampai Anda mencoba melakukan sesuatu padanya. Itu adalah kriteria desain fundamental.
Marquis dari Lorne
2
@EJP terima kasih atas jawaban Anda. Apakah Anda mungkin tahu cara memeriksa apakah soket ditutup tanpa "memblokir"? Menggunakan read atau readLine akan memblokir. Apakah ada cara untuk mengetahui jika soket lain ditutup dengan metode yang tersedia, tampaknya kembali 0bukan -1.
insumity
9

Ini adalah praktik umum dalam berbagai protokol perpesanan untuk menjaga detak jantung satu sama lain (tetap mengirim paket ping) paket tidak perlu berukuran sangat besar. Mekanisme probing akan memungkinkan Anda untuk mendeteksi klien yang terputus bahkan sebelum TCP mengetahuinya secara umum (batas waktu TCP jauh lebih tinggi) Kirim probe dan tunggu katakanlah 5 detik untuk balasan, jika Anda tidak melihat balasan untuk katakanlah 2-3 probe berikutnya, pemutar Anda terputus.

Juga, pertanyaan terkait

Kalpak Gadre
sumber
6
Ini adalah 'praktik umum' di beberapa protokol. Saya perhatikan bahwa HTTP, protokol aplikasi yang paling banyak digunakan di planet ini, tidak memiliki operasi PING.
Marquis dari Lorne
2
@ user207421 karena HTTP / 1 adalah protokol satu tembakan. Anda mengirim permintaan, Anda menerima tanggapan. Dan Anda selesai. Soket ditutup. Ekstensi Websocket memang memiliki operasi PING, begitu pula HTTP / 2.
Michał Zabielski
Saya merekomendasikan detak jantung dengan satu byte jika memungkinkan :)
Stefan Reich
@ MichałZabielski Ini karena desainer HTTP tidak menentukannya. Anda tidak tahu kenapa tidak. HTTP / 1.1 bukanlah protokol satu tembakan. Soket tidak ditutup: koneksi HTTP persisten secara default. FTP, SMTP, POP3, IMAP, TLS, ... tidak memiliki detak jantung.
Marquis dari Lorne
2

Saya melihat jawaban lain baru saja diposting, tetapi saya pikir Anda interaktif dengan klien yang memainkan game Anda, jadi saya dapat mengajukan pendekatan lain (sementara BufferedReader jelas valid dalam beberapa kasus).

Jika Anda ingin ... Anda dapat mendelegasikan tanggung jawab "pendaftaran" kepada klien. Yaitu Anda akan memiliki kumpulan pengguna yang terhubung dengan stempel waktu pada pesan terakhir yang diterima dari masing-masing ... jika waktu klien habis, Anda akan memaksa pendaftaran ulang klien, tetapi itu mengarah ke kutipan dan ide di bawah ini.

Saya telah membaca bahwa untuk benar-benar menentukan apakah soket telah ditutup, data harus ditulis ke aliran keluaran dan pengecualian harus ditangkap. Ini sepertinya cara yang sangat tidak bersih untuk menangani situasi ini.

Jika kode Java Anda tidak menutup / memutuskan Socket, bagaimana lagi Anda akan diberitahu bahwa remote host menutup koneksi Anda? Pada akhirnya, coba / tangkap Anda melakukan hal yang kira-kira sama dengan yang dilakukan oleh poller yang mendengarkan peristiwa di soket AKTUAL. Pertimbangkan hal berikut:

  • sistem lokal Anda dapat menutup soket Anda tanpa memberi tahu Anda ... itu hanya implementasi Socket (yaitu, tidak melakukan polling pada perangkat keras / driver / firmware / apa pun untuk perubahan status).
  • Socket baru (Proxy p) ... ada beberapa pihak (benar-benar 6 endpoint) yang dapat menutup koneksi Anda ...

Saya rasa salah satu fitur bahasa yang diabstraksi adalah Anda diabstraksi dari detail. Pikirkan penggunaan kata kunci di C # (coba / akhirnya) untuk SqlConnection atau apa pun ... itu hanya biaya melakukan bisnis ... Saya pikir coba / tangkap / akhirnya adalah pola yang diterima dan diperlukan untuk penggunaan Socket.

Scottley
sumber
Sistem lokal Anda tidak dapat 'menutup soket Anda', dengan atau tanpa memberi tahu Anda. Pertanyaan Anda dimulai 'jika kode Java Anda tidak menutup / memutuskan soket ...?' tidak masuk akal juga.
Marquis dari Lorne
1
Apa sebenarnya yang mencegah sistem (Linux misalnya) menutup paksa soket? Apakah masih mungkin untuk melakukannya dari 'gdb' menggunakan perintah 'call close'?
Andrey Lebedenko
@AndreyLebedenko Tidak ada yang 'secara tepat mencegahnya', tetapi tidak berhasil.
Marquis dari Lorne
@EJB siapa yang melakukannya?
Andrey Lebedenko
@ AndreyLebedenko Tidak ada yang melakukannya. Hanya aplikasi yang dapat menutup soketnya sendiri, kecuali jika aplikasi tersebut keluar tanpa melakukannya, dalam hal ini OS akan membersihkan.
Marquis dari Lorne
1

Saya pikir ini adalah sifat koneksi tcp, dalam standar itu dibutuhkan sekitar 6 menit keheningan dalam transmisi sebelum kami menyimpulkan bahwa koneksi keluar hilang! Jadi menurut saya Anda tidak dapat menemukan solusi yang tepat untuk masalah ini. Mungkin cara yang lebih baik adalah dengan menulis beberapa kode praktis untuk menebak kapan server seharusnya menganggap koneksi pengguna ditutup.

Ehsan Khodarahmi
sumber
2
Ini bisa memakan waktu lebih dari itu, hingga dua jam.
Marquis dari Lorne
0

Seperti yang dikatakan @ user207421, tidak ada cara untuk mengetahui status koneksi saat ini karena Model Arsitektur Protokol TCP / IP. Jadi server harus memperhatikan Anda sebelum menutup koneksi atau Anda memeriksanya sendiri.
Ini adalah contoh sederhana yang menunjukkan bagaimana mengetahui soket ditutup oleh server:

sockAdr = new InetSocketAddress(SERVER_HOSTNAME, SERVER_PORT);
socket = new Socket();
timeout = 5000;
socket.connect(sockAdr, timeout);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());
while ((data = reader.readLine())!=null) 
      log.e(TAG, "received -> " + data);
log.e(TAG, "Socket closed !");
ucMedia
sumber
0

Saya menghadapi masalah serupa. Dalam kasus saya, klien harus mengirim data secara berkala. Saya harap Anda memiliki persyaratan yang sama. Kemudian saya mengatur SO_TIMEOUT socket.setSoTimeout(1000 * 60 * 5);yang melempar java.net.SocketTimeoutExceptionketika waktu yang ditentukan telah habis. Kemudian saya dapat mendeteksi klien mati dengan mudah.

hurelhuyag
sumber
-2

Begitulah cara saya menanganinya

 while(true) {
        if((receiveMessage = receiveRead.readLine()) != null ) {  

        System.out.println("first message same :"+receiveMessage);
        System.out.println(receiveMessage);      

        }
        else if(receiveRead.readLine()==null)
        {

        System.out.println("Client has disconected: "+sock.isClosed()); 
        System.exit(1);
         }    } 

jika result.code == null

Petar Ceho
sumber
Anda tidak perlu menelepon readLine()dua kali. Anda sudah tahu bahwa elseblok itu nol .
Marquis dari Lorne
-4

Di Linux ketika menulis () ke dalam soket yang sisi lain, tidak Anda ketahui, tertutup akan memicu sinyal / pengecualian SIGPIPE bagaimanapun Anda ingin memanggilnya. Namun jika Anda tidak ingin ketahuan oleh SIGPIPE Anda dapat menggunakan send () dengan flag MSG_NOSIGNAL. Panggilan send () akan kembali dengan -1 dan dalam hal ini Anda dapat memeriksa errno yang akan memberitahu Anda bahwa Anda mencoba menulis pipa yang rusak (dalam hal ini soket) dengan nilai EPIPE yang menurut errno.h setara dengan 32. Sebagai reaksi terhadap EPIPE Anda dapat menggandakan kembali dan mencoba membuka kembali soket dan mencoba mengirimkan informasi Anda lagi.

MikeK
sumber
The send()panggilan akan kembali -1 hanya jika data keluar mendapat buffered cukup lama untuk timer kirim berakhir. Ini hampir pasti tidak akan terjadi pada pengiriman pertama setelah terputus, karena buffering di kedua ujungnya dan sifat asinkron send()di balik terpal.
Marquis dari Lorne