关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题

目录

1、出现问题

2、缓冲流的缓冲原理

2.1 基本概念

2.2 源码分析

2.3 缓冲流中 flush()方法执行的几种情况

① 缓冲区满了的时候会自动flush (刚刚验证过了)

② 手动调用flush

③ 使用close()关闭流的时候 会自动 flush()

3 、使用flush过后的代码

 4、关于socket.shutdownOutput()的理解

 5、总结


首先看一下这样的要求:

  1. 编写一个服务器端和客户端

  2. 服务器端在9999端口监听

  3. 客户端连接到服务器端,发送 hello server (字符)

  4. 服务器端接收到客户端发送的信息,输出信息 ,然后向客户端发送 hello client,最后退出

  5. 客户端接收到服务端发送的信息,输出信息 ,然后退出

代码如下:

服务端: 

package com.sofwin.socket;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @packageName: com.sofwin.socket
 * @author: wentao
 * @date: 2022/11/14 8:31
 * @version: 1.0
 * @email [email protected]
 * @description: 服务端
 */
public class ServerTcpSocket {
    public static void main(String[] args) throws IOException {
        // 1.在本地的9999端口监听   要求在本机没有其他服务使用9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2.当没有客户端连接的时候 程序就会阻塞  等待连接
        //   如果有客户端连接的时候,就会返回Socket对象,程序继续
        System.out.println("服务器等待连接------");
        Socket accept = serverSocket.accept();
        System.out.println("服务器端-9999端口被连接");
        //3.利用 accept获取输入流
        InputStream inputStream = accept.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String dataStr;
        dataStr = bufferedReader.readLine();
        System.out.println(dataStr);


        OutputStream outputStream = accept.getOutputStream();
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("hello client");
        bufferedWriter.newLine();
//       bufferedWriter.flush();
//       accept.shutdownOutput();
        //4. 关闭流和socket
        bufferedWriter.close();
        bufferedReader.close();
        accept.close();

    }
}

客户端:

package com.sofwin.socket;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @packageName: com.sofwin.socket
 * @author: wentao
 * @date: 2022/11/14 8:31
 * @version: 1.0
 * @email [email protected]
 * @description: 客户端
 */
public class ClientTcpSocket {
    public static void main(String[] args) throws IOException {
        //1.连接服务器     连接ip主机的端口号
        //  如果连接成功返回Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端开始连接-本机的9999端口");
        //2. 利用生成的socket得到 输出流
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("hello server");
        bufferedWriter.newLine(); //结束标记
        //这里这个flush必须要加入
//       bufferedWriter.flush();
//       socket.shutdownOutput();


        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String s;
        s = bufferedReader.readLine();
        System.out.println(s);

        //3.关闭流对象和socket
        bufferedReader.close();
        bufferedWriter.close();
        socket.close();
        System.out.println("客户端退出");
    }
}

1、出现问题

这样写完过后(先启动服务端 在启动客户端),我发现出现了一个这样的问题:

客户端和服务端都一直在运行

 

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第1张图片

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第2张图片 

先说一下结论为什么造成这样的效果

我们使用字符写入的时候(newLine()   是代表读的时候  结束的标志) ---规定

但是,我们使用的缓冲流

缓冲流会自动将写入的数据先存放在缓冲区

我们没有使用flush进行刷新  这个时候数据还是在缓冲区中,没有写入到流中

因此当服务器端读取的时候 没有读到结束的标志  也就是newLine() 因此一直在运行

然后客户端端 还一直在等服务端发送的数据  也一直在等待,也就造成了这样情况

2、缓冲流的缓冲原理

2.1 基本概念

BufferedReader 和 FileReader 差不多,是用来读取文件的一种字符输入流。
BufferedInputStream 和 FileInputStream 差不多,是用来读取文件的一种字节输入流。

 

  1. 区别就在于 BufferedReader 和 BufferedInputStream 里有一个8192长度的char[] 字符数组 或 字节数组,当做内部缓冲区来使用。
  2. 当读取数据的时候,一次性从硬盘当中读取最多8192个 字符 或 字节(读取的是字符还是字节,具体看我们所使用的输入流),放在数组缓冲区当中(即内部缓冲区)。
  3. 在调用read方法的时候,只是从内部缓冲区当中拿出来数据进行使用。
  4. 如果缓冲区当中的数据被“取空”了,那么将会自动再次读取最多8192个字符 或 字节 再次放在内部缓冲区当中。

