Java socket API: จะทราบได้อย่างไรว่าการเชื่อมต่อถูกปิดแล้ว?


94

ฉันพบปัญหาบางอย่างกับ Java socket API ฉันกำลังพยายามแสดงจำนวนผู้เล่นที่เชื่อมต่อกับเกมของฉันอยู่ ง่ายต่อการตรวจสอบว่าผู้เล่นเชื่อมต่อเมื่อใด อย่างไรก็ตามดูเหมือนว่าจะยากโดยไม่จำเป็นที่จะระบุว่าเมื่อใดที่ผู้เล่นตัดการเชื่อมต่อโดยใช้ socket API

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

มีทางออกอื่นอีกไหม?

คำตอบ:


184

ไม่มี TCP API ที่จะบอกสถานะปัจจุบันของการเชื่อมต่อ isConnected()และisClosed()บอกคุณสถานะปัจจุบันของซ็อกเก็ตของคุณ ไม่ใช่สิ่งเดียวกัน.

  1. isConnected()บอกคุณว่าคุณได้เชื่อมต่อซ็อกเก็ตนี้หรือไม่ คุณมีมันจึงคืนค่าจริง

  2. isClosed()บอกคุณว่าคุณได้ปิดซ็อกเก็ตนี้หรือไม่ จนกว่าคุณจะมีมันจะส่งคืนเท็จ

  3. หากเพียร์ปิดการเชื่อมต่ออย่างเป็นระเบียบ

    • read() ผลตอบแทน -1
    • readLine() ผลตอบแทน null
    • readXXX()พ่นEOFExceptionสำหรับ XXX อื่น ๆ

    • การเขียนจะทำให้IOException: 'การเชื่อมต่อรีเซ็ตโดยเพียร์' ในที่สุดอาจมีความล่าช้าในการบัฟเฟอร์

  4. หากการเชื่อมต่อหลุดไม่ว่าด้วยเหตุผลอื่นใดการเขียนจะทำให้เกิดข้อผิดพลาดIOExceptionดังที่กล่าวมาข้างต้นและการอ่านอาจทำสิ่งเดียวกัน

  5. หากเพียร์ยังคงเชื่อมต่ออยู่ แต่ไม่ได้ใช้การเชื่อมต่อสามารถใช้การหมดเวลาอ่านได้

  6. ตรงกันข้ามกับสิ่งที่คุณอาจอ่านจากที่อื่นClosedChannelExceptionไม่ได้บอกคุณเรื่องนี้ [ไม่ทำSocketException: socket closed.] เพียงบอกคุณว่าคุณปิดช่องแล้วจึงใช้ต่อไป กล่าวอีกนัยหนึ่งข้อผิดพลาดในการเขียนโปรแกรมในส่วนของคุณ ไม่ได้แสดงถึงการเชื่อมต่อแบบปิด

  7. จากการทดลองบางอย่างกับ Java 7 บน Windows XP ปรากฏว่าหาก:

    • คุณกำลังเลือก OP_READ
    • select() ส่งคืนค่าที่มากกว่าศูนย์
    • ที่เกี่ยวข้องSelectionKeyไม่ถูกต้องแล้ว ( key.isValid() == false)

    หมายความว่าเพียร์รีเซ็ตการเชื่อมต่อแล้ว อย่างไรก็ตามอาจเป็นเรื่องแปลกสำหรับเวอร์ชันหรือแพลตฟอร์มของ JRE


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

33
@PedroD ตรงกันข้าม: มันจงใจ ชุดโปรโตคอลก่อนหน้านี้เช่น SNA มี 'เสียงสัญญาณโทรเข้า' TCP ได้รับการออกแบบมาเพื่อเอาตัวรอดจากสงครามนิวเคลียร์และการดาวน์และอัพของเราเตอร์ที่ไม่สำคัญมากขึ้นด้วยเหตุนี้จึงไม่มีสิ่งใด ๆ เช่นสัญญาณโทรศัพท์สถานะการเชื่อมต่อ ฯลฯ และยังเป็นสาเหตุที่ TCP keepalive ถูกอธิบายไว้ใน RFCs ว่าเป็นคุณลักษณะที่ถกเถียงกันและเหตุใดจึงปิดโดยค่าเริ่มต้นเสมอ TCP ยังคงอยู่กับเรา SNA? IPX? ISO? ไม่. พวกเขาเข้าใจถูกแล้ว
Marquis of Lorne

