2010-01-08 43 views
13

Cách thích hợp nhất để phát hiện xem ổ cắm có bị rơi hay không? Hoặc liệu một gói thực sự đã được gửi chưa?Ổ cắm Java và các kết nối bị rơi

Tôi có thư viện để gửi Thông báo đẩy của Apple tới iPhone thông qua các con đường của Apple (available on GitHub). Khách hàng cần phải mở một ổ cắm và gửi một đại diện nhị phân của mỗi tin nhắn; nhưng tiếc là Apple không trả lại bất kỳ sự thừa nhận nào. Kết nối có thể được tái sử dụng để gửi nhiều tin nhắn. Tôi đang sử dụng các kết nối Socket Java đơn giản. Mã có liên quan là:

Socket socket = socket(); // returns an reused open socket, or a new one 
socket.getOutputStream().write(m.marshall()); 
socket.getOutputStream().flush(); 
logger.debug("Message \"{}\" sent", m); 

Trong một số trường hợp, nếu kết nối bị bỏ khi thư được gửi hoặc ngay trước đó; Socket.getOutputStream().write() hoàn tất thành công. Tôi hy vọng đó là do cửa sổ TCP chưa hết.

Có cách nào để tôi có thể biết chắc liệu một gói thực sự có trong mạng hay không? Tôi đã thử nghiệm với hai giải pháp sau:

  1. Chèn thêm socket.getInputStream().read() hoạt động với thời gian chờ 250ms. Điều này buộc một hoạt động đọc thất bại khi kết nối bị ngắt, nhưng bị treo khác 250ms.

  2. đặt kích thước bộ đệm gửi TCP (ví dụ: Socket.setSendBufferSize()) thành kích thước nhị phân thư.

Cả hai phương pháp đều hoạt động, nhưng chúng làm giảm đáng kể chất lượng dịch vụ; thông lượng đi từ 100 tin nhắn/giây đến khoảng 10 tin nhắn/giây nhiều nhất.

Mọi đề xuất?

CẬP NHẬT:

Thử thách bằng nhiều câu hỏi đặt câu hỏi về khả năng mô tả. Tôi đã xây dựng các bài kiểm tra "unit" của hành vi mà tôi mô tả. Xem các trường hợp đơn vị tại Gist 273786.

Cả hai bài kiểm tra đơn vị có hai luồng, một máy chủ và một máy khách. Máy chủ đóng trong khi máy khách đang gửi dữ liệu mà không có bất kỳ lỗi IOException nào được ném ra. Đây là phương pháp chính:

