Sebagai tindak lanjut dari pertanyaan terbaru , saya bertanya-tanya mengapa tidak mungkin di Java, tanpa mencoba membaca / menulis pada soket TCP, untuk mendeteksi bahwa soket telah ditutup dengan baik oleh rekan? Ini tampaknya menjadi kasus terlepas dari apakah seseorang menggunakan pra-NIO Socket
atau NIO SocketChannel
.
Ketika rekan dengan anggun menutup koneksi TCP, tumpukan TCP di kedua sisi koneksi mengetahui fakta tersebut. Sisi server (yang memulai penghentian) berakhir dalam status FIN_WAIT2
, sedangkan sisi klien (yang tidak secara eksplisit menanggapi pematian) berakhir dalam status CLOSE_WAIT
. Mengapa tidak ada metode Socket
atau SocketChannel
yang dapat menanyakan tumpukan TCP untuk melihat apakah koneksi TCP yang mendasari telah diakhiri? Apakah tumpukan TCP tidak memberikan informasi status seperti itu? Ataukah keputusan desain untuk menghindari panggilan yang mahal ke kernel?
Dengan bantuan pengguna yang telah memposting beberapa jawaban untuk pertanyaan ini, saya pikir saya melihat dari mana masalah itu mungkin berasal. Sisi yang tidak secara eksplisit menutup koneksi berakhir dalam status TCP yang CLOSE_WAIT
berarti bahwa koneksi sedang dalam proses mematikan dan menunggu sisi tersebut mengeluarkan operasinya sendiri CLOSE
. Saya kira cukup adil bahwa isConnected
pengembalian true
dan isClosed
pengembalian false
, tetapi mengapa tidak ada yang seperti itu isClosing
?
Di bawah ini adalah kelas pengujian yang menggunakan soket pra-NIO. Tetapi hasil yang identik diperoleh dengan menggunakan NIO.
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) throws Exception {
final ServerSocket ss = new ServerSocket(12345);
final Socket cs = ss.accept();
System.out.println("Accepted connection");
Thread.sleep(5000);
cs.close();
System.out.println("Closed connection");
ss.close();
Thread.sleep(100000);
}
}
import java.net.Socket;
public class MyClient {
public static void main(String[] args) throws Exception {
final Socket s = new Socket("localhost", 12345);
for (int i = 0; i < 10; i++) {
System.out.println("connected: " + s.isConnected() +
", closed: " + s.isClosed());
Thread.sleep(1000);
}
Thread.sleep(100000);
}
}
Saat klien uji terhubung ke server uji, hasilnya tetap tidak berubah bahkan setelah server memulai pemadaman koneksi:
connected: true, closed: false
connected: true, closed: false
...
sumber
Socket.close
bukanlah penutup yang anggun.Jawaban:
Saya telah sering menggunakan Sockets, sebagian besar dengan Selectors, dan meskipun bukan ahli OSI Jaringan, dari pemahaman saya, memanggil
shutdownOutput()
Socket sebenarnya mengirimkan sesuatu di jaringan (FIN) yang membangunkan Selector saya di sisi lain (perilaku yang sama di C bahasa). Di sini Anda memiliki deteksi : benar-benar mendeteksi operasi baca yang akan gagal saat Anda mencobanya.Dalam kode yang Anda berikan, menutup soket akan mematikan aliran input dan output, tanpa kemungkinan membaca data yang mungkin tersedia, oleh karena itu kehilangannya.
Socket.close()
Metode Java melakukan pemutusan hubungan "anggun" (berlawanan dengan apa yang awalnya saya pikirkan) di mana data yang tersisa di aliran keluaran akan dikirim diikuti oleh FIN untuk menandakan penutupannya. FIN akan ACK'd oleh pihak lain, karena setiap paket reguler akan 1 .Jika Anda perlu menunggu sisi lain menutup soketnya, Anda harus menunggu FIN-nya. Dan untuk mencapai itu, Anda harus mendeteksi
Socket.getInputStream().read() < 0
, yang berarti Anda tidak boleh menutup soket Anda, karena akan menutupnyaInputStream
.Dari apa yang saya lakukan di C, dan sekarang di Java, mencapai sinkronisasi yang begitu dekat harus dilakukan seperti ini:
read()
dan mendeteksi remoteclose()
InputStream
sampai kami menerima balasan-FIN dari ujung lain (karena akan mendeteksi FIN, itu akan melalui proses koneksi anggun yang sama). Ini penting pada beberapa OS karena mereka tidak benar-benar menutup soket selama salah satu buffernya masih berisi data. Mereka disebut soket "hantu" dan menggunakan nomor deskriptor di OS (yang mungkin tidak menjadi masalah lagi dengan OS modern)Socket.close()
atau menutupnyaInputStream
atauOutputStream
)Seperti yang ditunjukkan dalam cuplikan Java berikut:
public void synchronizedClose(Socket sok) { InputStream is = sok.getInputStream(); sok.shutdownOutput(); // Sends the 'FIN' on the network while (is.read() > 0) ; // "read()" returns '-1' when the 'FIN' is reached sok.close(); // or is.close(); Now we can close the Socket }
Tentu saja kedua belah pihak harus menggunakan cara yang sama untuk menutup, atau bagian pengirim mungkin selalu mengirimkan data yang cukup untuk membuat
while
loop tetap sibuk (misalnya jika bagian pengirim hanya mengirim data dan tidak pernah membaca untuk mendeteksi pemutusan koneksi. Yang kikuk, tetapi Anda mungkin tidak memiliki kendali atas hal itu).Seperti yang ditunjukkan @WarrenDew dalam komentarnya, membuang data dalam program (lapisan aplikasi) menyebabkan pemutusan sambungan yang tidak anggun di lapisan aplikasi: meskipun semua data diterima di lapisan TCP (
while
loop), data tersebut dibuang.1 : Dari " Jaringan Fundamental di Java ": lihat gbr. 3.3 hal.45, dan keseluruhan §3.7, hal 43-48
sumber
socket.close()
itu "brutal" dengan cara yang tidak menghormati sinyal Klien / Server ini. Server akan diberi tahu tentang pemutusan Klien hanya jika buffer keluaran soketnya sendiri penuh (karena tidak ada data yang diakses oleh sisi lain, yang ditutup).read()
mengembalikan -1 di akhir aliran, dan terus melakukannya berapa kali pun Anda menyebutnya. ACread()
ataurecv()
mengembalikan nol pada akhir aliran, dan terus melakukannya berapa kali pun Anda menyebutnya.Saya pikir ini lebih merupakan pertanyaan pemrograman soket. Java hanya mengikuti tradisi pemrograman soket.
Dari Wikipedia :
Setelah jabat tangan selesai, TCP tidak membuat perbedaan apa pun antara dua titik akhir (klien dan server). Istilah "klien" dan "server" sebagian besar ditujukan untuk kenyamanan. Jadi, "server" dapat mengirim data dan "klien" dapat mengirim beberapa data lain secara bersamaan satu sama lain.
Istilah "Tutup" juga menyesatkan. Yang ada hanya pernyataan FIN, yang artinya "Saya tidak akan mengirimkan barang lagi." Tetapi ini tidak berarti bahwa tidak ada paket dalam penerbangan, atau yang lain tidak bisa bicara lagi. Jika Anda menerapkan snail mail sebagai lapisan data link, atau jika paket Anda menempuh rute yang berbeda, mungkin penerima menerima paket dengan urutan yang salah. TCP tahu cara memperbaikinya untuk Anda.
Juga Anda, sebagai sebuah program, mungkin tidak punya waktu untuk terus memeriksa apa yang ada di buffer. Jadi, sesuai keinginan Anda, Anda dapat memeriksa apa yang ada di buffer. Secara keseluruhan, implementasi soket saat ini tidak terlalu buruk. Jika memang ada isPeerClosed (), itu adalah panggilan ekstra yang harus Anda lakukan setiap kali Anda ingin menelepon read.
sumber
API soket yang mendasarinya tidak memiliki pemberitahuan seperti itu.
Tumpukan TCP pengiriman tidak akan mengirim bit FIN hingga paket terakhir, jadi mungkin ada banyak data yang di-buffer dari saat aplikasi pengirim secara logis menutup soketnya bahkan sebelum data itu dikirim. Demikian juga, data yang di-buffer karena jaringan lebih cepat daripada aplikasi penerima (saya tidak tahu, mungkin Anda menyampaikannya melalui koneksi yang lebih lambat) dapat menjadi signifikan bagi penerima dan Anda tidak ingin aplikasi penerima membuangnya. hanya karena bit FIN telah diterima oleh stack.
sumber
Karena sejauh ini tidak ada jawaban yang sepenuhnya menjawab pertanyaan, saya meringkas pemahaman saya saat ini tentang masalah tersebut.
Ketika koneksi TCP dibuat dan satu peer memanggil
close()
ataushutdownOutput()
pada soketnya, soket di sisi lain dari koneksi beralih keCLOSE_WAIT
status. Pada prinsipnya, dimungkinkan untuk mengetahui dari tumpukan TCP apakah soket dalamCLOSE_WAIT
keadaan tanpa panggilanread/recv
(misalnya,getsockopt()
di Linux: http://www.developerweb.net/forum/showthread.php?t=4395 ), tetapi itu tidak portabel.Socket
Kelas Java tampaknya dirancang untuk menyediakan abstraksi yang sebanding dengan soket TCP TCP, mungkin karena ini adalah tingkat abstraksi yang biasa digunakan orang saat memprogram aplikasi TCP / IP. Soket BSD adalah soket pendukung generalisasi selain hanya soket INET (mis., TCP), jadi soket ini tidak menyediakan cara portabel untuk mengetahui status TCP soket.Tidak ada metode seperti itu
isCloseWait()
karena orang yang terbiasa memprogram aplikasi TCP pada tingkat abstraksi yang ditawarkan oleh soket BSD tidak mengharapkan Java menyediakan metode tambahan.sumber
isCloseWait()
karena tidak didukung di semua platform.Mendeteksi apakah sisi jarak jauh dari koneksi soket (TCP) telah ditutup dapat dilakukan dengan metode java.net.Socket.sendUrgentData (int), dan menangkap IOException yang dilemparkannya jika sisi jarak jauh tidak aktif. Ini telah diuji antara Java-Java, dan Java-C.
Ini untuk menghindari masalah merancang protokol komunikasi untuk menggunakan semacam mekanisme ping. Dengan menonaktifkan OOBInline pada soket (setOOBInline (false), setiap data OOB yang diterima akan dibuang secara diam-diam, tetapi data OOB masih dapat dikirim. Jika sisi jarak jauh ditutup, reset koneksi akan dicoba, gagal, dan menyebabkan beberapa IOException dilemparkan .
Jika Anda benar-benar menggunakan data OOB dalam protokol Anda, maka jarak tempuh Anda mungkin berbeda.
sumber
tumpukan Java IO pasti mengirim FIN saat ia dihancurkan saat pembongkaran tiba-tiba. Tidak masuk akal jika Anda tidak dapat mendeteksi ini, karena sebagian besar klien hanya mengirim FIN jika mereka mematikan koneksi.
... alasan lain saya benar-benar mulai membenci kelas NIO Java. Sepertinya semuanya sedikit setengah-setengah.
sumber
Itu topik yang menarik. Saya baru saja menggali kode java untuk memeriksa. Dari temuan saya, ada dua masalah yang berbeda: yang pertama adalah TCP RFC itu sendiri, yang memungkinkan soket yang tertutup dari jarak jauh untuk mengirimkan data dalam setengah dupleks, sehingga soket yang ditutup dari jarak jauh masih setengah terbuka. Sesuai RFC, RST tidak menutup koneksi, Anda perlu mengirim perintah ABORT eksplisit; jadi Java memungkinkan pengiriman data melalui setengah soket tertutup
(Ada dua metode untuk membaca status tutup di kedua titik akhir.)
Masalah lainnya adalah implementasi mengatakan bahwa perilaku ini opsional. Karena Java berusaha untuk menjadi portabel, mereka menerapkan fitur umum terbaik. Mempertahankan peta (OS, implementasi half duplex) akan menjadi masalah, saya kira.
sumber
Ini adalah cacat dari kelas soket OO Java (dan semua yang lain yang pernah saya lihat) - tidak ada akses ke panggilan sistem yang dipilih.
Jawaban yang benar di C:
struct timeval tp; fd_set in; fd_set out; fd_set err; FD_ZERO (in); FD_ZERO (out); FD_ZERO (err); FD_SET(socket_handle, err); tp.tv_sec = 0; /* or however long you want to wait */ tp.tv_usec = 0; select(socket_handle + 1, in, out, err, &tp); if (FD_ISSET(socket_handle, err) { /* handle closed socket */ }
sumber
getsocketop(... SOL_SOCKET, SO_ERROR, ...)
.Berikut ini solusi yang payah. Gunakan SSL;) dan SSL melakukan jabat tangan dekat saat pembongkaran sehingga Anda diberi tahu tentang soket yang ditutup (sebagian besar penerapan tampaknya melakukan jabat tangan yang tepat).
sumber
Alasan untuk perilaku ini (yang bukan khusus Java) adalah kenyataan bahwa Anda tidak mendapatkan informasi status apa pun dari tumpukan TCP. Bagaimanapun, soket hanyalah pegangan file lain dan Anda tidak dapat mengetahui apakah ada data aktual untuk dibaca darinya tanpa benar-benar mencoba (
select(2)
tidak akan membantu di sana, itu hanya memberi sinyal bahwa Anda dapat mencoba tanpa memblokir).Untuk informasi lebih lanjut, lihat FAQ soket Unix .
sumber
select()
memberi tahu Anda apakah ada data atau EOS yang tersedia untuk dibaca tanpa pemblokiran. 'Sinyal yang dapat Anda coba tanpa memblokir' tidak ada artinya. Jika Anda berada dalam mode non-pemblokiran, Anda selalu dapat mencoba tanpa memblokir.select()
didorong oleh data di soket yang menerima buffer atau FIN yang tertunda atau ruang di buffer pengiriman soket.getsockopt(SO_ERROR)
? Bahkan,getpeername
akan memberi tahu Anda jika soket masih terhubung.Hanya menulis yang mengharuskan paket dipertukarkan yang memungkinkan hilangnya koneksi untuk ditentukan. Solusi yang umum digunakan adalah dengan menggunakan opsi KEEP ALIVE.
sumber
Ketika berurusan dengan soket Java setengah terbuka, orang mungkin ingin melihat isInputShutdown () dan isOutputShutdown () .
sumber