เหตุใดจึงเป็นไปไม่ได้โดยไม่ต้องพยายาม I / O เพื่อตรวจพบว่าซ็อกเก็ต TCP ถูกปิดอย่างสง่างามโดยเพียร์


93

จากคำถามล่าสุดฉันสงสัยว่าเหตุใดจึงเป็นไปไม่ได้ใน Java โดยไม่ต้องพยายามอ่าน / เขียนบนซ็อกเก็ต TCP เพื่อตรวจสอบว่าเพียร์ปิดซ็อกเก็ตอย่างสง่างาม? ดูเหมือนว่าจะเป็นเช่นนี้ไม่ว่าจะใช้พรี NIO Socketหรือ NIO SocketChannelก็ตาม

เมื่อเพียร์ปิดการเชื่อมต่อ TCP อย่างสง่างาม TCP สแต็กทั้งสองด้านของการเชื่อมต่อจะรู้เกี่ยวกับข้อเท็จจริง ฝั่งเซิร์ฟเวอร์ (หนึ่งที่บำเพ็ญปิดระบบ) สิ้นสุดลงในรัฐFIN_WAIT2ในขณะที่ฝั่งไคลเอ็นต์ (หนึ่งที่ไม่ชัดเจนตอบสนองต่อการปิดเครื่อง) CLOSE_WAITจะสิ้นสุดลงในรัฐ เหตุใดจึงไม่มีเมธอดในSocketหรือSocketChannelที่สามารถสอบถามสแต็ก TCP เพื่อดูว่าการเชื่อมต่อ TCP พื้นฐานถูกยุติหรือไม่ สแต็ก TCP ไม่ให้ข้อมูลสถานะดังกล่าวหรือไม่? หรือเป็นการตัดสินใจออกแบบเพื่อหลีกเลี่ยงการโทรเข้าเคอร์เนลที่มีราคาแพง?

ด้วยความช่วยเหลือของผู้ใช้ที่ได้โพสต์คำตอบสำหรับคำถามนี้แล้วฉันคิดว่าฉันเห็นว่าปัญหาอาจมาจากไหน ด้านที่ไม่ได้ปิดการเชื่อมต่ออย่างชัดเจนจะสิ้นสุดลงในสถานะ TCP CLOSE_WAITหมายความว่าการเชื่อมต่ออยู่ระหว่างการปิดระบบและรอให้ฝั่งนั้นออกการCLOSEดำเนินการของตัวเอง ฉันคิดว่ามันยุติธรรมพอที่isConnectedจะส่งคืนtrueและisClosedคืนสินค้าfalseแต่ทำไมถึงไม่มีบางอย่างเช่นisClosing?

ด้านล่างนี้คือคลาสทดสอบที่ใช้ซ็อกเก็ต pre-NIO แต่จะได้ผลลัพธ์ที่เหมือนกันโดยใช้ 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);
  }
}

เมื่อไคลเอนต์ทดสอบเชื่อมต่อกับเซิร์ฟเวอร์ทดสอบเอาต์พุตจะยังคงไม่เปลี่ยนแปลงแม้ว่าเซิร์ฟเวอร์จะเริ่มการปิดการเชื่อมต่อ:

connected: true, closed: false
connected: true, closed: false
...

ฉันคิดว่าฉันจะพูดถึง: โปรโตคอล SCTP ไม่มี "ปัญหา" นี้ SCTP ไม่มีสถานะปิดครึ่งหนึ่งเป็น TCP กล่าวอีกนัยหนึ่งคือด้านหนึ่งไม่สามารถส่งข้อมูลต่อไปได้ในขณะที่อีกด้านหนึ่งปิดซ็อกเก็ตการส่ง นั่นควรทำให้สิ่งต่างๆง่ายขึ้น
Tarnay Kálmán

2
เรามีตู้จดหมายสองช่อง (ซ็อกเก็ต) ........................................... ...... ตู้จดหมายส่งเมลถึงกันโดยใช้ RoyalMail (IP) ลืม TCP ............................. .................... ทั้งหมดเรียบร้อยดีและสวยงามกล่องจดหมายสามารถส่ง / รับเมลไปยังอีกคนหนึ่งได้ (เมื่อเร็ว ๆ นี้มีความล่าช้ามาก) ส่งในขณะที่ได้รับไม่มีปัญหา ............. ถ้า Mailbox หนึ่งถูกรถบรรทุกชนแล้วล้มเหลว .... Mailbox อีกอันจะรู้ได้อย่างไร? จะต้องได้รับการแจ้งเตือนจาก Royal Mail ซึ่งในทางกลับกันจะไม่รู้จนกว่าจะลองส่ง / รับเมลจาก Mailbox ที่ล้มเหลวในครั้งต่อไป ...... เอิ่ม .....
divinci

