淺談java socket的正確關閉姿勢

java socket對應的是網絡協議中的tcp,tcp的三次握手、四次揮手、11中狀態什麼的這裡就不說瞭,不知道大傢平常使用socket的時候如果不註意的情況下,會不會遇到各種異常報錯。

例如:

java.net.SocketException:socket is closed

錯誤提示的出現場景:

自己主動關閉瞭socket,但是之後還從裡面讀寫數據

Software caused connection abort: socket write error

錯誤提示的出現場景:

對方已經關閉socket,依舊向對方寫數據

connection reset (by peer)

錯誤提示出現的場景:

一端socket被關閉,另一端仍然發送數據,發送的第一個數據包 connection reset by peer

一端socket退出,退出時為關閉連接,另一端讀數據 connection reset

所以在使用socket時,需要約定好雙方讀寫完成的條件,然後關閉輸入輸出流:

socket.shutdownInput();
socket.shutdownOutput();

即當一方寫入完成後,調用shutdownOutput關閉輸出流,這時候對方的read方法就會返回-1,這時候對方就知道你寫完瞭,對方可以關閉輸入流,然後等待對方寫入完成調用shutdownOutput後己方再調用shutdownInput,雙方就正常關閉瞭輸入輸出流,這時候socket就不會出現異常瞭。

下面是一個socket交互的例子:

server端

public class OioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("socket = " + socket);
            new Thread(() -> {
                try {
                    InputStream in = socket.getInputStream();
                    OutputStream out = socket.getOutputStream();
                    out.write("hello! I get your message that is follow".getBytes(Charset.forName("UTF-8")));
                    byte[] buf = new byte[1024];
                    int len;
                    while ((len = in.read(buf)) != -1) {
                        System.out.print(new String(buf, 0, len, Charset.forName("UTF-8")));
                        out.write(buf, 0, len);
                    }
                    out.write("\n end \n".getBytes(Charset.forName("UTF-8")));
                    out.flush();
                    socket.shutdownInput();
                    socket.shutdownOutput();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

client端

public class OioClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        InputStream in = socket.getInputStream();
        new Thread(() -> {
            BufferedInputStream bufferIn = new BufferedInputStream(in);
            byte[] buf = new byte[1024];
            try {
                int len;
                while ((len = bufferIn.read(buf)) != -1) {
                    System.out.print(new String(buf, 0, len, Charset.forName("UTF-8")));
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
            try {
                socket.shutdownInput();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        OutputStream out = socket.getOutputStream();
        int cout = 10;
        while (cout-- > 0) {
            out.write(("this time is " + System.currentTimeMillis() + "\n").getBytes("UTF-8"));
        }
        socket.shutdownOutput();
    }
}

java socket – 半關閉

通常,使用關閉輸出流來表示輸出已經結束。但在進行網絡通信時則不能這樣做。因為我們關閉輸出流時,該輸出流對應的Socket也將隨之關閉,這樣程序將無法再從該socket中讀取數據。

為瞭應付這種情況,socket提供瞭兩個半關閉的方法用來隻關閉socket的輸入流或者輸出流,用以表示輸出數據已經發送完成。

方法詳情:

shutdownInput():關閉該socket的輸入流,程序還可以通過該socket的輸出流輸出數據;

shutdownOutput():關閉該socket的輸出流,程序還可以通過該socket的輸入流讀取數據。

當調用shutdownInput()或shutdownOutput()方法關閉輸入流或輸出流後,該socket處於半關閉狀態。

此時可以使用isInputShutdown()或isOutputShutdown()來判斷該socket是否處於半讀狀態或半寫狀態。

需要註意的是,即使同一個socket先後調用shutdownInput()和shutdownInput()方法,該socket實例仍然沒有被關閉,隻是該socket既不能輸出數據也不能讀取數據而已。

當調用shutdownInput()或shutdownOutput()方法關閉瞭輸入流或輸出流之後,該socket無法再次打開輸出流或輸入流,因此這種做法不適合需要保持持久通信狀態的交互式應用。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: