2014-04-08 34 views
7

Tôi gặp rất nhiều khó khăn trong việc tìm hiểu sự khác biệt về cách Java xử lý Ổ cắm trên Windows và Linux - Đặc biệt khi một trong các bên (Máy khách hoặc Máy chủ) đóng kết nối đột ngột.Sự khác biệt về Ổ cắm Java giữa Windows và Linux - Cách xử lý chúng?

Tôi đã viết như sau rất đơn giản Server và Client lớp để giữ quan điểm của tôi cũng đơn giản, khách quan và dễ dàng để bạn có thể hiểu càng tốt:

SimpleClient.java:

import java.io.BufferedReader; 
import java.io.BufferedWriter; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 

import java.net.Socket; 

public class SimpleClient { 

    public static void main(String args[]) { 
     try { 
      Socket client_socket = new Socket("127.0.0.1", 9009); 

      // Used to read from a terminal input: 
      BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 

      // Used for client/server communication: 
      BufferedReader in = new BufferedReader(new InputStreamReader(client_socket.getInputStream())); 
      BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client_socket.getOutputStream())); 

      while(true) { 
       System.out.print("Command: "); 
       String msg = br.readLine(); 

       // Send: 
       out.write(msg); 
       out.newLine(); 
       out.flush(); 

       // Receive: 
       int ifirst_char; 
       char first_char; 

       if((ifirst_char = in.read()) == -1) { // Server Closed 
        System.out.println("Server was closed on the other side."); 

        break; 
       } 

       first_char = (char) ifirst_char; 

       msg = String.valueOf(first_char); 

       msg += in.readLine(); 

       // Shows the message received from the server on the screen: 
       System.out.println(msg); 
      } 
     } 
     catch(Exception e) { 
      e.printStackTrace(); 
     } 

    } 
} 


SimpleServer.java:

import java.io.BufferedReader; 
import java.io.BufferedWriter; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 

import java.net.ServerSocket; 
import java.net.Socket; 

public class SimpleServer { 