ถ้าคุณจะไม่อ่านจากซ็อกเก็ตหรือเขียนลงในซ็อกเก็ตทำไมคุณถึงสนใจ? และถ้าคุณจะอ่านจากซ็อกเก็ตหรือเขียนลงในซ็อกเก็ตทำไมต้องตรวจสอบเพิ่มเติม? กรณีการใช้งานคืออะไร?
David Schwartz

2
Socket.closeไม่ใช่การปิดที่สง่างาม
user253751

@immibis แน่นอนที่สุดคือการปิดที่สง่างามเว้นแต่จะมีข้อมูลที่ยังไม่ได้อ่านในซ็อกเก็ตรับบัฟเฟอร์หรือคุณยุ่งกับ SO_LINGER
Marquis of Lorne

คำตอบ:


29

ฉันใช้ Sockets บ่อยครั้งโดยส่วนใหญ่ใช้ Selectors และแม้ว่าจะไม่ใช่ผู้เชี่ยวชาญ Network OSI จากความเข้าใจของฉันการเรียกshutdownOutput()ใช้ Socket จะส่งบางสิ่งบนเครือข่าย (FIN) ที่ปลุก Selector ของฉันในอีกด้านหนึ่ง (พฤติกรรมเดียวกันใน C ภาษา). คุณมีการตรวจจับที่นี่: ตรวจจับการดำเนินการอ่านที่จะล้มเหลวเมื่อคุณลองใช้

ในรหัสที่คุณให้การปิดซ็อกเก็ตจะปิดทั้งสตรีมอินพุตและเอาต์พุตโดยไม่มีความเป็นไปได้ในการอ่านข้อมูลที่อาจมีอยู่ดังนั้นจึงสูญเสียข้อมูลเหล่านี้ Socket.close()วิธีการของJava ทำการตัดการเชื่อมต่อที่ "สง่างาม" (ตรงข้ามกับที่ฉันคิดไว้ตอนแรก) โดยข้อมูลที่เหลือในสตรีมเอาต์พุตจะถูกส่งตามด้วย FINเพื่อส่งสัญญาณปิด ครีบจะ ACK'd จากด้านอื่น ๆ ที่เป็นแพ็คเก็ตใด ๆ ที่ปกติจะ1

หากคุณต้องการรอให้อีกด้านปิดซ็อกเก็ตคุณต้องรอ FIN และเพื่อให้บรรลุเป้าหมายนั้นคุณต้องตรวจจับSocket.getInputStream().read() < 0ซึ่งหมายความว่าคุณไม่ควรปิดซ็อกเก็ตของคุณเพราะมันจะปิดInputStreamลง

จากสิ่งที่ฉันทำใน C และตอนนี้ใน Java การบรรลุการปิดที่ซิงโครไนซ์นั้นควรทำดังนี้:

  1. เอาต์พุตซ็อกเก็ตปิดเครื่อง (ส่ง FIN ที่ปลายอีกด้านหนึ่งนี่คือสิ่งสุดท้ายที่ซ็อกเก็ตนี้จะส่งมา) อินพุตยังคงเปิดอยู่เพื่อให้คุณสามารถread()ตรวจจับรีโมทได้close()
  2. อ่านซ็อกเก็ต InputStreamจนกว่าเราจะได้รับการตอบกลับ-FIN จากอีกด้านหนึ่ง (เนื่องจากจะตรวจพบ FIN จึงจะผ่านกระบวนการแยกการเชื่อมต่อที่สง่างามเช่นเดียวกัน) สิ่งนี้มีความสำคัญในระบบปฏิบัติการบางระบบเนื่องจากไม่ได้ปิดซ็อกเก็ตตราบใดที่บัฟเฟอร์ตัวใดตัวหนึ่งยังคงมีข้อมูลอยู่ พวกเขาเรียกว่าซ็อกเก็ต "ghost" และใช้หมายเลขตัวบ่งชี้ในระบบปฏิบัติการ (ซึ่งอาจไม่ใช่ปัญหาอีกต่อไปกับระบบปฏิบัติการสมัยใหม่)
  3. ปิดซ็อกเก็ต (โดยการเรียกSocket.close()หรือปิดInputStreamหรือOutputStream )

ดังที่แสดงในตัวอย่าง Java ต่อไปนี้:

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
}

แน่นอนว่าทั้งสองฝ่ายต้องใช้วิธีการปิดเดียวกันไม่เช่นนั้นส่วนที่ส่งอาจส่งข้อมูลเพียงพอที่จะเก็บไฟล์whileลูปไม่ว่าง (เช่นหากส่วนที่ส่งกำลังส่งข้อมูลเท่านั้นและไม่เคยอ่านเพื่อตรวจจับการยุติการเชื่อมต่อ แต่คุณอาจไม่สามารถควบคุมได้)

