Kodėl neįmanoma, nesistengiant atlikti I / O, nustatyti, kad TCP lizdas buvo elegantiškai uždarytas tarpusavio?

Kaip neseniai pateikto klausimo pasekmė , man įdomu, kodėl tai neįmanoma „Java“, nesistengiant skaityti / rašyti TCP lizdui, kad sužinotumėte, jog lizdas yra elegantiškai uždarytas bendraamžių? Atrodo, kad taip atsitinka nepriklausomai nuo to, ar naudojamas „NIO Socket ar „NIO SocketChannel .

Kai bendraamžis grakščiai uždaro TCP ryšį, abiejose ryšio pusėse esantys TCP kaminai žino apie šį faktą. Serverio pusė (uždarymo sustabdymas) baigiasi FIN_WAIT2 , o kliento pusė (kuri neatsako aiškiai uždaryti) baigiasi CLOSE_WAIT būsenoje. Kodėl „ Socket ar „ SocketChannel nėra metodo, galinčio prašyti TCP kamino patikrinti, ar pagrindinis TCP ryšys yra baigtas? Ar TCP sklypas nepateikia tokios būsenos informacijos? Ar tai sprendimas dėl dizaino, kad būtų išvengta brangių skambučių į pagrindinį?

Naudodamiesi naudotojais, kurie jau atsakė į šį klausimą, manau, galiu pamatyti, iš kur kilo problema. Pusė, kuri nėra aiškiai uždaryta, baigiasi „TCP CLOSE_WAIT , o tai reiškia, kad ryšys išjungiamas ir tikisi, kad pusė išduos savo CLOSE operaciją. Manau, kad tai yra teisinga, kad yra isConnected grįžta true ir yra isClosed grąžina false , bet kodėl nėra isClosing ?

Toliau pateikiamos bandymų klasės, kuriose naudojamos išankstinės NIO lizdai. Tačiau identiški rezultatai gaunami naudojant 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); } } 

Kai bandymo klientas prisijungia prie bandymų serverio, išėjimas išlieka nepakitęs netgi tada, kai serveris pradeda jungties išjungimą:

 connected: true, closed: false connected: true, closed: false ... 
87
01 окт. nustatė Aleksandras 01 okt. 2008-10-01 00:49 '08, 12:49 AM 2008-10-01 00:49
@ 12 atsakymų

Aš dažnai naudojasi lizdais, dažniausiai su selektoriais, ir, nors aš nesu ekspertas OSI tinkluose, nuo mano supratimo, skambutis shutdownOutput() iš Socket iš tikrųjų siunčia kažką tinkle (FIN), kurį mano selektorius remiasi kitu (tas pats) elgesys C kalba). Čia jūs turite aptikimą : faktinį skaitymo operacijos aptikimą, kuris nepavyks, kai jį išbandysite.

Nurodytame kode lizdo uždarymas išjungs tiek įvesties, tiek išėjimo srautus, nesugebės skaityti duomenų, kuriuos galima pasiekti, todėl jie praranda juos. „Java Socket.close() metodas atlieka „elegantišką“ išjungimą (priešingai nei aš iš pradžių maniau), nes duomenys, kurie lieka išvesties sraute, bus išsiųsti, o paskui - FIN, kad signalas apie jo uždarymą. FIN bus iš kitos pusės, nes bet koks įprastas paketas bus 1 .

Jei jums reikia palaukti, kol kita pusė uždarys savo lizdą, reikia palaukti, kol jūsų FIN bus. Ir tai jums reikia aptikti „ Socket.getInputStream().read() < 0 , o tai reiškia, kad neturėtumėte uždaryti lizdo, nes jis uždaro jį su InputStream .

Iš to, ką aš padariau „C“, o dabar „Java“, tokio sinchroninio uždarymo pasiekimas turėtų būti atliekamas taip:

  • Išjungimo išjungimo išėjimas (siunčia FIN kitame gale, tai paskutinis dalykas, kurį kada nors siųs šis lizdas). Įvestis vis dar atvira, todėl galite read() ir aptikti nuotolinį close()
  • Perskaitykite „ InputStream lizdą, kol iš kito galo gausime atsakymą-FIN (nes jis aptinka FIN, jis eis per tą patį grakštų jungimo procesą). Tai svarbu kai kurioms operacinėms sistemoms, nes jos faktiškai neuždaro lizdo, jei vienas iš jo buferių vis dar turi duomenų. Jie vadinami vaiduoklių lizdais ir naudoja deskriptorių numerius operacinėje sistemoje (tai gali nebūti problema su modernia OS)
  • Uždarykite lizdą (skambindami „ Socket.close() arba uždarydami jį „ InputStream arba „ OutputStream )

Kaip parodyta sekančiame „Java“ fragmente:

 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 } 

