【Java TCP/IP Socket】 — close()/shutdownOutput()/shutdownInput() 分析

前言:

  在Java Socket中提供了2中关闭方法,其中包括:close() 、shutdownOutput()/shutdownInput() ,前一段时间做项目中用到了Socket,但是没有好好总结。

  现在,我把我遇到的问题分享一下:

  

  在网络协议中,通常明确指定了由谁来发起 “关闭”连接。

  例如:在HTTP协议中,客户端先向服务端发送一个请求,然后服务器响应请求的信息。由于客户端不知道服务端响应信息的大小,因此服务器必须通过关闭套

  接字来指示响应信息结束 —— 这个就是由服务器端发起的关闭连接!


一. Socket.close()方法

   这个方法我们估计都不会陌生,一般使用完Socket后,都会调用Socket.close()  来释放相关的资源。当我们调用Socket.close()方法将同时终止两个方向(输入

   和输出)的数据流。一旦一个终端(客户端或服务端)关闭了套接字,它将无法再发送或接收数据。这就意味着close( )方法只能在调用者完成了通信之后用来

   给另一端发送信号。只要服务端收到了客户端的关闭信号,就立即关闭连接。

  

   1. 考虑一个场景:

   假设你有一个服务器,接收到消息后,在发回给客户端,这种情况下应该是有哪一端来关闭连接呢?

   由于从客户端发送的消息长度是任意的,客户端需要关闭连接以通知服务器消息发送完毕,那么客户端应该什么时候调用close( )方法呢,如果客户端在发送完最后

   一个字节后立即调用close( )方法,它将无法接收服务器发送的消息 (因为: 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream);如果客户端在接收

   完所有的数据后在调用close ( )方法,那么服务端 将不知道到底有多少数据要接收(因为:客户端的输出流一直没有关闭,所以,服务端的输入流也就无法到达流末尾返回-1),

   所以,服务器在接收数据时就一直阻塞,而这个时候客户端又要等待接收服务端发送来的消息(即使服务端发送了消息,但因为服务端的输出流一直没有关闭,所以,

   服务端的输入流也就无法到达流末尾返回-1,所以客户端在接收数据时就一直阻塞) —— 以上问题就造成:”服务端和客户端都在接收数据时阻塞"

   

   情况一 :客户端在发送完最后一个字节后立即调用close( )方法

//服务端  
public class Server  
{  
    public static void main(String[] args) throws IOException  
    {  
        System.out.println("------------------------------------");  
        System.out.println("Server start......");  
        System.out.println("------------------------------------");  
  
        ServerSocket server = new ServerSocket(8888);  
  
        while (true)  
        {  
            Socket client = server.accept();  
            OutputStream out = client.getOutputStream();  
            InputStream in = client.getInputStream();  
  
            ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
            byte[] temp = new byte[1024];  
            int realLen = 0;  
            while ((realLen = in.read(temp)) != -1)  
            {  
                byteArrayOut.write(temp, 0, realLen);  
            }  
  
            byte[] recv = byteArrayOut.toByteArray();  
  
            // 将接收的消息,发回给客户端  
            out.write(recv);  
            out.flush();  
        }  
    }  
}  
  
  
//客户端  
public class Client  
{  
  