ดังที่ @WarrenDew ชี้ให้เห็นในความคิดเห็นของเขาการทิ้งข้อมูลในโปรแกรม (ชั้นแอปพลิเคชัน) จะทำให้เกิดการตัดการเชื่อมต่อที่ไม่สง่างามที่เลเยอร์แอปพลิเคชัน: แม้ว่าข้อมูลทั้งหมดจะได้รับที่เลเยอร์ TCP ( whileลูป) แต่ข้อมูลเหล่านี้จะถูกละทิ้ง

1 : จาก " Fundamental Networking in Java ": ดูรูปที่ 3.3 น. 45 และทั้ง§3.7, หน้า 43-48


Java ทำการปิดอย่างสง่างาม ไม่ใช่ 'โหด'
Marquis of Lorne

2
@EJP "การตัดการเชื่อมต่ออย่างสง่างาม" เป็นการแลกเปลี่ยนเฉพาะที่เกิดขึ้นที่ระดับ TCP ซึ่งลูกค้าควรส่งสัญญาณว่าจะยกเลิกการเชื่อมต่อกับเซิร์ฟเวอร์ซึ่งจะส่งข้อมูลที่เหลือก่อนปิดฝั่ง ส่วน "ส่งข้อมูลที่เหลือ" จะต้องได้รับการจัดการโดยโปรแกรม (แม้ว่าคนส่วนใหญ่จะไม่ส่งอะไรเลยก็ตาม) การโทรsocket.close()นั้น "โหดร้าย" ในลักษณะที่ไม่เคารพการส่งสัญญาณไคลเอ็นต์ / เซิร์ฟเวอร์นี้ เซิร์ฟเวอร์จะได้รับแจ้งเกี่ยวกับการตัดการเชื่อมต่อไคลเอ็นต์เฉพาะเมื่อบัฟเฟอร์เอาต์พุตซ็อกเก็ตของตัวเองเต็ม (เนื่องจากไม่มีข้อมูลใดที่ถูกเชื่อมต่อโดยอีกด้านหนึ่งซึ่งถูกปิด)
Matthieu

ดูMSDNสำหรับข้อมูลเพิ่มเติม
Matthieu

1
@Matthieu หากแอปพลิเคชันของคุณไม่อ่านข้อมูลที่มีอยู่ทั้งหมดนั่นอาจไม่ถูกต้องที่เลเยอร์แอปพลิเคชัน แต่ที่เลเยอร์การขนส่ง TCP ข้อมูลจะยังคงได้รับและการเชื่อมต่อจะสิ้นสุดลงอย่างสง่างาม เช่นเดียวกันหากแอปพลิเคชันของคุณอ่านข้อมูลทั้งหมดจากสตรีมอินพุตและทิ้งข้อมูลนั้นไปเท่านั้น
Warren Dew

2
@LeonidUsov นั่นไม่ถูกต้อง Java read()ส่งคืน -1 เมื่อสิ้นสุดสตรีมและทำต่อไปไม่ว่าคุณจะเรียกมันกี่ครั้งก็ตาม AC read()หรือrecv()ส่งกลับศูนย์เมื่อสิ้นสุดสตรีมและทำต่อไปไม่ว่าคุณจะเรียกมันกี่ครั้งก็ตาม
Marquis of Lorne

18

ฉันคิดว่านี่เป็นคำถามเกี่ยวกับการเขียนโปรแกรมซ็อกเก็ตมากกว่า Java เป็นไปตามประเพณีการเขียนโปรแกรมซ็อกเก็ต

จากWikipedia :

TCP จัดส่งกระแสข้อมูลไบต์ตามลำดับที่เชื่อถือได้จากโปรแกรมหนึ่งบนคอมพิวเตอร์เครื่องหนึ่งไปยังโปรแกรมอื่นบนคอมพิวเตอร์เครื่องอื่น

เมื่อการจับมือเสร็จสิ้น TCP จะไม่สร้างความแตกต่างระหว่างสองจุดสิ้นสุด (ไคลเอนต์และเซิร์ฟเวอร์) คำว่า "ไคลเอนต์" และ "เซิร์ฟเวอร์" ส่วนใหญ่เพื่อความสะดวก ดังนั้น "เซิร์ฟเวอร์" อาจส่งข้อมูลและ "ไคลเอนต์" อาจส่งข้อมูลอื่น ๆ พร้อมกัน