Žinoma, abi šalys turi naudoti tą patį uždarymo metodą, arba siunčiančioji dalis visada gali siųsti pakankamai duomenų, kad išlaikytų while kilpos while (pvz., Jei siunčianti dalis siunčia tik duomenis ir niekada neskaito aptikti ryšio, kuris yra nepatogu, bet ne gali jį kontroliuoti).

Kaip pastebėjo @WarrenDew savo komentare, duomenų pašalinimas programoje (taikomojo sluoksnio) sukelia neraštingą išjungimą taikomųjų programų lygiu: nors visi duomenys buvo gauti TCP lygiu ( while ), jie yra atmesti.

1 : Iš „ Pagrindinio tinklo Java “: žr. 3.3 p .45, taip pat visi §3.7, p. 43-48

28
22 февр. Atsakymą pateikė Matthieu vasario 22 d. 2012-02-22 20:12 '12 8:12 val. 2012-02-22 20:12

Manau, kad tai labiau susiję su lizdų programavimu. „Java“ tiesiog seka lizdų programavimo tradiciją.

Iš „ Wikipedia“ :

TCP suteikia patikimą, užsakytą baito srauto pristatymą iš vienos programos į kitą kompiuterį į kitą kompiuterį.

Kai rankinis paspaudimas bus baigtas, TCP nedaro skirtumo tarp dviejų galinių taškų (kliento ir serverio). Sąvoka „klientas“ ir „serveris“ dažniausiai naudojami patogumui. Taigi "serveris" gali siųsti duomenis, o "klientas" vienu metu gali siųsti kai kuriuos kitus duomenis.

Sąvoka „uždaryti“ taip pat yra klaidinanti. Yra tik FIN deklaracija, o tai reiškia, kad „nebesiųsiu jums daugiau“. Tačiau tai nereiškia, kad skrydžio metu nėra paketų, kitaip nieko nereiškia. Jei gatvės paštą naudosite kaip duomenų ryšio sluoksnį arba jei jūsų paketas juda įvairiais maršrutais, gavėjas gali priimti paketus netinkama tvarka. TCP žino, kaip tai išspręsti.

Be to, jūs, kaip programa, neturite pakankamai laiko patikrinti, kas yra buferyje. Taigi, jūsų patogumui galite patikrinti, kas yra buferyje. Apskritai, dabartinės lizdo diegimas nėra toks blogas. Jei iš tikrųjų yraPerClosed (), šis papildomas skambutis turi būti atliekamas kiekvieną kartą, kai norite skambinti.

17
01 окт. atsakymą pateikė Eugene Yokota 01 okt. 2008-10-01 09:06 '08, 9:06 am. 2008-10-01 09:06

Tokio pranešimo socket API nėra.

Siunčiantis TCP kaminas neperduos FIN bitų į paskutinį paketą, todėl gali būti daug buferinių duomenų, kai siuntimo programa logiškai uždaro savo lizdą prieš siunčiant šiuos duomenis. Panašiai, duomenys, kurie yra saugomi dėl to, kad tinklas yra greitesnis nei gaunančioji programa (aš nežinau, galbūt perkeliate jį per lėtesnį ryšį), gali būti svarbūs gavėjui, todėl nenorite, kad priimančioji programa būtų atmesta todėl, kad pinigai gavo FIN bitą.

10
01 окт. atsakymas, kurį pateikė Mike Dimmick Oct 01 2008-10-01 01:11 '08, 1:11 val. 2008-10-01 01:11

Kadangi nė vienas atsakymas iki šiol visiškai neatsakė į klausimą, apibendrinu savo dabartinį supratimą apie problemą.