    public static void main(String[] args) throws IOException  
    {  
        System.out.println("------------------------------------");  
        System.out.println("Client start......");  
        System.out.println("------------------------------------");  
        byte[] msg = new String("connect successfully!!!").getBytes();  
  
        InetAddress inetAddr = InetAddress.getLocalHost();  
        Socket client = new Socket(inetAddr, 8888);  
        OutputStream out = client.getOutputStream();  
        InputStream in = client.getInputStream();  
  
        out.write(msg);  
        out.flush();  
        // 情况一 :客户端在发送完最后一个字节后立即调用close( )方法  
        client.close();  
  
        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
        byte[] temp = new byte[1024];  
        int realLen = 0;  
        while ((realLen = in.read(temp)) != -1)  
        {  
            byteArrayOut.write(temp, 0, realLen);  
        }  
  
        byte[] recv = byteArrayOut.toByteArray();  
  
        System.out.println("Client receive msg:" + new String(recv));  
  
        /* 
         * 切记:在这里关闭输入流,并不会使服务端的输入流到达流末尾返回-1,仅仅是释放资源而已 
         */  
        in.close();  
        out.close();  
  
    }  
}  


 运行结果:服务端与客户端都接收不到数据



 情况二:如果客户端在接收完所有的数据后在调用close ( )方法

   服务端代码没变*(请参看情况一,所以这里只贴出客户端代码:

//客户端  
public class Client  
{  
  
    public static void main(String[] args) throws IOException  
    {  
        System.out.println("------------------------------------");  
        System.out.println("Client start......");  
        System.out.println("------------------------------------");  
        byte[] msg = new String("connect successfully!!!").getBytes();  
  
        InetAddress inetAddr = InetAddress.getLocalHost();  
        Socket client = new Socket(inetAddr, 8888);  
        OutputStream out = client.getOutputStream();  
        InputStream in = client.getInputStream();  
  
        out.write(msg);  
        out.flush();  
  
        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
        byte[] temp = new byte[1024];  
        int realLen = 0;  
        while ((realLen = in.read(temp)) != -1)  
        {  
            byteArrayOut.write(temp, 0, realLen);  
        }  
        // 情况二 :如果客户端在接收完所有的数据后在调用close()方法  
        client.close();  
  
        byte[] recv = byteArrayOut.toByteArray();  
  
        System.out.println("Client receive msg:" + new String(recv));  
  
        /* 
         * 切记:在这里关闭输入流,并不会使服务端的输入流到达流末尾返回-1,仅仅是释放资源而已 
         */  
        in.close();  
        out.close();  
  
    }  
}  

 运行结果:服务端与客户端都接收不到数据

     


二. Sockt.shutdownInput( )与Socket.shutdownOutput( )

   在上边的例子中,我们考虑了一个场景,但是分析之后——发现会造成 “服务端和客户端都在接收数据时阻塞 ”,我们现在需要一种方法来告诉连接的另一端 

   “我已经发送完所有数据” ,同时还要保持接收数据的能力。

   这个问题Java中已经帮我们解决了,TCP套接字中提供了一种实现这个功能的方法,Socket.shutdownOutput( )和Socket.shutdownInput( )方法能够将输入输出流相互独立的关闭。

   调用Socket.shutdownInput( )后, 禁用此套接字的输入流,发送到套接字的输入流端的任何数据都将被确认然后被静默丢弃。任何想从该套接字的输入流读取数据的操作都将返回-1;

   调用Socket.shutdownOutput()后,禁用此套接字的输出流,对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列(即-1),之后,从另一端TCP

   套接字的输入流中读取数据时,如果到达输入流末尾而不再有数据可用,则返回 -1

    

   注意:

   当调用Socket.shutdownInput( )后,还能够往该套接字中写数据(执行OutputStream.write( ));

   当调用Socket.shutdownOutput( )后,还能够往该套接字中读数据(执行InputStream.read( ));

   

  使用Socket.shutdownInput( )和Socket.shutdownOutput( ),代码如下:


//服务端  
public class Server  
{  
    public static void main(String[] args) throws IOException  
    {  
        System.out.println("------------------------------------");  
        System.out.println("Server start......");  
        System.out.println("------------------------------------");  
  
        ServerSocket server = new ServerSocket(8888);  
  
        while (true)  
        {  
            Socket client = server.accept();  
            OutputStream out = client.getOutputStream();  
            InputStream in = client.getInputStream();  
  
            ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
            byte[] temp = new byte[1024];  
            int realLen = 0;  
            while ((realLen = in.read(temp)) != -1)  
            {  
                byteArrayOut.write(temp, 0, realLen);  
            }  
  
            byte[] recv = byteArrayOut.toByteArray();  
  
            // 将接收的消息,发回给客户端  
            out.write(recv);  
            out.flush();  
            // 关闭该套接字的输入流  
            client.shutdownOutput();  
        }  
    }  
}  
  
  
//客户端  
public class Client  
{  
  
    public static void main(String[] args) throws IOException  
    {  
        System.out.println("------------------------------------");  
        System.out.println("Client start......");  
        System.out.println("------------------------------------");  
        byte[] msg = new String("connect successfully!!!").getBytes();  
  
        InetAddress inetAddr = InetAddress.getLocalHost();  
        Socket client = new Socket(inetAddr, 8888);  
        OutputStream out = client.getOutputStream();  
        InputStream in = client.getInputStream();  
  
        out.write(msg);  
        out.flush();  
        // 关闭该套接字的输出流  
        client.shutdownOutput();  
  
        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
        byte[] temp = new byte[1024];  
        int realLen = 0;  
        while ((realLen = in.read(temp)) != -1)  
        {  
            byteArrayOut.write(temp, 0, realLen);  
        }  
  
        byte[] recv = byteArrayOut.toByteArray();  
  
        System.out.println("Client receive msg:" + new String(recv));  
  
        /* 
         * 切记:在这里关闭输入流,并不会使服务端的输入流到达流末尾返回-1,仅仅是释放资源而已 
         */  
        in.close();  
        out.close();  
  
    }  
}  

   运行看看,完全没有问题,网上还有人说 “Socket.shutdownInput( )与Socket.shutdownOutput( )” 是 “半关闭方法” — 其实这么说也对。


三. 深入剖析TCP关闭连接机制

   TCP套接字中有一个优雅的关闭机制,以保证应用程序在关闭连接时不必担心正在传输的数据会丢失。这个机制还允许两个方向的数据传输相互独立的终止。

   关闭机制的工作流程:应用程序通过调用连接套接字的close( )方法或shutdownOutput( )方法表明数据已经发送完毕。此刻,底层的TCP实现首先将留存在

   SendQ队列中的数据传输出去(还要依赖另一端RecvQ队列的剩余空间),然后向另一端发送一个关闭TCP连接的握手消息,该关闭握手消息可以看作时流

   终止标志:它告诉接收端TCP不会再有新的数据传入RecvQ队列了。(关闭握手消息本身并没有传递给接收端应用程序,而是通过read( ) 方法返回-1来指示

   其在字节流中的位置)正在关闭的TCP将等待其关闭握手消息的确认信息,该确认信息表明在连接上传输的所有数据已经安全的传输到了RecvQ中。只要接收

   到了确认信息,该连接就变成 “半关闭 ”状态。直到连接的另一个方向上收到了对称的握手消息后,连接才完全关闭——也就是说,连接的两端都表明它们再也

  没有数据要发送了。




  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中可以使用Socket和ServerSocket类基于TCP/IP协议进行文件传输。 具体步骤如下: 1. 服务器端创建ServerSocket对象,等待客户端连接。 2. 客户端创建Socket对象,连接服务器端。 3. 服务器端接受客户端连接,创建Socket对象,获取输入输出流。 4. 客户端获取输入输出流。 5. 客户端将要传输的文件读入内存中。 6. 客户端将文件内容写入输出流。 7. 服务器端从输入流中读取文件内容。 8. 服务器端将文件内容写入指定文件中。 9. 关闭输入输出流,关闭Socket对象,关闭ServerSocket对象。 下面是服务器端的代码示例: ```java public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务器已启动,等待客户端连接..."); Socket socket = serverSocket.accept(); System.out.println("客户端已连接,地址为:" + socket.getInetAddress().getHostAddress()); InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; FileOutputStream fileOutputStream = new FileOutputStream("E:\\test.txt"); while ((len = inputStream.read(bytes)) != -1) { fileOutputStream.write(bytes, 0, len); } OutputStream outputStream = socket.getOutputStream(); outputStream.write("文件已接收".getBytes()); fileOutputStream.close(); outputStream.close(); inputStream.close(); socket.close(); serverSocket.close(); } } ``` 下面是客户端的代码示例: ```java public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8888); OutputStream outputStream = socket.getOutputStream(); FileInputStream fileInputStream = new FileInputStream("E:\\test.txt"); byte[] bytes = new byte[1024]; int len; while ((len = fileInputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, len); } socket.shutdownOutput(); InputStream inputStream = socket.getInputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); while ((len = inputStream.read(bytes)) != -1) { byteArrayOutputStream.write(bytes, 0, len); } System.out.println(byteArrayOutputStream.toString()); fileInputStream.close(); outputStream.close(); inputStream.close(); socket.close(); } } ``` 以上代码仅供参考,实际使用时需要根据具体情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值