4
ฉันไม่เชื่อว่านั่นเป็นข้ออ้างที่ดีในการซ่อนข้อมูลนี้จากเรา การรู้ว่าการเชื่อมต่อหายไปไม่ได้แปลว่าโปรโตคอลจะต้านทานความผิดพลาดได้น้อย แต่ขึ้นอยู่กับสิ่งที่เราทำกับความรู้นั้นเสมอไป ... สำหรับฉันเมธอด isBound และ isConnected จาก java เป็นวิธีการจำลองที่บริสุทธิ์ไม่มีประโยชน์ แต่แสดงความต้องการผู้ฟังเหตุการณ์การเชื่อมต่อ ... แต่ขอย้ำอีกครั้งว่าการรู้ว่าการเชื่อมต่อขาดหายไปไม่ได้ทำให้โปรโตคอลแย่ลง ตอนนี้ถ้าโปรโตคอลเหล่านั้นที่คุณบอกว่าฆ่าการเชื่อมต่อทันทีที่ตรวจพบว่าสูญหายนั่นก็เป็นอีกเรื่องหนึ่ง
PedroD

23
@Pedro คุณไม่เข้าใจ. ไม่ใช่ 'ข้อแก้ตัว' ไม่มีข้อมูลที่จะระงับ ไม่มีสัญญาณโทรศัพท์ TCP ไม่ทราบว่าการเชื่อมต่อล้มเหลวหรือไม่จนกว่าคุณจะพยายามทำบางอย่างกับมัน นั่นคือเกณฑ์การออกแบบพื้นฐาน
Marquis of Lorne

2
@EJP ขอบคุณสำหรับคำตอบของคุณ คุณอาจรู้วิธีตรวจสอบว่าซ็อกเก็ตปิดโดยไม่มีการ "ปิดกั้น" หรือไม่? การใช้ read หรือ readLine จะบล็อก มีวิธีที่จะตระหนักถึงถ้าซ็อกเก็ตอื่น ๆ ได้ปิดด้วยวิธีการที่มีอยู่ก็ดูเหมือนว่าจะกลับมาแทน0 -1
Insumity

9

เป็นแนวทางปฏิบัติทั่วไปในโปรโตคอลการส่งข้อความต่างๆเพื่อให้หัวใจเต้นกัน (ส่งแพ็กเก็ต ping ต่อไป) แพ็กเก็ตไม่จำเป็นต้องมีขนาดใหญ่มาก กลไกการตรวจสอบจะช่วยให้คุณตรวจพบไคลเอนต์ที่ถูกตัดการเชื่อมต่อก่อนที่ TCP จะคำนวณโดยทั่วไป (การหมดเวลาของ TCP สูงกว่ามาก) ส่งโพรบและรอการตอบกลับ 5 วินาทีหากคุณไม่เห็นการตอบกลับสำหรับการพูด 2-3 โพรบที่ตามมาเครื่องเล่นของคุณถูกตัดการเชื่อมต่อ

นอกจากนี้คำถามที่เกี่ยวข้อง


6
เป็น 'การปฏิบัติทั่วไป' ในบางโปรโตคอล ฉันทราบว่า HTTP ซึ่งเป็นโปรโตคอลแอปพลิเคชันที่มีคนใช้มากที่สุดในโลกนี้ไม่มีการทำงานแบบ PING
Marquis of Lorne

2
@ user207421 เนื่องจาก HTTP / 1 เป็นโปรโตคอลแบบ one shot คุณส่งคำขอคุณจะได้รับการตอบกลับ และคุณทำเสร็จแล้ว ซ็อกเก็ตปิดอยู่ ส่วนขยาย Websocket มีการดำเนินการ PING HTTP / 2 ก็เช่นกัน
Michał Zabielski

ฉันแนะนำให้หัวใจเต้นด้วยไบต์เดียวถ้าเป็นไปได้ :)
Stefan Reich