Kai užmezgamas TCP ryšys ir vienas pokalbio skambutis close() arba shutdownOutput() CLOSE_WAIT shutdownOutput() , lizdas kitoje ryšio pusėje pereina į CLOSE_WAIT būseną. Iš TCP kamino iš esmės galite pasakyti, ar lizdas yra CLOSE_WAIT būsenoje be skambinimo read/recv (pvz., „ getsockopt() „Linux“: http://www.developerweb.net/forum/showthread.php?t=4395 ), tačiau jis nėra toleruojamas.

Atrodo, kad „Java Socket klasė sukurta taip, kad abstrakcija būtų panaši į BSD TCP steką, tikriausiai todėl, kad tai yra abstrakcijos lygis, kuriuo žmonės naudojasi programuodami TCP / IP programas. BSD lizdai yra pagalbinės lizdai, išskyrus INET (pvz., TCP), todėl jie nepateikia nešiojamojo būdo sužinoti TCP lizdo būklę.

Nėra tokio metodo kaip isCloseWait() , nes žmonės, naudojami programuoti TCP programas abstrakcijos lygiu, kurį siūlo BSD lizdai, nemano, kad „Java“ suteiks papildomų metodų.

7
01 окт. atsakymas pateikiamas Aleksandro 01 okt. 2008-10-01 16:17 '08 at 4:17 pm 2008-10-01 16:17

Aptikimas, ar pašalinta lizdo jungties (TCP) nuotolinė pusė, gali būti atlikta naudojant java.net.Socket.sendUrgentData (int) metodą ir sugavus IOException, jei ji yra pašalinta. Jis buvo išbandytas tarp java java ir java-c.

Taip išvengiama problemos, susijusios su komunikacijos protokolo sukūrimu tam tikro tipo pingavimo mechanizmo naudojimui. OOBInline išjungimas lizde (setOOBInline (false), bet kokie gauti OOB duomenys yra tyliai atmesti, bet OOB duomenys vis dar gali būti siunčiami. Jei nuotolinė pusė yra uždaryta, bandymas atliekamas, atstatymo ryšys nepavyksta ir sukelia tam tikrą IOException.

Jei protokole naudosite OOB duomenis, jūsų rida gali skirtis.

6
10 авг. Atsakymas pateikiamas Dėdė Per 10 rug. 2011-08-10 15:28 '11 prie 15:28 2011-08-10 15:28

Tai įdomi tema. Dabar patikrinau java kodą. Mano nuomone, yra dvi skirtingos problemos: pirmasis yra pats RFC TCP, kuris leidžia jums nuotoliniu būdu uždaryti duomenų perdavimo duomenų lizdą pusiau dvipusiu režimu, todėl nuotoliniu būdu uždara jungtis vis dar yra atvira. Pagal RFC, RST neuždaro ryšio, jums reikia siųsti aiškią komandą ABORT; todėl „Java“ leidžia siųsti duomenis per pusę uždaro lizdo

(Yra du būdai uždaryti būseną abiejuose taškuose).

Kita problema yra ta, kad įgyvendinimas teigia, kad toks elgesys yra neprivalomas. Kadangi „Java“ linkusi būti nešiojama, jie įgyvendino geriausią bendrą funkciją. Galbūt problema būtų išsaugoti žemėlapį (OS, pusiau dvipusio įrenginio įdiegimas).

4
01 окт. atsakymas, kurį pateikė Lorenzo Boccaccia 01 spalis 2008-10-01 01:15 '08, 1:15 val. 2008-10-01 01:15

Tai yra „OO“ lizdų klasių „Java“ (ir visų kitų, kuriuos aš pažiūrėjau) trūkumas - nėra prieigos prie pasirinkto sistemos skambučio.

Teisingas atsakymas 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;  tp.tv_usec = 0; select(socket_handle + 1, in, out, err,  if (FD_ISSET(socket_handle, err) {  } 
3
25 янв. Jozuės sausio 25 d. Atsakymas 2009-01-25 00:23 '09 - 0:23 2009-01-25 00:23

„Java IO“ stack tikrai siunčia FIN, kai jis žlunga staigiu atskyrimu. Tai tiesiog nėra prasminga, kad negalite jo aptikti, b / c dauguma klientų siunčia FIN tik tuo atveju, jei jie uždaro ryšį.

... kita priežastis, kodėl aš tikrai nekenčiu „Java“ NIO klasių. Atrodo, kad viskas yra pusė vakarų.

3
06 июля '09 в 6:44 2009-07-06 06:44 atsakymą Seanas pateikė liepos 06 d., 09:44, 2009-07-06 06:44

Čia yra chromo problemos sprendimas. Naudokite SSL;) ir SSL atlieka glaudžią ranką, kai lūžta, kad jums būtų pranešta apie lizdo uždarymą (atrodo, kad dauguma įdiegimų yra tai, kas yra).

2
24 авг. Atsakymą pateikė Dean Hiller 24 rug. 2012-08-24 18:47 '12 at 18:47 pm 2012-08-24 18:47

Šios elgsenos priežastis (kuri nėra būdinga „Java“) yra tai, kad negaunate jokios būsenos informacijos iš TCP kamino. Galų gale, lizdas yra tik dar vienas failų deskriptorius, ir jūs negalite sužinoti, ar nėra realių duomenų, kuriuos reikia perskaityti be bandymo ( select(2) nepadeda, yra tik signalai, kuriuos galite pabandyti be blokavimo ).

Daugiau informacijos rasite dažniausiai užduodami klausimai apie „Unix“ lizdus .

2
01 окт. Atsakymas pateikiamas WMR 01 okt. 2008-10-01 14:09 '08 at 2:09 pm 2008-10-01 14:09

Tik įrašams reikalingas paketų keitimas, kuris leidžia nustatyti ryšio praradimą. Bendra užduotis - naudoti pasirinktį KEEP ALIVE.

0
10 окт. atsakymą pateikė Ray 10 d. 2008-10-10 20:35 '08 at 8:35 pm 2008-10-10 20:35

Kai kalbama apie pusę atidarytas „Java“ lizdas, galite pažvelgti į „ isInputShutdown“ () ir „ isOutputShutdown“ () .

-3
27 окт. JimmyB atsakymas 27 spalis 2011-10-27 13:34 '11 at 13:34 2011-10-27 13:34

Peržiūrėkite kitus klausimus apie „ žymes arba Užduokite klausimą