同理:
BufferedWriter 和 FileWriter 差不多,是用来向文件中写数据的一种字符输出流。
BufferedOutputStream 和 FileOutputStream 差不多,是用来向文件中写数据的一种字节输出流。

  1. 区别就在于 BufferedWriter 和 BufferedOutputStream 里有一个8192长度的char[] 字符数组 或 字节数组,当做内部缓冲区来使用。

  2. 每次在调用write方法 写数据的时候,实际上都是在不断的向缓冲数组中添加 字符 或 字节。

  3. 如果缓冲数组(即内部缓冲区)已经满了,那么将会统一的写入到硬盘的文件当中。

  4. 如果内部缓冲区还没有满,那么就等待下一次写入。

  5. 如果最终关闭流的时候,缓冲数组仍然没满,那么也会将剩余的有效数据写入到硬盘文件中

 关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第3张图片

2.2 源码分析

此处以 BufferedReader 和 BufferedWriter为例:

BufferedReader 源码

当我们使用 BufferedReader (Reader in) 构造器 创建一个流对象时,实际上底层调用了它的另一个带参构造器BufferedReader (Reader in, int sz) ,并默认设置了一个大小为8192的内部缓冲区用来存放读取到的数据。 

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第4张图片

BufferedWriter 源码

当我们使用 BufferedWriter (Writer out) 构造器 创建一个流对象时,实际上底层调用了它的另一个带参构造器 BufferedWriter (Writer out, int sz) ,并默认设置了一个大小为8192的内部缓冲区用来存放要写出的数据。 

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第5张图片

 nChars 等于缓冲数组长度8192   nextChar是存放数据的大小

 当调用writer的时候

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第6张图片

 解释:只要 nextChar(每次写入的数据长度)大于缓冲数组的大小 即8192 就进行刷新进行真正的写入操作

2.3 缓冲流中 flush()方法执行的几种情况

① 缓冲区满了的时候会自动flush (刚刚验证过了)

② 手动调用flush

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第7张图片

③ 使用close()关闭流的时候 会自动 flush()

close相当于 将数据置空(让垃圾回收机制回收) + 刷新(flush) 

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第8张图片

 

3 、使用flush过后的代码

服务端:

package com.sofwin.socket;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @packageName: com.sofwin.socket
 * @author: wentao
 * @date: 2022/11/14 8:31
 * @version: 1.0
 * @email [email protected]
 * @description: 服务端
 */
public class ServerTcpSocket {
    public static void main(String[] args) throws IOException {
        // 1.在本地的9999端口监听   要求在本机没有其他服务使用9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2.当没有客户端连接的时候 程序就会阻塞  等待连接
        //   如果有客户端连接的时候,就会返回Socket对象,程序继续
        System.out.println("服务器等待连接------");
        Socket accept = serverSocket.accept();
        System.out.println("服务器端-9999端口被连接");
        //3.利用 accept获取输入流
        InputStream inputStream = accept.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String dataStr;
        dataStr = bufferedReader.readLine();
        System.out.println(dataStr);


        OutputStream outputStream = accept.getOutputStream();
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("hello client");
        bufferedWriter.newLine();
       bufferedWriter.flush();
        //4. 关闭流和socket
        bufferedWriter.close();
        bufferedReader.close();
        accept.close();

    }
}

客户端:

package com.sofwin.socket;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @packageName: com.sofwin.socket
 * @author: wentao
 * @date: 2022/11/14 8:31
 * @version: 1.0
 * @email [email protected]
 * @description: 客户端
 */
public class ClientTcpSocket {
    public static void main(String[] args) throws IOException {
        //1.连接服务器     连接ip主机的端口号
        //  如果连接成功返回Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端开始连接-本机的9999端口");
        //2. 利用生成的socket得到 输出流
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("hello server");
        bufferedWriter.newLine(); //结束标记
        //这里这个flush必须要加入
       bufferedWriter.flush();


        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String s;
        s = bufferedReader.readLine();
        System.out.println(s);

        //3.关闭流对象和socket
        bufferedReader.close();
        bufferedWriter.close();
        socket.close();
        System.out.println("客户端退出");
    }
}