คำว่า "ปิด" ยังทำให้เข้าใจผิด มีเพียงการประกาศ FIN ซึ่งหมายความว่า "ฉันจะไม่ส่งของให้คุณอีก" แต่นี่ไม่ได้หมายความว่าไม่มีแพ็กเก็ตในเที่ยวบินหรืออย่างอื่นไม่มีอะไรจะพูดอีกแล้ว หากคุณใช้หอยทากเป็นชั้นลิงค์ข้อมูลหรือหากแพ็กเก็ตของคุณเดินทางไปต่างเส้นทางอาจเป็นไปได้ว่าผู้รับได้รับแพ็กเก็ตผิดลำดับ TCP รู้วิธีแก้ไขปัญหานี้ให้คุณ

นอกจากนี้คุณในฐานะโปรแกรมอาจไม่มีเวลาตรวจสอบสิ่งที่อยู่ในบัฟเฟอร์ ดังนั้นคุณสามารถตรวจสอบสิ่งที่อยู่ในบัฟเฟอร์ได้ตามความสะดวก สรุปแล้วการใช้งานซ็อกเก็ตในปัจจุบันไม่ได้แย่มากนัก หากมี isPeerClosed () จริงนั่นคือการเรียกพิเศษที่คุณต้องทำทุกครั้งที่คุณต้องการเรียกว่า read


1
ฉันไม่คิดอย่างนั้นคุณสามารถทดสอบสถานะในรหัส C ทั้งบน windows และ linux ได้ !!! Java ด้วยเหตุผลบางประการอาจไม่เปิดเผยบางสิ่งเช่นจะเป็นการดีที่จะเปิดเผยฟังก์ชัน getsockopt ที่อยู่บน windows และ linux ในความเป็นจริงคำตอบด้านล่างมีโค้ด linux C สำหรับฝั่ง linux
Dean Hiller

ฉันไม่คิดว่าการมีเมธอด 'isPeerClosed ()' อย่างใดให้คุณเรียกมันก่อนที่จะพยายามอ่านทุกครั้ง คุณสามารถเรียกมันได้เฉพาะเมื่อคุณต้องการอย่างชัดเจนเท่านั้น ฉันยอมรับว่าการใช้งานซ็อกเก็ตในปัจจุบันไม่ได้แย่มากแม้ว่าคุณจะต้องเขียนลงในสตรีมเอาต์พุตหากคุณต้องการทราบว่าส่วนระยะไกลของซ็อกเก็ตปิดอยู่หรือไม่ เพราะถ้าไม่เป็นเช่นนั้นคุณต้องจัดการกับข้อมูลที่เป็นลายลักษณ์อักษรของคุณในอีกด้านหนึ่งเช่นกันและมันก็มีความสุขมากพอ ๆ กับการนั่งบนเล็บในแนวตั้ง)
Jan Hruby

1
มันจะหมายถึง 'มีแพ็คเก็ตไม่มากในการบิน' ได้รับ FIN หลังจากข้อมูลใด ๆ ในเที่ยวบิน อย่างไรก็ตามสิ่งที่ไม่ได้หมายความว่าเพียร์ปิดซ็อกเก็ตสำหรับอินพุต คุณต้อง** ส่งบางอย่าง * และรับ 'การรีเซ็ตการเชื่อมต่อ' เพื่อตรวจจับสิ่งนั้น FIN อาจหมายถึงการปิดระบบสำหรับเอาต์พุต
Marquis of Lorne

11

sockets API ที่สำคัญไม่มีการแจ้งเตือนดังกล่าว

สแต็ก TCP ที่ส่งจะไม่ส่งบิต FIN จนกว่าจะถึงแพ็กเก็ตสุดท้ายดังนั้นอาจมีข้อมูลจำนวนมากที่ถูกบัฟเฟอร์จากการที่แอปพลิเคชันการส่งปิดซ็อกเก็ตอย่างมีเหตุผลก่อนที่ข้อมูลนั้นจะถูกส่งด้วยซ้ำ ในทำนองเดียวกันข้อมูลที่ถูกบัฟเฟอร์เนื่องจากเครือข่ายนั้นเร็วกว่าแอปพลิเคชันที่รับ (ฉันไม่รู้ว่าคุณกำลังส่งต่อผ่านการเชื่อมต่อที่ช้ากว่า) อาจมีความสำคัญต่อผู้รับและคุณไม่ต้องการให้แอปพลิเคชันที่ได้รับทิ้งไป เพียงเพราะสแต็กได้รับบิต FIN


ในตัวอย่างการทดสอบของฉัน (ฉันควรระบุที่นี่ ... ) ไม่มีข้อมูลที่ส่ง / รับผ่านการเชื่อมต่อตามวัตถุประสงค์ ดังนั้นฉันค่อนข้างมั่นใจว่าสแต็กได้รับ FIN (สง่างาม) หรือ RST (ในบางสถานการณ์ที่ไม่สง่างาม) นอกจากนี้ยังได้รับการยืนยันจาก netstat
Alexander

