Java socket API: Làm thế nào để biết một kết nối đã bị đóng?


92

Tôi đang gặp phải một số vấn đề với API ổ cắm Java. Tôi đang cố gắng hiển thị số lượng người chơi hiện đang kết nối với trò chơi của tôi. Rất dễ dàng để xác định khi một người chơi đã kết nối. Tuy nhiên, có vẻ như khó xác định khi nào người chơi đã ngắt kết nối bằng API ổ cắm.

Cuộc gọi isConnected()trên một ổ cắm đã bị ngắt kết nối từ xa dường như luôn quay trở lại true. Tương tự, việc gọi isClosed()một ổ cắm đã được đóng từ xa dường như luôn quay trở lại false. Tôi đã đọc điều đó để thực sự xác định xem một ổ cắm đã được đóng hay chưa, dữ liệu phải được ghi vào luồng đầu ra và phải bắt một ngoại lệ. Đây có vẻ như là một cách thực sự không sạch để xử lý tình huống này. Chúng tôi sẽ liên tục phải gửi thư rác trên mạng để biết khi nào ổ cắm đã đóng.

Còn có những giải pháp nào nữa ko?

Câu trả lời:


183

Không có API TCP nào cho bạn biết trạng thái hiện tại của kết nối. isConnected()isClosed()cho bạn biết trạng thái hiện tại của ổ cắm của bạn . Không giống nhau.

  1. isConnected()cho bạn biết liệu bạn đã kết nối ổ cắm này hay chưa . Bạn có, vì vậy nó trả về true.

  2. isClosed()cho bạn biết liệu bạn đã đóng ổ cắm này chưa. Cho đến khi bạn có, nó trả về false.

  3. Nếu đồng nghiệp đã đóng kết nối một cách có trật tự

    • read() trả về -1
    • readLine() trả lại null
    • readXXX()ném EOFExceptioncho bất kỳ XXX nào khác.

    • Một lần ghi sẽ đưa ra một IOException: 'kết nối được thiết lập lại bởi ngang hàng', cuối cùng, có thể bị trễ bộ đệm.

  4. Nếu kết nối bị ngắt vì bất kỳ lý do nào khác IOException, thì cuối cùng, một lần ghi sẽ ném ra như trên, và một lần đọc có thể thực hiện điều tương tự.

  5. Nếu máy ngang hàng vẫn được kết nối nhưng không sử dụng kết nối, có thể sử dụng thời gian chờ đọc.

  6. Trái ngược với những gì bạn có thể đọc ở nơi khác, ClosedChannelExceptionkhông cho bạn biết điều này. [Cũng không SocketException: socket closed.] Nó chỉ cho bạn biết rằng bạn đã đóng kênh và sau đó tiếp tục sử dụng kênh đó. Nói cách khác, lỗi lập trình từ phía bạn. Nó không chỉ ra một kết nối đã đóng .

  7. Theo kết quả của một số thử nghiệm với Java 7 trên Windows XP, có vẻ như nếu:

    • bạn đang chọn OP_READ
    • select() trả về giá trị lớn hơn 0
    • liên kết SelectionKeyđã không hợp lệ ( key.isValid() == false)

    nó có nghĩa là ngang hàng đã thiết lập lại kết nối. Tuy nhiên, điều này có thể là đặc biệt đối với phiên bản hoặc nền tảng JRE.


18
Thật khó tin rằng giao thức TCP, vốn là định hướng kết nối, thậm chí không thể biết được trạng thái kết nối của nó ... Những người nghĩ ra giao thức này có nhắm mắt làm việc lái xe của họ không?
PedroD

31
@PedroD Ngược lại: đó là cố ý. Các bộ giao thức trước đây như SNA có 'âm quay số'. TCP được thiết kế để tồn tại trong một cuộc chiến tranh hạt nhân, và quan trọng hơn là sự thăng trầm của bộ định tuyến: do đó hoàn toàn không có bất cứ thứ gì như âm quay số, trạng thái kết nối, v.v.; và đó cũng là lý do tại sao TCP keepalive được mô tả trong RFC như một tính năng gây tranh cãi và tại sao nó luôn bị tắt theo mặc định. TCP vẫn ở bên chúng tôi. SNA? IPX? ISO? Không phải. Họ đã hiểu đúng.
Marquis of Lorne,