public static void main(String[] args) throws Throwable { 
    final int PORT = 8005; 
    final int FIRST_BUF_SIZE = 5; 

    final Throwable[] errors = new Throwable[1]; 
    final Semaphore serverClosing = new Semaphore(0); 
    final Semaphore messageFlushed = new Semaphore(0); 

    class ServerThread extends Thread { 
     public void run() { 
      try { 
       ServerSocket ssocket = new ServerSocket(PORT); 
       Socket socket = ssocket.accept(); 
       InputStream s = socket.getInputStream(); 
       s.read(new byte[FIRST_BUF_SIZE]); 

       messageFlushed.acquire(); 

       socket.close(); 
       ssocket.close(); 
       System.out.println("Closed socket"); 

       serverClosing.release(); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    class ClientThread extends Thread { 
     public void run() { 
      try { 
       Socket socket = new Socket("localhost", PORT); 
       OutputStream st = socket.getOutputStream(); 
       st.write(new byte[FIRST_BUF_SIZE]); 
       st.flush(); 

       messageFlushed.release(); 
       serverClosing.acquire(1); 

       System.out.println("writing new packets"); 

       // sending more packets while server already 
       // closed connection 
       st.write(32); 
       st.flush(); 
       st.close(); 

       System.out.println("Sent"); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    Thread thread1 = new ServerThread(); 
    Thread thread2 = new ClientThread(); 

    thread1.start(); 
    thread2.start(); 

    thread1.join(); 
    thread2.join(); 

    if (errors[0] != null) 
     throw errors[0]; 
    System.out.println("Run without any errors"); 
} 

[Ngẫu nhiên, tôi cũng có thư viện kiểm tra đồng thời, giúp thiết lập tốt hơn và rõ hơn một chút. Kiểm tra mẫu tại gist là tốt].

Khi chạy tôi nhận được kết quả như sau:

+0

Câu trả lời được cập nhật. –

+0

Như tôi đã nêu trong câu trả lời của tôi, nếu bạn muốn chắc chắn không có lỗi, bạn phải thực hiện một kết nối kết nối duyên dáng. Thực hiện shutdownOutput(), sau đó đọc cho đến khi bạn nhận được EOF, sau đó đóng socket. Kết thúc kết nối duyên dáng trong bản chất nhận được xác nhận từ người ngang hàng rằng nó đã nhận được bạn OK, đó chính là điều bạn muốn. –

Trả lời

17

này không giúp được gì nhiều cho bạn, nhưng về mặt kỹ thuật cả các giải pháp đề xuất của bạn là không chính xác. OutputStream.flush() và bất kỳ cuộc gọi API nào khác mà bạn có thể nghĩ là sẽ không làm những gì bạn cần.

Cách duy nhất đáng tin cậy và di động để xác định xem một gói có được nhận bởi đồng nghiệp không phải chờ xác nhận từ đồng nghiệp. Xác nhận này có thể là một phản ứng thực tế, hoặc tắt một ổ cắm duyên dáng. Kết thúc câu chuyện - có thực sự không có cách nào khác, và điều này không Java cụ thể - nó là lập trình mạng cơ bản.

Nếu đây không phải là kết nối liên tục - có nghĩa là, nếu bạn chỉ gửi một cái gì đó và sau đó đóng kết nối - cách bạn làm là bạn bắt tất cả IOExceptions (bất kỳ lỗi nào cho biết lỗi) và bạn thực hiện một ổ cắm duyên dáng tắt máy:

1. socket.shutdownOutput(); 
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket 
+1

Hoàn toàn chính xác. Nếu lớp ứng dụng không cung cấp cho bạn bất kỳ sự thừa nhận nào, thì giao thức chỉ đơn giản là một giao thức không đáng tin cậy, và bạn sẽ phải chịu đựng điều đó (chỉ biết rằng gói tin đi ra ngoài dây không cho bạn biết đến đầu kia trong một phần, sau khi tất cả). – caf

2

Nếu bạn đang gửi thông tin bằng giao thức TCP/IP tới quả táo, bạn phải nhận được lời cảm ơn. Tuy nhiên bạn đã nêu:

Apple không trả lại bất kỳ sự thừa nhận nào

Ý anh là gì của thành viên này? TCP/IP đảm bảo giao hàng do đó người nhận PHẢI xác nhận đã nhận. Nó không đảm bảo khi giao hàng sẽ diễn ra, tuy nhiên.

Nếu bạn gửi thông báo cho Apple và bạn ngắt kết nối trước khi nhận ACK, không có cách nào để biết bạn có thành công hay không, do đó bạn chỉ cần gửi lại. Nếu đẩy cùng một thông tin hai lần là một vấn đề hoặc không được xử lý đúng cách bởi thiết bị thì có vấn đề. Giải pháp là khắc phục sự cố xử lý thiết bị của thông báo đẩy trùng lặp: bạn không thể làm gì ở phía đẩy.

@Comment Làm rõ/Câu hỏi

Ok. Phần đầu tiên của những gì bạn hiểu là câu trả lời của bạn cho phần thứ hai. Chỉ các gói đã nhận ACKS mới được gửi và nhận đúng cách. Tôi chắc chắn rằng chúng ta có thể nghĩ ra một số chương trình rất phức tạp của việc theo dõi từng gói riêng lẻ, nhưng TCP được cho là trừu tượng lớp này đi và xử lý nó cho bạn. Kết thúc của bạn, bạn chỉ cần phải đối phó với vô số thất bại có thể xảy ra (trong Java nếu bất kỳ trong số này xảy ra một ngoại lệ được nâng lên). Nếu không có ngoại lệ, dữ liệu bạn vừa gửi sẽ được gửi theo giao thức TCP/IP.

Có tình huống mà dữ liệu dường như được "gửi" nhưng không được đảm bảo để nhận được khi không có ngoại lệ nào được nêu ra? Câu trả lời là không.

@Examples

Ví dụ hay, điều này làm rõ mọi thứ một chút. Tôi đã nghĩ rằng một lỗi sẽ bị ném. Trong ví dụ đăng một lỗi được ném vào lần viết thứ hai, nhưng không phải là lỗi đầu tiên. Đây là hành vi thú vị ... và tôi đã không thể tìm thấy nhiều thông tin giải thích tại sao nó hoạt động như thế này. Tuy nhiên, nó giải thích tại sao chúng ta phải phát triển các giao thức mức ứng dụng của riêng chúng ta để xác minh việc phân phối.

Có vẻ như bạn đã chính xác mà không có giao thức để xác nhận họ không đảm bảo rằng thiết bị Apple sẽ nhận được thông báo. Apple cũng chỉ gửi tin nhắn cuối cùng của hàng đợi. Nhìn một chút tại dịch vụ tôi đã có thể xác định dịch vụ này là nhiều hơn cho thuận tiện cho khách hàng, nhưng không thể được sử dụng để đảm bảo dịch vụ và phải được kết hợp với các phương pháp khác. Tôi đọc nó từ nguồn sau.

http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/

Có vẻ như câu trả lời là không có trên hay không, bạn có thể nói chắc chắn. Bạn có thể sử dụng một gói sniffer như Wireshark để cho biết nếu nó đã được gửi đi, nhưng điều này vẫn sẽ không đảm bảo nó đã được nhận và gửi đến thiết bị do bản chất của dịch vụ.

+0

Tôi có nghĩa là Apple không trả lại bất kỳ xác nhận giao thức cụ thể nào ngoài TCP ACK. Tôi cũng không biết làm thế nào tôi có thể cho biết những gói dữ liệu nào được gửi đúng và những gói nào không. – notnoop

+0

Cảm ơn bạn đã cập nhật. Tôi nhận ra tôi nên tập trung vào câu hỏi trên TCP với các ví dụ, thay vì thảo luận về các dịch vụ của Apple. Tôi nghĩ tôi sẽ từ bỏ việc hỗ trợ phát hiện kết nối bị mất ngay bây giờ. – notnoop

3

Sau nhiều rắc rối với các kết nối giảm, tôi chuyển mã của tôi để use the enhanced format, trong đó khá nhiều có nghĩa là bạn thay đổi gói của bạn trông như thế này:

enter image description here

Bằng cách này Apple sẽ không thả một kết nối nếu một lỗi xảy ra, nhưng sẽ viết mã phản hồi vào socket.

Các vấn đề liên quan