关于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 1660420659@qq.com
 * @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 1660420659@qq.com
 * @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、出现问题

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

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

 

 

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

我们使用字符写入的时候(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. 如果最终关闭流的时候,缓冲数组仍然没满,那么也会将剩余的有效数据写入到硬盘文件中

 

2.2 源码分析

此处以 BufferedReader 和 BufferedWriter为例:

BufferedReader 源码

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

BufferedWriter 源码

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

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

 当调用writer的时候

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

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

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

② 手动调用flush

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

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

 

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 1660420659@qq.com
 * @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 1660420659@qq.com
 * @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("客户端退出");
    }
}

结果:

 

 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 1660420659@qq.com
 * @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 1660420659@qq.com
 * @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("客户端退出");
    }
}

结论:

 5、总结

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

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

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

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在 Android 上实现 Socket 客户端服务端通信,可以通过 JavaSocket 编程实现。下面是一个简单的示例: 1. 服务端代码: ```java import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务器已启动,等待客户端连接..."); while (true) { Socket socket = serverSocket.accept(); System.out.println("客户端已连接:" + socket.getInetAddress()); Thread thread = new Thread(new ServerThread(socket)); thread.start(); } } } ``` 2. 服务端线程代码: ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; public class ServerThread implements Runnable { private Socket socket; public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String str; while ((str = reader.readLine()) != null) { System.out.println("客户端发送消息:" + str); } } catch (IOException e) { e.printStackTrace(); } } } ``` 3. 客户端代码: ```java import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 8888); OutputStream outputStream = socket.getOutputStream(); outputStream.write("Hello Server".getBytes()); outputStream.flush(); socket.shutdownOutput(); socket.close(); } } ``` 在服务端启动后,等待客户端连接,当客户端连接后,开启一个线程处理客户端的请求。客户端通过 Socket 连接到服务端,发送消息后关闭连接。 注意:以上代码仅为示例,没有进行异常处理等操作,实际使用时需要根据具体需求补充完整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值