结果:

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第9张图片

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第10张图片 

 4、关于socket.shutdownOutput()的理解

这个代码的作用是

 调用Socket.shutdownOutput()后,禁用此套接字的输出流,对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列(即-1),之后,从另一端TCP套接字的输入流中读取数据时,如果到达输入流末尾而不再有数据可用,则返回 -1。

也就是当没有调用shutdownOutput时,相当于没有跟服务器说“我已经说完啦,你可以说了”。那么服务器呢就觉得“哦,客户端还没说完,我要等它说完,要有礼貌”然后服务器就一直等,就阻塞了。他就不会给客户端发送消息“ hello client ”。

其实就是当你写完过后 在写一个  socket.shutdownOutput() 代表我说完了的意思(类似于字符中的   bufferedWriter.newLine(); 也是一个结束标记 )

案例:

  1. 编写一个服务器端和客户端

  2. 服务器端在9999端口监听

  3. 客户端连接到服务器端,发送 hello server (字节)

  4. 服务器端接收到客户端发送的信息,输出信息 ,然后向客户端发送 hello client,最后退出

  5. 客户端接收到服务端发送的信息,输出信息 ,然后退出

服务端:

package com.sofwin.socket;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @packageName: com.sofwin.socket
 * @author: wentao
 * @date: 2022/11/14 8:31
 * @version: 1.0
 * @email [email protected]
 * @description: 服务端
 */
public class ServerTcpSocket {
    public static void main(String[] args) throws IOException {
        // 1.在本地的9999端口监听   要求在本机没有其他服务使用9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2.当没有客户端连接的时候 程序就会阻塞  等待连接
        //   如果有客户端连接的时候,就会返回Socket对象,程序继续
        System.out.println("服务器等待连接------");
        Socket accept = serverSocket.accept();
        System.out.println("服务器端-9999端口被连接");
        //3.利用 accept获取输入流
        InputStream inputStream = accept.getInputStream();
        byte[] b = new byte[1024];
        //接收长度
        int bLen = 0;
        while ((bLen = inputStream.read(b)) != -1) {
            System.out.println(new String(b, 0, bLen));
        }
        OutputStream outputStream = accept.getOutputStream();
        outputStream.write("hello client".getBytes());
        accept.shutdownOutput();
        //4. 关闭流和socket
        outputStream.close();
        inputStream.close();
        accept.close();

    }
}

客户端:

package com.sofwin.socket;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @packageName: com.sofwin.socket
 * @author: wentao
 * @date: 2022/11/14 8:31
 * @version: 1.0
 * @email [email protected]
 * @description: 客户端
 */
public class ClientTcpSocket {
    public static void main(String[] args) throws IOException {
        //1.连接服务器     连接ip主机的端口号
        //  如果连接成功返回Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端开始连接-本机的9999端口");
        //2. 利用生成的socket得到 输出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello server".getBytes());
        socket.shutdownOutput();  //结束标志


        InputStream inputStream = socket.getInputStream();
        byte[] b = new byte[1024];
        //接收长度
        int bLen = 0;
        while ((bLen = inputStream.read(b)) != -1) {
            System.out.println(new String(b, 0, bLen));
        }

        //3.关闭流对象和socket
        inputStream.close();
        outputStream.close();
        socket.close();
        System.out.println("客户端退出");
    }
}

结论:

关于Socket 客户端和服务端通信,为什么要flush 为什么使用shutdownOutput的问题_第11张图片

 5、总结

  1. 使用缓冲流的时候,写操作(Writer)注意写完要调用flush()进行刷新
  2. Socket中字符的传输 可以使用 newLine() 作为结束标志
  3. Socket中字节的传输 可以使用 socket.shutdownOutput() 作为结束标志
  4. 注意关闭流  一般采取后用先关的原则 

最后关于缓冲流的思路是参考的这个博主:

从源码分析:关于IO流中缓冲流 和 flush()方法_Transform。的博客-CSDN博客_io流源码解析

你可能感兴趣的:(Java基础,java,服务器)