@ MichałZabielskiเป็นเพราะผู้ออกแบบ HTTP ไม่ได้ระบุไว้ คุณไม่รู้ว่าทำไมไม่ HTTP / 1.1 ไม่ใช่โปรโตคอลยิงครั้งเดียว ซ็อกเก็ตไม่ได้ปิด: การเชื่อมต่อ HTTP จะคงอยู่โดยค่าเริ่มต้น FTP, SMTP, POP3, IMAP, TLS, ... ไม่มีการเต้นของหัวใจ
Marquis of Lorne

2

ฉันเห็นคำตอบอื่นที่เพิ่งโพสต์ แต่ฉันคิดว่าคุณโต้ตอบกับลูกค้าที่เล่นเกมของคุณดังนั้นฉันอาจใช้แนวทางอื่น (ในขณะที่ BufferedReader ใช้ได้แน่นอนในบางกรณี)

หากคุณต้องการ ... คุณสามารถมอบหมายความรับผิดชอบ "การลงทะเบียน" ให้กับลูกค้าได้ กล่าวคือคุณจะมีกลุ่มผู้ใช้ที่เชื่อมต่อกับการประทับเวลาในข้อความสุดท้ายที่ได้รับจากแต่ละข้อความ ... หากไคลเอ็นต์หมดเวลาคุณจะบังคับให้ลงทะเบียนไคลเอนต์ใหม่ แต่จะนำไปสู่การเสนอราคาและแนวคิดด้านล่าง

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

หากรหัส Java ของคุณไม่ได้ปิด / ปลดการเชื่อมต่อ Socket คุณจะได้รับแจ้งว่าโฮสต์ระยะไกลปิดการเชื่อมต่อของคุณอย่างไร ท้ายที่สุดแล้ว try / catch ของคุณกำลังทำสิ่งเดียวกับที่โพลเลอร์ฟังเหตุการณ์บนซ็อกเก็ต ACTUAL จะทำ พิจารณาสิ่งต่อไปนี้:

  • ระบบภายในของคุณสามารถปิดซ็อกเก็ตของคุณได้โดยไม่ต้องแจ้งให้คุณทราบ ... นั่นเป็นเพียงการใช้งาน Socket (กล่าวคือไม่ได้สำรวจฮาร์ดแวร์ / ไดรเวอร์ / เฟิร์มแวร์ / อะไรก็ตามสำหรับการเปลี่ยนแปลงสถานะ)
  • ซ็อกเก็ตใหม่ (Proxy p) ... มีหลายฝ่าย (6 ปลายทางจริงๆ) ที่อาจปิดการเชื่อมต่อกับคุณ ...

ฉันคิดว่าคุณสมบัติอย่างหนึ่งของภาษาที่เป็นนามธรรมก็คือคุณแยกออกจากข้อเล็กน้อย ลองนึกถึงคำหลักที่ใช้ใน C # (ลอง / ในที่สุด) สำหรับ SqlConnection s หรืออะไรก็ตาม ... มันเป็นเพียงต้นทุนในการทำธุรกิจ ... ฉันคิดว่าลอง / จับ / ในที่สุดก็เป็นรูปแบบที่ยอมรับและจำเป็นสำหรับการใช้ Socket


ระบบภายในของคุณไม่สามารถ 'ปิดซ็อกเก็ตของคุณ' ได้โดยจะแจ้งให้คุณทราบหรือไม่ก็ได้ คำถามของคุณเริ่มต้น 'ถ้าโค้ด Java ของคุณไม่ได้ปิด / ถอดซ็อกเก็ต ... ?' ก็ไม่สมเหตุสมผลเช่นกัน
Marquis of Lorne

1
อะไรกันแน่ที่ป้องกันไม่ให้ระบบ (เช่น Linux) บังคับปิดซ็อกเก็ต? ยังสามารถทำได้จาก 'gdb' โดยใช้คำสั่ง 'call close'?
Andrey Lebedenko

@AndreyLebedenko ไม่มีอะไรที่จะป้องกันได้อย่างแน่นอน แต่ก็ไม่ได้ทำ
Marquis of Lorne

@EJB แล้วใคร?
Andrey Lebedenko