1
แน่นอน - หากไม่มีสิ่งใดถูกบัฟเฟอร์ FIN จะถูกส่งทันทีบนแพ็คเก็ตที่ว่างเปล่า (ไม่มี payload) หลังจาก FIN แล้วจะไม่มีการส่งแพ็กเก็ตข้อมูลอีกต่อไปเมื่อสิ้นสุดการเชื่อมต่อนั้น (จะยังคงส่ง ACK อะไรก็ตามที่ส่งไป)
Mike Dimmick

1
สิ่งที่เกิดขึ้นคือด้านข้างของการเชื่อมต่อจะจบลงใน <code> CLOSE_WAIT </code> และ <code> FIN_WAIT_2 </code> และอยู่ในสถานะนี้คือ <code> isConcected () </code> และ <code> isClosed () </code> ยังไม่เห็นว่าการเชื่อมต่อถูกยกเลิก
Alexander

ขอบคุณสำหรับคำแนะนำ! ฉันคิดว่าฉันเข้าใจปัญหาดีขึ้นแล้ว ฉันตั้งคำถามให้เจาะจงมากขึ้น (ดูย่อหน้าที่สาม): ทำไมไม่มี "Socket.isClosing" ให้ทดสอบการเชื่อมต่อแบบปิดครึ่งหนึ่ง
Alexander

8

เนื่องจากจนถึงขณะนี้ไม่มีคำตอบใดที่ตอบคำถามได้ครบถ้วนฉันจึงสรุปความเข้าใจในปัญหานี้ในขณะนี้