3
Tôi không tin đó là lý do chính đáng để che giấu thông tin này với chúng tôi. Biết rằng kết nối bị mất không nhất thiết có nghĩa là giao thức ít khả năng chống lỗi hơn, nó luôn phụ thuộc vào những gì chúng ta làm với kiến ​​thức đó ... Đối với tôi, phương pháp isBound và isConnected từ java là các phương thức giả lập thuần túy, chúng không có tác dụng , nhưng bày tỏ sự cần thiết của một trình nghe sự kiện kết nối ... Nhưng tôi nhắc lại: biết rằng kết nối bị mất không làm cho giao thức tồi tệ hơn. Bây giờ nếu những giao thức bạn đang nói đã giết kết nối ngay khi họ phát hiện ra nó bị mất, đó là một câu chuyện khác.
PedroD

21
@Pedro Bạn không hiểu. Đó không phải là một 'cái cớ'. Không có thông tin để giữ lại. Không có âm quay số. TCP không biết liệu kết nối có bị lỗi hay không cho đến khi bạn cố gắng làm gì đó với nó. Đó là tiêu chí thiết kế cơ bản.
Hầu tước Lorne

2
@EJP cảm ơn câu trả lời của bạn. Bạn có biết làm thế nào để kiểm tra xem một ổ cắm đã được đóng lại mà không "chặn" không? Sử dụng read hoặc readLine sẽ bị chặn. Có cách nào để nhận ra nếu ổ cắm khác đã đóng bằng phương thức có sẵn, nó dường như quay trở lại 0thay vì -1.
ngu ngốc

9

Thông lệ chung trong các giao thức nhắn tin khác nhau là giữ cho trái tim lẫn nhau (tiếp tục gửi các gói ping) gói tin không cần phải quá lớn. Cơ chế thăm dò sẽ cho phép bạn phát hiện máy khách bị ngắt kết nối ngay cả trước khi TCP tìm ra nó nói chung (thời gian chờ của TCP cao hơn nhiều) Gửi một thăm dò và đợi 5 giây để trả lời, nếu bạn không thấy trả lời cho biết 2-3 thăm dò tiếp theo, máy nghe nhạc của bạn bị ngắt kết nối.

Cũng thế, câu hỏi liên quan


6
Đó là 'thông lệ chung' trong một số giao thức. Tôi lưu ý rằng HTTP, giao thức ứng dụng được sử dụng nhiều nhất trên hành tinh, không có hoạt động PING.
Marquis of Lorne

2
@ user207421 vì HTTP / 1 là giao thức một lần. Bạn gửi yêu cầu, bạn nhận được phản hồi. Và bạn đã hoàn thành. Ổ cắm đã đóng. Phần mở rộng Websocket có hoạt động PING, HTTP / 2 cũng vậy.
Michał Zabielski

Tôi đề nghị heartbeating với một byte đơn nếu có thể :)
Stefan Reich

@ MichałZabielski Đó là bởi vì các nhà thiết kế của HTTP đã không chỉ định nó. Bạn không biết tại sao không. HTTP / 1.1 không phải là một giao thức một lần. Ổ cắm chưa được đóng: Các kết nối HTTP là liên tục theo mặc định. FTP, SMTP, POP3, IMAP, TLS, ... không có nhịp tim.
Marquis of Lorne

2

Tôi thấy câu trả lời khác vừa được đăng, nhưng tôi nghĩ rằng bạn đang tương tác với khách hàng chơi trò chơi của mình, vì vậy tôi có thể đặt ra một cách tiếp cận khác (trong khi BufferedReader chắc chắn có hiệu lực trong một số trường hợp).

Nếu bạn muốn ... bạn có thể ủy thác trách nhiệm "đăng ký" cho máy khách. Tức là bạn sẽ có một tập hợp những người dùng được kết nối với dấu thời gian trên tin nhắn cuối cùng nhận được từ mỗi ... nếu khách hàng hết thời gian chờ, bạn sẽ buộc đăng ký lại khách hàng, nhưng điều đó dẫn đến báo giá và ý tưởng bên dưới.

Tôi đã đọc điều đó để thực sự xác định xem liệu một ổ cắm đã được đóng hay chưa, dữ liệu phải được ghi vào luồng đầu ra và phải bắt một ngoại lệ. Đây có vẻ như là một cách thực sự không sạch để xử lý tình huống này.

Nếu mã Java của bạn không đóng / ngắt kết nối Socket, thì làm thế nào khác bạn sẽ được thông báo rằng máy chủ từ xa đã đóng kết nối của bạn? Cuối cùng, thử / bắt của bạn gần giống như điều mà một người thăm dò ý kiến ​​đang lắng nghe các sự kiện trên ổ cắm ACTUAL sẽ làm. Hãy xem xét những điều sau:

  • hệ thống cục bộ của bạn có thể đóng ổ cắm của bạn mà không cần thông báo cho bạn ... đó chỉ là việc triển khai Socket (tức là nó không thăm dò phần cứng / trình điều khiển / phần sụn / bất cứ điều gì để thay đổi trạng thái).
  • Socket mới (Proxy p) ... có nhiều bên (thực sự là 6 điểm cuối) có thể đóng kết nối với bạn ...