@AndreyLebedenko ไม่มีใครทำ มีเพียงแอปพลิเคชันเท่านั้นที่สามารถปิดซ็อกเก็ตของตัวเองได้เว้นแต่จะออกโดยไม่ทำเช่นนั้นซึ่งในกรณีนี้ระบบปฏิบัติการจะล้างข้อมูล
Marquis of Lorne

1

ฉันคิดว่านี่เป็นลักษณะของการเชื่อมต่อ tcp ในมาตรฐานนั้นจะใช้เวลาในการส่งสัญญาณประมาณ 6 นาทีก่อนที่เราจะสรุปได้ว่าการเชื่อมต่อขาดหายไป! ดังนั้นฉันไม่คิดว่าคุณจะพบวิธีแก้ปัญหาที่แน่นอนสำหรับปัญหานี้ บางทีวิธีที่ดีกว่าคือการเขียนโค้ดที่มีประโยชน์เพื่อคาดเดาเมื่อเซิร์ฟเวอร์ควรสมมติว่าการเชื่อมต่อผู้ใช้ถูกปิด


2
อาจใช้เวลามากกว่านั้นมากถึงสองชั่วโมง
Marquis of Lorne

0

ดังที่ @ user207421 กล่าวว่าไม่มีทางทราบสถานะปัจจุบันของการเชื่อมต่อเนื่องจาก TCP / IP Protocol Architecture Model ดังนั้นเซิร์ฟเวอร์ต้องสังเกตคุณก่อนที่จะปิดการเชื่อมต่อหรือคุณตรวจสอบด้วยตัวเอง
นี่เป็นตัวอย่างง่ายๆที่แสดงให้เห็นว่าเซิร์ฟเวอร์ปิดซ็อกเก็ตได้อย่างไร:

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 !");

0

ฉันประสบปัญหาที่คล้ายกัน ในกรณีของฉันลูกค้าต้องส่งข้อมูลเป็นระยะ ฉันหวังว่าคุณจะมีความต้องการเดียวกัน จากนั้นฉันตั้งค่า SO_TIMEOUT socket.setSoTimeout(1000 * 60 * 5);ซึ่งจะโยนjava.net.SocketTimeoutExceptionเมื่อเวลาที่กำหนดหมดลง จากนั้นฉันสามารถตรวจจับไคลเอนต์ที่ตายได้อย่างง่ายดาย


-2

นั่นคือวิธีที่ฉันจัดการ

 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);
         }    } 

ถ้า result.code == null


คุณไม่จำเป็นต้องโทรreadLine()ซ้ำสองครั้ง คุณรู้อยู่แล้วว่ามันเป็นโมฆะในelseบล็อก
Marquis of Lorne

-4

บน Linux เมื่อเขียน () เข้าไปในซ็อกเก็ตซึ่งอีกด้านหนึ่งที่คุณไม่รู้จักปิดจะกระตุ้นสัญญาณ / ข้อยกเว้น SIGPIPE แต่คุณต้องการเรียกมัน อย่างไรก็ตามหากคุณไม่ต้องการถูก SIGPIPE จับคุณสามารถใช้ send () ด้วยแฟล็ก MSG_NOSIGNAL การโทร send () จะส่งกลับด้วย -1 และในกรณีนี้คุณสามารถตรวจสอบ errno ซึ่งจะบอกคุณว่าคุณพยายามเขียนท่อที่แตก (ในกรณีนี้คือซ็อกเก็ต) ด้วยค่า EPIPE ซึ่งตาม errno.h เทียบเท่ากับ 32. ในการตอบสนองต่อ EPIPE คุณสามารถย้อนกลับสองครั้งแล้วลองเปิดซ็อกเก็ตใหม่และลองส่งข้อมูลของคุณอีกครั้ง


การsend()โทรจะส่งกลับ -1 ก็ต่อเมื่อข้อมูลขาออกถูกบัฟเฟอร์นานพอที่ตัวจับเวลาการส่งจะหมดอายุ เกือบจะไม่เกิดขึ้นในการส่งครั้งแรกหลังจากการตัดการเชื่อมต่อเนื่องจากการบัฟเฟอร์ที่ปลายทั้งสองข้างและลักษณะแบบอะซิงโครนัสของsend()ใต้ฝากระโปรง
Marquis of Lorne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.