เมื่อสร้างการเชื่อมต่อ TCP และมีการเรียกเพียร์close()หรือshutdownOutput()บนซ็อกเก็ตซ็อกเก็ตที่อยู่อีกด้านหนึ่งของการเชื่อมต่อจะเปลี่ยนเข้าสู่CLOSE_WAITสถานะ โดยหลักการแล้วเป็นไปได้ที่จะค้นหาจากสแต็ก TCP ว่าซ็อกเก็ตอยู่ในCLOSE_WAITสถานะโดยไม่ต้องเรียกหรือไม่read/recv(เช่นgetsockopt()บน Linux: http://www.developerweb.net/forum/showthread.php?t=4395 ) แต่นั่นไม่ใช่ แบบพกพา

ของ Java Socketคลาสดูเหมือนจะได้รับการออกแบบมาเพื่อให้มีนามธรรมเทียบได้กับซ็อกเก็ต BSD TCP อาจเป็นเพราะนี่คือระดับของนามธรรมที่ผู้คนคุ้นเคยเมื่อเขียนโปรแกรมแอปพลิเคชัน TCP / IP ซ็อกเก็ต BSD เป็นซ็อกเก็ตทั่วไปที่รองรับซ็อกเก็ตอื่นที่ไม่ใช่แค่ INET (เช่น TCP) ดังนั้นจึงไม่มีวิธีแบบพกพาในการค้นหาสถานะ TCP ของซ็อกเก็ต

ไม่มีวิธีใดเหมือนisCloseWait()เพราะผู้คนเคยเขียนโปรแกรมแอปพลิเคชัน TCP ในระดับนามธรรมที่เสนอโดยซ็อกเก็ต BSD ไม่คาดหวังให้ Java มีวิธีการพิเศษใด ๆ


3
Java ไม่สามารถจัดหาวิธีการพิเศษใด ๆ แบบพกพาได้ บางทีพวกเขาอาจสร้างเมธอด isCloseWait () ซึ่งจะส่งคืนเท็จหากแพลตฟอร์มไม่รองรับ แต่จะมีโปรแกรมเมอร์กี่คนที่ถูก gotcha กัดหากพวกเขาทดสอบบนแพลตฟอร์มที่รองรับเท่านั้น?
ephemient

1
ดูเหมือนว่ามันจะพกพาได้สำหรับฉัน ... windows มีmsdn.microsoft.com/en-us/library/windows/desktop/…และ linux นี้pubs.opengroup.org/onlinepubs/009695399/functions/…
Dean Hiller

1
ไม่ใช่ว่าโปรแกรมเมอร์จะเคยชินกับมัน นั่นคืออินเทอร์เฟซซ็อกเก็ตมีประโยชน์สำหรับโปรแกรมเมอร์ โปรดทราบว่าการใช้ซ็อกเก็ตที่เป็นนามธรรมมากกว่าโปรโตคอล TCP
Warren Dew

ไม่มีวิธีการใดใน Java isCloseWait()เนื่องจากไม่ได้รับการสนับสนุนในทุกแพลตฟอร์ม
Marquis of Lorne

โปรโตคอลระบุ (RFC 1413) ช่วยให้เซิร์ฟเวอร์สามารถเปิดการเชื่อมต่อไว้ได้หลังจากส่งการตอบกลับหรือปิดโดยไม่ส่งข้อมูลเพิ่มเติม ไคลเอ็นต์ Java Ident อาจเลือกที่จะเปิดการเชื่อมต่อไว้เพื่อหลีกเลี่ยงการจับมือ 3 ทางในการค้นหาครั้งต่อไป แต่จะรู้ได้อย่างไรว่าการเชื่อมต่อยังเปิดอยู่ ควรลองตอบสนองต่อข้อผิดพลาดโดยเปิดการเชื่อมต่ออีกครั้งหรือไม่ หรือว่าเป็นข้อผิดพลาดในการออกแบบโปรโตคอล?
david

7

การตรวจจับว่าด้านรีโมตของการเชื่อมต่อซ็อกเก็ต (TCP) ปิดลงหรือไม่สามารถทำได้ด้วยเมธอด java.net.Socket.sendUrgentData (int) และการจับ IOException จะพ่นหากด้านรีโมตไม่ทำงาน สิ่งนี้ได้รับการทดสอบระหว่าง Java-Java และ Java-C

วิธีนี้หลีกเลี่ยงปัญหาในการออกแบบโปรโตคอลการสื่อสารเพื่อใช้กลไกการ Ping บางประเภท ด้วยการปิดใช้งาน OOBInline บนซ็อกเก็ต (setOOBInline (false) ข้อมูล OOB ใด ๆ ที่ได้รับจะถูกละทิ้งโดยไม่โต้ตอบ แต่ยังสามารถส่งข้อมูล OOB ได้หากด้านระยะไกลปิดอยู่การรีเซ็ตการเชื่อมต่อจะล้มเหลวและทำให้ IOException บางส่วนถูกโยนทิ้ง .

หากคุณใช้ข้อมูล OOB จริงในโปรโตคอลระยะทางของคุณอาจแตกต่างกันไป


4

Java IO stack จะส่ง FIN อย่างแน่นอนเมื่อถูกทำลายจากการฉีกขาดอย่างกะทันหัน มันไม่สมเหตุสมผลเลยที่คุณไม่สามารถตรวจจับสิ่งนี้ได้ b / c ไคลเอนต์ส่วนใหญ่จะส่ง FIN ก็ต่อเมื่อพวกเขากำลังปิดการเชื่อมต่อ

... อีกเหตุผลหนึ่งที่ฉันเริ่มเกลียดคลาส NIO Java จริงๆ ดูเหมือนว่าทุกอย่างจะเป็นแบบครึ่งๆกลางๆ


1
นอกจากนี้ดูเหมือนว่าฉันจะได้รับและสิ้นสุดสตรีมเมื่ออ่าน (-1 return) เมื่อมี FIN เท่านั้น นี่เป็นวิธีเดียวที่ฉันเห็นเพื่อตรวจจับการปิดเครื่องที่ด้านอ่าน

3
คุณสามารถตรวจจับได้ คุณจะได้รับ EOS เมื่ออ่าน และ Java ไม่ส่ง FIN TCP ทำเช่นนั้น Java ไม่ได้ใช้ TCP / IP เพียงแค่ใช้การใช้งานแพลตฟอร์ม
Marquis of Lorne

4

เป็นหัวข้อที่น่าสนใจ ตอนนี้ฉันขุดโค้ด java เพื่อตรวจสอบ จากการค้นพบของฉันมีปัญหาที่แตกต่างกันสองประการ: ประการแรกคือ TCP RFC เองซึ่งช่วยให้ซ็อกเก็ตที่ปิดจากระยะไกลสามารถส่งข้อมูลแบบ half-duplex ได้ดังนั้นซ็อกเก็ตที่ปิดจากระยะไกลจึงยังคงเปิดอยู่ครึ่งหนึ่ง ตาม RFC RST ไม่ได้ปิดการเชื่อมต่อคุณต้องส่งคำสั่ง ABORT อย่างชัดเจน ดังนั้น Java จึงอนุญาตให้ส่งข้อมูลผ่านซ็อกเก็ตปิดครึ่งหนึ่ง

(มีสองวิธีในการอ่านสถานะปิดที่จุดสิ้นสุดทั้งสอง)

ปัญหาอื่น ๆ คือการใช้งานบอกว่าพฤติกรรมนี้เป็นทางเลือก เนื่องจาก Java มุ่งมั่นที่จะพกพาพวกเขาจึงใช้คุณลักษณะทั่วไปที่ดีที่สุด ฉันเดาว่าการดูแลรักษาแผนที่ (OS, การใช้ half duplex) อาจเป็นปัญหาได้


1
ฉันคิดว่าคุณกำลังพูดถึง RFC 793 ( faqs.org/rfcs/rfc793.html ) ตอนที่ 3.5 การปิดการเชื่อมต่อ ฉันไม่แน่ใจว่ามันอธิบายปัญหาได้เพราะทั้งสองฝ่ายทำการปิดการเชื่อมต่ออย่างสมบูรณ์แบบและจบลงในสถานะที่ไม่ควรส่ง / รับข้อมูลใด ๆ
Alexander

ขึ้นอยู่กับ. คุณเห็น FIN กี่ตัวบนซ็อกเก็ต? นอกจากนี้อาจเป็นปัญหาเฉพาะของแพลตฟอร์ม: บางที windows ตอบกลับ FIN แต่ละครั้งด้วย FIN และการเชื่อมต่อถูกปิดทั้งสองด้าน แต่ระบบปฏิบัติการอื่นอาจไม่ทำงานในลักษณะนี้และนี่คือจุดที่เกิดปัญหา 2
Lorenzo Boccaccia

1
ไม่น่าเสียดายที่ไม่เป็นเช่นนั้น isOutputShutdown และ isInputShutdown เป็นสิ่งแรกที่ทุกคนพยายามเมื่อเผชิญกับ "การค้นพบ" นี้ แต่ทั้งสองวิธีกลับเป็นเท็จ ฉันเพิ่งทดสอบบน Windows XP และ Linux 2.6 ค่าส่งคืนของทั้ง 4 วิธียังคงเหมือนเดิมแม้จะพยายามอ่านแล้วก็ตาม
Alexander

1
สำหรับเร็กคอร์ดนี่ไม่ใช่ฮาล์ฟดูเพล็กซ์ ฮาล์ฟดูเพล็กซ์หมายถึงเพียงด้านเดียวที่สามารถส่งได้ในเวลาเดียวกัน ทั้งสองฝ่ายยังสามารถส่งได้
rbp

1
isInputShutdown และ isOutputShutdown ทดสอบจุดสิ้นสุดภายในของการเชื่อมต่อ - เป็นการทดสอบเพื่อดูว่าคุณได้เรียกว่า shutdownInput หรือ shutdownOutput บน Socket นี้หรือไม่ พวกเขาไม่ได้บอกอะไรคุณเกี่ยวกับการสิ้นสุดระยะไกลของการเชื่อมต่อ
Mike Dimmick

3

นี่เป็นข้อบกพร่องของ Java's (และอื่น ๆ ทั้งหมดที่ฉันได้ดู) คลาส OO ของซ็อกเก็ต - ไม่มีการเข้าถึงการเรียกระบบที่เลือก

คำตอบที่ถูกต้องใน 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 */  
}  

คุณสามารถทำสิ่งเดียวกันกับgetsocketop(... SOL_SOCKET, SO_ERROR, ...).
David Schwartz

1
ชุดคำอธิบายไฟล์ข้อผิดพลาดจะไม่ระบุการเชื่อมต่อแบบปิด โปรดอ่านคู่มือที่เลือก: 'exceptfds - ชุดนี้มีไว้สำหรับ "เงื่อนไขพิเศษ" ในทางปฏิบัติมีเงื่อนไข excetional เพียงอย่างเดียวเท่านั้น: ความพร้อมใช้งานของข้อมูลนอกแบนด์ (OOB) สำหรับการอ่านจากซ็อกเก็ต TCP ' FIN ไม่ใช่ข้อมูล OOB
ม.ค. Wrobel

1
คุณสามารถใช้คลาส 'Selector' เพื่อเข้าถึง syscall 'select ()' ใช้ NIO แม้ว่า
Matthieu

1
ไม่มีอะไรพิเศษเกี่ยวกับการเชื่อมต่อที่ถูกปิดโดยอีกด้าน
David Schwartz

1
แพลตฟอร์มจำนวนมากรวมทั้ง Java, ทำให้เข้าถึงเลือก () สายระบบ
Marquis of Lorne

2

นี่คือวิธีแก้ปัญหาแบบง่อย ๆ ใช้ SSL;) และ SSL จะทำการจับมือกันอย่างใกล้ชิดในการฉีกขาดดังนั้นคุณจะได้รับแจ้งว่าซ็อกเก็ตถูกปิด (การใช้งานส่วนใหญ่ดูเหมือนจะเป็นการจัดการการจับมือที่เหมาะสม)


