จัดการการเชื่อมต่อซ็อกเก็ตใน Java ผ่าน TCP

ฉันเขียนไคลเอนต์ซึ่งโดยพื้นฐานแล้วเพียงแค่เปิดซ็อกเก็ตและส่งเนื้อหาผ่านการเชื่อมต่อ (เนื้อหาเป็นไปตามโปรโตคอล Http)

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

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

บางครั้งคำตอบก็มาถึงและบางครั้งก็ไม่ได้ (สภาพการแข่งขัน)

ฉันจะหน่วงเวลาข้อความ "FIN" เพื่อไม่ให้พลาดการตอบกลับของเซิร์ฟเวอร์ได้อย่างไร

ฉันเพิ่มคลาสที่เกี่ยวข้องแล้ว ฟังก์ชันที่เกี่ยวข้องคือ sendContentOverSocket

public class SocketClient {

           private String hostName;
           private int portNumber;
           private Socket ConnectionSocket;

           public void init(String hostName, int portNumber){
                          this.hostName = hostName;
                          this.portNumber = portNumber;
                          this.ConnectionSocket=createSocketConnection();

           }

           private Socket createSocketConnection() {
                          Socket socket = null;
                          try {
                                         socket = new Socket(this.hostName, this.portNumber);
                                         return socket;
                          } catch (UnknownHostException e) {
                                         e.printStackTrace();
                          } catch (IOException e) {
                                         e.printStackTrace();
                          }

                          return socket;
           }

           public void sendContentOverSocket(String content) {
                          try {
                                         PrintWriter out = new PrintWriter(
                                                                       ConnectionSocket.getOutputStream(), true);
                                         BufferedReader in = new BufferedReader(new InputStreamReader(
                                                                       ConnectionSocket.getInputStream()));
                                         out.print(content);

                                         try {
                                                        Thread.sleep(2000);
                                         } catch (InterruptedException e) {
                                                        // TODO Auto-generated catch block
                                                        e.printStackTrace();
                                         }

                                         out.close();
                                         in.close();                           
                                         ConnectionSocket.close();

                          } catch (IOException e) {
                                         e.printStackTrace();
                          }
           }

}

ป้อนคำอธิบายรูปภาพที่นี่


person mosh    schedule 23.03.2015    source แหล่งที่มา
comment
@ user1274820 ซ็อกเก็ตไม่ 'ปิดโดยมีข้อผิดพลาด' เว้นแต่ว่าคุณได้ทำสิ่งผิด   -  person user207421    schedule 23.03.2015


คำตอบ (4)


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

  • เซิร์ฟเวอร์ปิดปลายซ็อกเก็ตโดยพื้นฐานแล้วบอกว่าฉันจะไม่ส่งอีกต่อไป
  • คุณรอมาสักระยะแล้วคุณเบื่อที่จะรอและตัดสินใจปิดเต้ารับด้วยตัวเอง
  • เงื่อนไขข้อผิดพลาดอื่นๆ แต่โดยปกติแล้วทั้งหมดจะปรากฏเป็นการรับ 0 ไบต์หรือจำนวนลบ
person Philip Stuyck    schedule 23.03.2015
comment
ขอบใจนะ แต่ฉันจะรอสักพักได้ยังไงล่ะ? ฉันพยายามใช้โหมดสลีปหลังจากส่งข้อความและก่อนที่จะปิดการเชื่อมต่อ แต่มันไม่ทำงานอย่างที่คาดไว้ คุณสามารถเห็นในภาพ wireshark ว่าการนอนหลับ (ด้วยเหตุผลบางอย่าง) จะถูกดำเนินการก่อน Http และไม่ใช่ก่อนที่จะส่ง FIN - person mosh; 23.03.2015
comment
คุณควรใช้ select api คุณเลือกด้วยชุดการอ่านบนซ็อกเก็ตและระบุการหมดเวลาเป็น 10 วินาที การเลือกจะส่งคืนหากมีข้อมูลอยู่บนซ็อกเก็ตหรือหากเกิดการหมดเวลา หากเป็นเวลาหมดเวลา คุณจะปิดซ็อกเก็ต - person Philip Stuyck; 23.03.2015
comment
หรือมากกว่า เนื่องจากนี่คือ Socket, คุณจึงตั้งค่าการหมดเวลาการอ่านด้วย setSoTimeout() และจับผลลัพธ์ SocketTimeoutException. - person user207421; 24.03.2015

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

รหัสของคุณมีคุณภาพไม่ดี วิธีการเหล่านั้นทั้งหมดควรเผยแพร่ข้อยกเว้นแทนที่จะจับมันภายในและส่งกลับค่าว่าง

person user207421    schedule 23.03.2015
comment
นี่เป็นเพียงตัวอย่างสั้นๆ ของปัญหา การจัดการข้อยกเว้นได้รับการดูแล ขอบคุณ. - person mosh; 23.03.2015

ในกรณีของ Java 7 เนื่องจากทั้งสามคลาส เช่น Socket, ตัวพิมพ์, BufferedReader ใช้งาน AutoCloseable และจากข้อเท็จจริงที่ว่าคุณต้องการปิดซ็อกเก็ตทันทีหลังจากที่คุณเรียกใช้ sendContentOverSocket(String content) ลองใช้รหัสต่อไปนี้:

    public class SocketClient {

    private String hostName;
    private int portNumber;

    public void init(String hostName, int portNumber) {
        this.hostName = hostName;
        this.portNumber = portNumber;
    }

    public void sendContentOverSocket(String content) {
        try (Socket socket = new Socket(this.hostName, this.portNumber);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true); 
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            out.print(content);
        } catch(IOException e) {
            //Appropriate exception handler
        }
    }
}

ในกรณีนี้ Java จะปิดทรัพยากรทั้งหมดอย่างถูกต้องด้วยตัวเอง

หากคุณใช้ Java 6 หรือเก่ากว่า ให้ลองใช้ try-finally บล็อกแทน:

person fenske    schedule 23.03.2015

แก้ไขแล้ว

ตอนนี้ฉันเข้าใจวิธีการทำงานแล้ว สิ่งที่ฉันขาดหายไปในไคลเอนต์คือความพยายามในการอ่านจากอินพุตสตรีม เมื่อลองอ่าน.

while ((inputFromServer = in.readLine()) != null)

ลูกค้ารอการป้อนข้อมูล สิ่งเดียวที่จะทำลายลูปนี้คือเซิร์ฟเวอร์ปิดการเชื่อมต่อ

หลังจากนั้นคุณสามารถปิดการเชื่อมต่อบนฝั่งไคลเอ็นต์ได้อย่างปลอดภัย ไม่จำเป็นต้องชะลอ FIN และเช่นนั้น...

person mosh    schedule 24.03.2015