Tôi nghĩ một trong những đặc điểm của ngôn ngữ trừu tượng là bạn được trừu tượng hóa từ những điều vụn vặt. Hãy nghĩ đến việc sử dụng từ khóa trong C # (thử / cuối cùng) cho SqlConnection hoặc bất cứ thứ gì ... đó chỉ là chi phí kinh doanh ... Tôi nghĩ rằng thử / bắt / cuối cùng là mẫu được chấp nhận và cần thiết cho việc sử dụng Socket.


Hệ thống cục bộ của bạn không thể 'đóng ổ cắm của bạn', dù có hoặc không thông báo cho bạn. Câu hỏi của bạn bắt đầu 'nếu mã Java của bạn không đóng / ngắt kết nối ổ cắm ...?' cũng không có ý nghĩa gì.
Marquis of Lorne

1
Chính xác thì điều gì ngăn hệ thống (ví dụ như Linux) buộc đóng ổ cắm? Vẫn có thể làm điều đó từ 'gdb' bằng lệnh 'call close'?
Andrey Lebedenko

@AndreyLebedenko Không có gì 'ngăn cản chính xác nó', nhưng nó không làm điều đó.
Marquis of Lorne,

@EJB sau đó ai làm?
Andrey Lebedenko

@AndreyLebedenko Không ai làm điều đó. Chỉ ứng dụng mới có thể đóng các ổ cắm của chính nó, trừ khi nó thoát ra mà không làm như vậy, trong trường hợp đó, hệ điều hành sẽ dọn dẹp.
Marquis of Lorne,

1

Tôi nghĩ rằng đây là bản chất của kết nối tcp, theo tiêu chuẩn đó, phải mất khoảng 6 phút im lặng trong quá trình truyền trước khi chúng tôi kết luận rằng kết nối ngoài đã biến mất! Vì vậy, tôi không nghĩ rằng bạn có thể tìm thấy một giải pháp chính xác cho vấn đề này. Có lẽ cách tốt hơn là viết một số mã tiện dụng để đoán khi nào máy chủ nên giả sử kết nối người dùng bị đóng.


2
Có thể mất nhiều hơn thế, lên đến hai giờ.
Marquis of Lorne,

0

Như @ user207421 nói rằng không có cách nào để biết trạng thái hiện tại của kết nối do Mô hình kiến ​​trúc giao thức TCP / IP. Vì vậy máy chủ phải thông báo cho bạn trước khi đóng kết nối hoặc bạn tự kiểm tra.
Đây là một ví dụ đơn giản cho thấy cách biết máy chủ đã đóng ổ cắm:

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

Tôi phải đối mặt với vấn đề tương tự. Trong trường hợp của tôi, khách hàng phải gửi dữ liệu định kỳ. Tôi hy vọng bạn có cùng yêu cầu. Sau đó, tôi đặt SO_TIMEOUT socket.setSoTimeout(1000 * 60 * 5);được ném java.net.SocketTimeoutExceptionkhi hết thời gian quy định. Sau đó, tôi có thể phát hiện khách hàng đã chết một cách dễ dàng.


-2

Đó là cách tôi xử lý nó

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

nếu kết quả.code == null


Bạn không cần phải gọi readLine()hai lần. Bạn đã biết nó trống trong elsekhối.
Marquis of Lorne,

-4

Trên Linux khi ghi () vào một socket mà phía bên kia, bạn không biết, đã đóng, sẽ kích hoạt tín hiệu / ngoại lệ SIGPIPE mà bạn muốn gọi nó. Tuy nhiên, nếu bạn không muốn bị SIGPIPE phát hiện, bạn có thể sử dụng send () với cờ MSG_NOSIGNAL. Lệnh gọi send () sẽ trả về với -1 và trong trường hợp này, bạn có thể kiểm tra errno, điều này sẽ cho bạn biết rằng bạn đã cố viết một đường ống bị hỏng (trong trường hợp này là một ổ cắm) với giá trị EPIPE mà theo errno.h tương đương với 32. Để phản ứng với EPIPE, bạn có thể quay lại gấp đôi và cố gắng mở lại ổ cắm và thử gửi lại thông tin của mình.


Cuộc send()gọi sẽ chỉ trả về -1 nếu dữ liệu gửi đi được lưu vào bộ đệm đủ lâu để bộ hẹn giờ gửi hết hạn. Nó gần như chắc chắn sẽ không xảy ra trong lần gửi đầu tiên sau khi ngắt kết nối, do bộ đệm ở cả hai đầu và bản chất không đồng bộ của send()bên dưới.
Marquis of Lorne,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.