2
จะได้รับ "แจ้ง" ซ็อกเก็ตที่ปิดเมื่อใช้ SSL ใน java ได้อย่างไร?
Dave B

2

สาเหตุของพฤติกรรมนี้ (ซึ่งไม่ใช่เฉพาะ Java) คือความจริงที่ว่าคุณไม่ได้รับข้อมูลสถานะใด ๆ จากสแต็ก TCP ท้ายที่สุดซ็อกเก็ตเป็นเพียงตัวจัดการไฟล์อื่นและคุณไม่สามารถตรวจสอบได้ว่ามีข้อมูลจริงให้อ่านโดยไม่ต้องพยายาม (select(2)จะไม่ช่วยอะไรที่นั่นเพียง แต่ส่งสัญญาณว่าคุณสามารถลองได้โดยไม่ต้องปิดกั้น)

สำหรับข้อมูลเพิ่มเติมโปรดดูที่Unix ซ็อกเก็ตคำถามที่พบบ่อย


2
ซ็อกเก็ต REALbasic (บน MacOS X และ Linux) ใช้ซ็อกเก็ต BSD แต่ RB สามารถให้ข้อผิดพลาด 102 เมื่อการเชื่อมต่อหลุดจากปลายอีกด้านหนึ่ง ดังนั้นฉันเห็นด้วยกับโปสเตอร์ต้นฉบับนี่น่าจะเป็นไปได้และเป็นเรื่องง่อยที่ Java (และ Cocoa) ไม่ได้จัดเตรียมไว้ให้
Joe Strout