    public static void main(String args[]) { 
     try { 
      ServerSocket server_socket = new ServerSocket(9009); 

      Socket client_socket = server_socket.accept(); 

      while(true) { 
       BufferedReader in = new BufferedReader(new InputStreamReader(client_socket.getInputStream())); 
       BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client_socket.getOutputStream())); 

       // Receive: 
       int ifirst_char; 
       char first_char; 

       if((ifirst_char = in.read()) == -1) { // Client Closed 
        System.out.println("Client was closed on the other side."); 

        break; 
       } 

       first_char = (char) ifirst_char; 

       String msg = msg = String.valueOf(first_char); 

       msg += in.readLine(); 

       msg = "Server Received: " + msg; 

       // Send: 
       out.write(msg); 
       out.newLine(); 
       out.flush(); 
      } 
     } 
     catch(Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 


Dĩ nhiên tôi có thể thực hiện một mã cho đúng cách tắt máy khách hoặc máy chủ, nhưng khách quan, như tôi đã nói, là để mô phỏng một shutdown đột ngột ở hai bên, nơi không có "mã ngắt kết nối "có thể được gửi hoặc nhận. Đó là lý do tại sao tôi tạo ra 2 lớp học rất đơn giản này.

Trên Linux, nó chạy khá tốt:

$ java SimpleClient 
Command: echo 
Server Received: echo 
Command: test 
Server Received: test 
Command: (server now was closed on the other side) 
Server was closed on the other side. 
$ 


Trên Windows, tuy nhiên:

C:\simplesocket>java SimpleClient 
Command: echo 
Server Received: echo 
Command: test 
Server Received: test 
Command: (server now was closed on the other side) 
java.net.SocketException: Connection reset by peer: socket write error 
     at java.net.SocketOutputStream.socketWrite0(Native Method) 
     at java.net.SocketOutputStream.socketWrite(Unknown Source) 
     at java.net.SocketOutputStream.write(Unknown Source) 
     at sun.nio.cs.StreamEncoder.writeBytes(Unknown Source) 
     at sun.nio.cs.StreamEncoder.implFlushBuffer(Unknown Source) 
     at sun.nio.cs.StreamEncoder.implFlush(Unknown Source) 
     at sun.nio.cs.StreamEncoder.flush(Unknown Source) 
     at java.io.OutputStreamWriter.flush(Unknown Source) 
     at java.io.BufferedWriter.flush(Unknown Source) 
     at SimpleClient.main(SimpleClient.java:32) 


Hãy nói rằng tôi cố gắng bỏ qua ngoại lệ này bằng cách sửa đổi những dòng sau vào SimpleClient của tôi .java:

// Send: 
try { 
    out.write(msg); 
    out.newLine(); 
    out.flush(); 
    } 
catch(Exception e) {} 


ngoại lệ khác là ném:

C:\simplesocket>java SimpleClient 
Command: echo 
Server Received: echo 
Command: test 
Server Received: test 
Command: (server now was closed on the other side) 
java.net.SocketException: Connection reset 
     at java.net.SocketInputStream.read(Unknown Source) 
     at java.net.SocketInputStream.read(Unknown Source) 
     at sun.nio.cs.StreamDecoder.readBytes(Unknown Source) 
     at sun.nio.cs.StreamDecoder.implRead(Unknown Source) 
     at sun.nio.cs.StreamDecoder.read(Unknown Source) 
     at java.io.InputStreamReader.read(Unknown Source) 
     at java.io.BufferedReader.fill(Unknown Source) 
     at java.io.BufferedReader.read(Unknown Source) 
     at SimpleClient.main(SimpleClient.java:42) 


Tôi không biết nếu các dòng tương ứng trên mã sẽ là những người chỉ ra bằng các trường hợp ngoại lệ, nhưng một trong những đầu tiên được ném vào out.flush () và ô thứ hai trên in.read(). Vì vậy, về cơ bản, như bạn có thể thấy trên Linux, ngay cả sau khi đột ngột đóng máy chủ:

1. Nó không ném một ngoại lệ khi tôi cố gắng gửi dữ liệu.
2. Và quan trọng hơn, khi tôi cố gắng nhận nó, char đầu tiên là "-1" và nhận được chính xác.

Trên Windows, nó ném ngoại lệ cả khi gửi và quan trọng hơn khi nhận - khi gọi phương thức read() - Tôi không thể lấy mã "cuối dòng" (-1).

Dẫn đến một số câu hỏi:

1. Tại sao có sự khác biệt lớn trên Windows x Linux? Tại sao trên Linux những ngoại lệ này không được ném trong khi trên Windows chúng?

2. Không nên Java, với tất cả các phẩm chất đa nền tảng của nó, cố gắng giảm thiểu sự khác biệt khi chạy trong cả hai Hệ thống? (bằng cách tôi đang sử dụng JDK 7 trên cả hai)

3. Có cách nào thay đổi mã để tắt đột ngột và làm cho nó hoạt động tốt hơn "giống như Linux" trên Windows hay không Ngoại lệ và nhận được -1 trên in.read của tôi() ??

4. Nếu không, bất kỳ API bên ngoài nào được đề xuất?


Tôi đã cố gắng tìm kiếm trên web hàng giờ về chủ đề cụ thể này nhưng không thành công.

Tôi cũng đã thử nhiều giải pháp như phương pháp gọi như isConnected(), isBound(), isClosed(), vv trong client_socket về phía khách hàng nhưng không thành công. Họ luôn nói rằng có một kết nối hoạt động và không có vấn đề với nó, ngay cả sau khi tắt máy chủ.

Hy vọng rằng ai đó sẽ dành thời gian để trả lời ít nhất một trong những câu hỏi này.

Bạn có lời cảm ơn chân thành nhất trước mọi câu trả lời.

+0

Phương thức isXXX() cho bạn biết trạng thái của socket, không phải về trạng thái kết nối. Sự khác biệt chính giữa ổ cắm Linux và ổ cắm Windows là kích thước của bộ đệm, và điều đó một mình là đủ để giải thích mọi thứ bạn thấy ở đây. Và nó không phải là một cái gì đó Java cố gắng để mịn hơn. – EJP

Trả lời

6

Mã của bạn không làm bất kỳ đóng, vì vậy tôi giả sử bạn thực sự có nghĩa là một quy trình điểm cuối bị ngừng lại cũng bị giết.

Unix socket sd là "chỉ" fd và khi quá trình Unix kết thúc mà không đóng fd, bao gồm trường hợp JVM dừng và bạn chưa gọi gần (hoặc shutdown-WR), fd sẽ bị đóng bởi hệ điều hành, mà cho TCP socket nào (ít nhất là cố) bình thường hay còn gọi là duyên dáng: FIN/ACK trao đổi với FIN-WAIT và TIME-WAIT. Cách duy nhất tôi biết để làm cho Unix socket làm không cần thiết ở mức TCP (RST) là thiết lập nán lại thành 0 trước khi đóng (hoặc rõ ràng hoặc bằng cách thoát). Nó cũng có thể và không phổ biến cho một hộp giữa để buộc phải ngắt kết nối của bạn với RST; ví dụ tôi đã thấy tường lửa RST bạn sau 15 phút không hoạt động. Tôi cũng hiếm khi thấy hộp trung gian giả mạo FIN, hoặc cố gắng nhưng làm điều đó sai.

Ổ cắm Windows (WinSock) là một API khác với tệp. Nếu một quá trình Windows kết thúc mà không gọi closesocket (tương tự như nhưng tách biệt khỏi đóng) hoặc ít nhất là shutdown-WR, Winsock thực hiện RST. Để có được sự gần gũi duyên dáng (FIN) trên Windows bạn (qua JVM) phải gọi một trong số đó. JVM có lẽ có thể theo dõi java.net.Sockets (nhưng không phải bất kỳ trong JNI) và làm điều này cho bạn trên lối ra JVM, nhưng nó không; bạn có thể yêu cầu tăng cường. Thậm chí điều đó có thể không hoạt động nếu bạn bên ngoài giết nó bằng TaskMgr hoặc tương tự, và có thể không hoạt động ngay nếu bạn nhấn lỗi JVM: JVM cố gắng bắt lỗi và đưa ra một minidump, đây sẽ là nơi để thử để làm sạch ổ cắm , nhưng nếu có lỗi JVM nó có thể thất bại một lần nữa - và hầu hết các lỗi JVM nhất là do lỗi JVM.

Nếu đủ để xử lý lỗi mã (rò rỉ) và tín hiệu nhưng không phải lỗi JVM và lỗi, bạn chỉ có thể phân lớp Socket để nếu lực (duyên dáng) đóng .finalize và khi thoát bằng Runtime.addShutdownHook và sử dụng thay vào đó.

Trong cả hai ổ cắm Unix hoặc Windows, FIN nhận được coi là cuối tệp, giống như bất kỳ tệp nào khác ví dụ như tệp đĩa. RST đã nhận được trả về dưới dạng lỗi [WSA] ECONNRESET đối với JVM, điều này làm tăng ngoại lệ.Sẽ không tốt nếu bạn giấu sự khác biệt này bởi vì đối với các ứng dụng không phải của bạn, điều đó có thể là quan trọng - đủ để một số giao thức phải được thay đổi để ngăn chặn giả mạo FIN là lỗ hổng bảo mật đáng chú ý là SSLv2 và HTTP/0.9 .

Nếu bạn cũng xem xét trường hợp các đồng đẳng hệ thống thất bại (không chỉ là JVM), hoặc một số bộ phận của mạng thất bại, hoặc giao diện mạng của bạn không thành công, ngoại lệ bạn có thể nhận được rất đa dạng khá hơn một chút. IMHO không cố gắng xử lý chúng, chỉ báo cáo những gì bạn thấy và để cho các hệ thống quản trị mạng và netadmins sắp xếp nó ra. Tôi đã nhìn thấy trường hợp một lập trình viên có ngoại lệ X do vấn đề P trong điều kiện phòng thí nghiệm và mã hóa cho điều đó, nhưng trong trường hợp ngoại lệ X thực tế xảy ra vì lý do rất khác nhau và xử lý "hữu ích" thực sự làm cho khó giải quyết vấn đề.

Bên cạnh: máy chủ nên tạo BufferedReader trước khi không nằm trong vòng lặp while (true) do-a-line; nếu bạn từng nhận/muốn một khách hàng gửi nhiều dòng cùng một lúc, mã được hiển thị sẽ mất dữ liệu. Bạn không cần tóc đó nếu first_char == - 1 khác chuyển thành Chuỗi; chỉ cần sử dụng in.readLine, nó trả về null trong chính xác trường hợp tương tự trong đó trả về in.read ban đầu -1, cho (TCP) Socket là khi FIN được nhận. Ngược lại, readLine của khách hàng từ System.in phải được kiểm tra; nếu ai đó gõ^Z hoặc^D hoặc bất cứ điều gì bạn sẽ nhận được NPE.

+0

Cảm ơn bạn rất nhiều vì câu trả lời và dành thời gian giải thích mọi thứ xảy ra trên cả hai hệ điều hành ở cấp TCP! Tôi chắc chắn sẽ liên kết nó với những người khác có thể có cùng một câu hỏi để tham khảo thêm. Tôi đã không nhận ra rằng sự khác biệt giữa Windows và Linux trên Sockets đạt TCP và làm thế nào có thể là có lỗi tương tự (RST) trên Linux quá trong một môi trường không phòng thí nghiệm. Tốt hơn là sau đó chỉ cần nắm bắt ngoại lệ, hiển thị thông báo ngoại lệ "Frienldly" cho người dùng và đăng nhập ở đâu đó. Trân trọng. –

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