1
@JoeStrout RB สามารถทำได้ก็ต่อเมื่อคุณทำ I / O บางอย่างเท่านั้น ไม่มี API ที่จะทำให้คุณมีสถานะของการเชื่อมต่อโดยไม่ต้องทำ I / O ระยะเวลา นี่ไม่ใช่ข้อบกพร่องของ Java อันที่จริงแล้วเกิดจากการไม่มี 'เสียงสัญญาณโทรเข้า' ใน TCP ซึ่งเป็นคุณสมบัติการออกแบบโดยเจตนา
Marquis of Lorne

select() บอกคุณว่ามีข้อมูลหรือ EOS ให้อ่านได้โดยไม่ต้องปิดกั้น 'สัญญาณที่คุณสามารถลองได้โดยไม่ต้องปิดกั้น' นั้นไม่มีความหมาย หากคุณอยู่ในโหมดไม่ปิดกั้นคุณสามารถลองได้ตลอดเวลาโดยไม่ต้องปิดกั้น select()ถูกขับเคลื่อนโดยข้อมูลในซ็อกเก็ตรับบัฟเฟอร์หรือ FIN ที่รอดำเนินการหรือพื้นที่ในบัฟเฟอร์การส่งซ็อกเก็ต
Marquis of Lorne

@EJP เรื่องอะไรgetsockopt(SO_ERROR)? ในความเป็นจริงแม้getpeernameจะบอกคุณว่าซ็อกเก็ตยังคงเชื่อมต่ออยู่
David Schwartz

0

การเขียนเฉพาะต้องการให้มีการแลกเปลี่ยนแพ็กเก็ตซึ่งทำให้สามารถกำหนดการสูญเสียการเชื่อมต่อได้ วิธีแก้ไขทั่วไปคือการใช้ตัวเลือก KEEP ALIVE


ฉันคิดว่าจุดสิ้นสุดได้รับอนุญาตให้เริ่มต้นการปิดการเชื่อมต่อที่สง่างามโดยการส่งแพ็คเก็ตพร้อมชุด FIN โดยไม่ต้องเขียนเพย์โหลดใด ๆ
Alexander

@Alexander แน่นอนว่าเป็นเช่นนั้น แต่ไม่เกี่ยวข้องกับคำตอบนี้ซึ่งเกี่ยวกับการตรวจจับการเชื่อมต่อน้อยลง
Marquis of Lorne

-3

เมื่อมันมาถึงการจัดการกับครึ่งเปิดซ็อกเก็ต Java หนึ่งอาจต้องการที่จะมีลักษณะที่ isInputShutdown ()และisOutputShutdown ()


ไม่นั่นบอกเพียงว่าคุณเรียกว่าอะไรไม่ใช่สิ่งที่เพื่อนเรียก
Marquis of Lorne

สนใจที่จะแบ่งปันแหล่งที่มาของคุณสำหรับข้อความนั้นหรือไม่?
JimmyB

3
สนใจที่จะแบ่งปันแหล่งที่มาของคุณในทางตรงกันข้ามหรือไม่? มันเป็นคำสั่งของคุณ ถ้าคุณมีหลักฐานก็ขอเถอะ ฉันยืนยันว่าคุณไม่ถูกต้อง ทำการทดลองและพิสูจน์ว่าฉันผิด
Marquis of Lorne

สามปีต่อมาและไม่มีการทดลอง QED
Marquis of Lorne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.