java网络编程 BufferedReader的readLine方法读不到数据且一直阻塞

ops/2024/9/18 21:12:39/ 标签: java, bio, BufferedReader, readLine

最近在整理Java IO相关内容,会遇到一些以前没有注意的问题,特此记录,以供自查和交流。

需求:

基于Java的BIO API,实现简单的客户端和服务端通信模型,客户端使用BufferedReaderreadLine方法读取System.in上的用户输入,然后通过字节输出流发送给服务端,服务端使用BufferedReaderreadLine方法读取客户端的数据,进行打印;

问题:

服务端没有打印出客户端发送的数据,且卡在BufferedReaderreadLine方法处

上代码:

客户端:

java">import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;/*** 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端**/
class ChatClient {public static void main(String[] args) throws IOException {// 连接serverSocket serverSocket = new Socket("localhost", 9999);System.out.println("client connected to server");// 读取用户在控制台上的输入,并发送给服务器InputStream in = System.in;// sendDataToServerByByteStream(in, serverSocket.getOutputStream()); //服务端可以正常接收sendDataToServerByCharStream(in, serverSocket.getOutputStream()); //服务端无法正常接收}/*** 通过字节流发送数据给服务端*/private static void sendDataToServerByByteStream(InputStream in, OutputStream outputStream) throws IOException {byte[] buffer = new byte[1024];int len;// read操作阻塞,直到有数据可读while ((len = in.read(buffer)) != -1) {System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));// 发送数据给服务器端outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符的}}/*** 通过字符流,使用readLine发送数据给服务端*/private static void sendDataToServerByCharStream(InputStream in, OutputStream outputStream) {String content = null;try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里content = bufferedReader.readLine();while (content != "exit") {System.out.println("client send data: " + content);outputStream.write(content.getBytes()); // 字节流,没有添加换行符content = bufferedReader.readLine();}} catch (IOException e) {throw new RuntimeException(e);}}
}

服务端代码:

java">import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;/*** 基于BIO的TCP网络通信的服务端,可以接收多个客户端连接,通过字节流接收客户端发送的消息;* 一个客户端需要使用一个线程* todo:线程资源复用** @author freddy*/
class ChatServer {public static void main(String[] args) throws IOException {// 开启server 监听端口ServerSocket serverSocket = new ServerSocket(9999);while (true) {Socket client = serverSocket.accept(); // 阻塞操作,需要新的线程处理客户端// 接收Client数据,并转发new Thread(new ServerThread(client)).start();}}
}/*** 服务端的线程,一个客户端对应一个*/
class ServerThread implements Runnable {Socket socket;public ServerThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {System.out.println("server had a client" + socket);// 获取输入流程,读取用户输入// 持续接收Client数据,并打印readDataFromClientByCharStream(); // 无法正常读取客户端发送过来的数据}/*** 使用字符流读取客户端的数据,主要使用readLine*/private void readDataFromClientByCharStream() {try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里String content = bufferedReader.readLine();while (content != "exit") {System.out.println("serer receive data from " + socket + " : " + content);content = bufferedReader.readLine();}} catch (IOException e) {System.out.println("Client disconnect ");}}   
}

先运行服务端,在启动客户端,然后在客户端的控制台发送数据:

可以看到,客户端和服务端之间已经建立了连接,但是服务端并没有打印日志,说明服务端的程序卡在了代码1这个地方。

为什么呢?

那我们需要去看java.io.BufferedReader#readLine()这个方法的源码:

基于debug方式,我们可以看到java.io.BufferedReader#readLine()这个方法

先调用java.io.BufferedReader#fill方法读取输入流的内容

可以看到,这里读取到的内容是hello 5个字符,没有换行符;

fill方法调用完后,回到readLine方法的charLoop中:

可以看到,for循环中有个条件,当读取到的字节中包含'\n' 或者 '\r'的时候,会设置eol = true,后面会根据该eol标志,return读取到的字符串,结束readLine方法;

当读取到的字节中没有'\n' 或者 '\r'的时候,eol = false,readLine方法就会回到

bufferLoop循环中的fill方法继续读取输入流程中的内容:

如果输入流中有内容,会读取后继续判断是否有换行符:'\n' 或者 '\r'

如果输入流中没有内容,那么fill方法会阻塞在java.io.Reader#read(char[], int, int)方法:

这就是服务端的代码阻塞在java.io.BufferedReader#readLine()的原因;

解决问题:

找到问题后,那么我们就好解决问题了:

解决思路如下:

1.服务端仍然使用java.io.BufferedReader#readLine()读取客户端的数据的话,那么客户端发送数据时,就必须代换行符

1.1  客户端在发送完用户数据后,继续Socket.getOutputStream().write("\r\n".getBytes());发送换行符;

1.2 调用增强的输出流的api,直接发送数据的同时发送换行符:

比如:PrintWriter pw = new PrintWriter(outputStream, true);

pw.println(content); // 添加换行符

1.3 调整客户端获取用户输入数据的方式,把用户的换行符直接读取过来后,用原来的方式发送

java">    private static void sendDataToServerByByteStream(InputStream in, OutputStream outputStream) throws IOException {byte[] buffer = new byte[1024];int len;// read操作阻塞,直到有数据可读while ((len = in.read(buffer)) != -1) {System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));// 发送数据给服务器端outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符的}}

2.服务端调整数据读取方式

客户端使用java.io.DataOutputStream#writeUTF(java.lang.String)发送给数据

服务端使用java.io.DataInputStream#readUTF()方法接收数据

这种方式,是相当于客户端在发送数据的时候,给数据规定了格式,服务端可以根据约定的格式,来正确读取数据;类似于java.io.DataOutputStream#writeShort方法

关于这种思想,用的地方很多

常用来解决RPC发送数据的粘包问题

在常用的RPC框架,如Netty中就有使用;在大数据框架如MapReduce中也有writeShort类似方式序列号和反序列话;

完整的客户端和服务端验证代码如下:

客户端:

java">import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;/*** 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端**/
class ChatClient {public static void main(String[] args) throws IOException {// 连接serverSocket serverSocket = new Socket("localhost", 9999);System.out.println("client connected to server");// 读取用户在控制台上的输入,并发送给服务器InputStream in = System.in;// sendDataToServerByByteStream(in, serverSocket.getOutputStream()); //服务端可以正常接收sendDataToServerByCharStream(in, serverSocket.getOutputStream()); // 服务端无法正常接收}/*** 通过字节流发送数据给服务端*/private static void sendDataToServerByByteStream(InputStream in, OutputStream outputStream) throws IOException {byte[] buffer = new byte[1024];int len;// read操作阻塞,直到有数据可读while ((len = in.read(buffer)) != -1) {System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));// 发送数据给服务器端outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符的}}/*** 通过字符流,使用readLine发送数据给服务端*/private static void sendDataToServerByCharStream(InputStream in, OutputStream outputStream) {String content = null;try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里content = bufferedReader.readLine();while (content != "exit") {System.out.println("client send data: " + content);outputStream.write(content.getBytes()); // 字节流,没有添加换行符content = bufferedReader.readLine();}} catch (IOException e) {throw new RuntimeException(e);}}/*** 通过字符流,使用readLine发送数据给服务端*/private static void sendDataToServerByCharStream2(InputStream in, OutputStream outputStream) {String content = null;try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里content = bufferedReader.readLine();while (content != "exit") {System.out.println("client send data: " + content);outputStream.write(content.getBytes()); // 字节流,没有添加换行符outputStream.write("\r\n".getBytes());content = bufferedReader.readLine();}} catch (IOException e) {throw new RuntimeException(e);}}/*** 通过字符流,使用readLine发送数据给服务端*/private static void sendDataToServerByCharStream3(InputStream in, OutputStream outputStream) {String content = null;try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));PrintWriter pw = new PrintWriter(outputStream, true);) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里content = bufferedReader.readLine();while (content != "exit") {System.out.println("client send data: " + content);pw.println(content); // 添加换行符content = bufferedReader.readLine();}} catch (IOException e) {throw new RuntimeException(e);}}private static void sendDataToServerByCharStream4(InputStream in, OutputStream outputStream) {String content = null;try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));DataOutputStream pw = new DataOutputStream(outputStream);) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里content = bufferedReader.readLine();while (content != "exit") {System.out.println("client send data: " + content);pw.writeUTF(content);pw.flush();content = bufferedReader.readLine();}} catch (IOException e) {throw new RuntimeException(e);}}
}

服务端:

java">import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;/*** 基于BIO的TCP网络通信的服务端,可以接收多个客户端连接,通过字节流接收客户端发送的消息;* 一个客户端需要使用一个线程* todo:线程资源复用** @author freddy*/
class ChatServer {public static void main(String[] args) throws IOException {// 开启server 监听端口ServerSocket serverSocket = new ServerSocket(9999);while (true) {Socket client = serverSocket.accept(); // 阻塞操作,需要新的线程处理客户端// 接收Client数据,并转发new Thread(new ServerThread(client)).start();}}
}/*** 服务端的线程,一个客户端对应一个*/
class ServerThread implements Runnable {Socket socket;public ServerThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {System.out.println("server had a client" + socket);// 获取输入流程,读取用户输入// 持续接收Client数据,并打印readDataFromClientByCharStream(); // 无法正常读取客户端发送过来的数据}/*** 使用字节流读取客户端的数据*/private void readDataFromClientByByteStream() {try (InputStream inputStream = socket.getInputStream()) {byte[] buffer = new byte[1024];int len;// read操作阻塞,直到有数据可读while ((len = inputStream.read(buffer)) != -1) {System.out.println("serer receive data from " + socket + " : " + new String(buffer, 0, len));}} catch (IOException e) {System.out.println(socket + " disconnect ");}}/*** 使用字符流读取客户端的数据,主要使用readLine*/private void readDataFromClientByCharStream() {try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里String content = bufferedReader.readLine();while (content != "exit") {System.out.println("serer receive data from " + socket + " : " + content);content = bufferedReader.readLine();}} catch (IOException e) {System.out.println("Client disconnect ");}}/*** 使用字符流读取客户端的数据,主要使用readLine*/private void readDataFromClientByCharStream2() {try (DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());) {// 此时content中已经没有换行符了,// 并且如果原始字节流中没有换行符,该方法内部会循环等待换行符,相当于阻塞在这里String content = dataInputStream.readUTF();while (content != "exit") {System.out.println("serer receive data from " + socket + " : " + content);content = dataInputStream.readUTF();}} catch (IOException e) {System.out.println("Client disconnect ");}}
}

参考:java网络编程 BufferedReaderreadLine方法读不到数据的原因_java后台服务端bufferedreader不能读全数据 前台出现超时提示-CSDN博客


http://www.ppmy.cn/ops/16004.html

相关文章

十大经典排序算法之插入排序。

插入排序 (Insertion Sort) ​ 插入排序(Insertion Sort)是一种简单直观的排序算法,它的基本思想是逐步构建最终的排序列表,每次将一个未排序的元素插入到已排序的部分的适当位置。 插入排序的基本步骤如下: 首先&a…

mdk输出本语句在源程序中行数以及函数名称

代码如下: /* USER CODE BEGIN WHILE */while (1){printf("anlog uart1 test 2024-4-16\r\n");printf("[%s] %d\r\n",__func__,__LINE__);printf("[%s] %d",__func__,__LINE__);HAL_Delay(200);/* USER CODE END WHILE *//* USER COD…

4 -25

1 100个英语单词两篇六级阅读 2 cf补题; 3 仿b站项目看源码 debug分析业务。 上了一天课,晚上去健身。 物理备课,周六去上课腻。 五一回来毛泽东思想期末考试,概率论期中考试。

pytest数据驱动DDT(数据库/execl/yaml)

常见的DDT技术 数据结构: 列表、字典、json串 文件: txt、csv、excel 数据库: 数据库链接 数据库提取 参数化: pytest.mark.parametrize() pytest.fixture() …

微前端通信机制及其实现

微前端通信机制是指在微前端架构中,不同的微应用之间进行通信的方式和机制。微前端通信机制的实现可以通过以下几种方式: 事件总线(Event Bus):微前端架构中的主应用可以创建一个事件总线,用于发布和订阅事…

【力扣 Hot100 | 第七天】4.22(找到字符串中所有字母异位词)

文章目录 2.找到字符串中所有字母异位词2.1题目2.2解法:滑动窗口2.2.1解题思路2.2.2代码实现 2.找到字符串中所有字母异位词 2.1题目 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺…

找不到vcruntime140_1.dll,无法继续执行代码的多种解决方法

在启动电脑并着手进行日常工作的过程中,当我尝试运行一款至关重要的软件时,系统突然弹出一个令人困扰的错误提示:“由于找不到vcruntime140_1.dll,无法继续执行代码”,这个错误信息明确指出,由于缺失了vcru…

微软发布!提示工程进化为位置工程,有效提升RAG与上下文学习

别再光顾着优化提示工程啦!微软最近推出位置工程研究思路,只需调整token的索引位置,而不修改文本本身,就能显著提高任务性能。 提示工程通过添加、替换或删除段落和句子改变提示,调整语义信息,激发LLMs的推…

Nginx下载安装,什么是nginx,什么是反向代理,Windows下、linux下安装nginx(保姆级教程)

文章目录 一、Nginx简介为什么要使用NginxNginx的特点Nginx的相关概念正向代理反向代理动静分离负载均衡 二、Nginx安装1. Windows安装2. Linux安装 一、Nginx简介 Nginx 是一个高性能的 HTTP(静态资源服务器) 和 反向代理 Web 服务器。 为什么要使用N…

16篇 hdfs中篇

36. **更改文件权限** (hdfs fs -chmod): 更改HDFS中文件或目录的权限模式。 - 示例:hdfs fs -chmod 666 hdfs_file(设置文件的权限为可读写) 37. **更改文件所有者** (hdfs fs -chown): 更改HDFS中文件或目录的所有者和组。 - 示例…

java8 LocalDateTime

LocalDateTime java8使用了LocalDateTime和DateTimeFormatter。比之前的Date和Carlendar有所改进。 DateTimeFormatter是线程安全的。DateTimeFormatter中很多属性使用了final修饰。 LocalDate: 只能设置仅含年月日的格式,表示没有时区的日期, LocalDate是不可变并…

【C 数据结构】图

文章目录 【 1. 基本原理 】1.1 无向图1.2 有向图1.3 基本知识 【 2. 图的存储结构 】2.1 完全图2.2 稀疏图和稠密图2.3 连通图2.3.1 (普通)连通图连通图 - 无向图非连通图 的 连通分量 2.3.2 强连通图强连通图 - 有向图非强连通有向图 的 强连通分量 2.3.3 生成树 - 连通图2.3…

树莓派学习笔记--串口通信(配置硬件串口进行通信)

树莓派串口知识点 树莓派4b的外设一共包含两个串口:硬件串口(/dev/ttyAMA0),mini串口(/dev/ttyS0) 硬件串口由硬件实现,有单独的波特率时钟源,性能高,可靠;而mini串口性能…

MyBatis的关联映射

一、概述 在关系型数据库中,多表之间存在着三种关联关系,分别为一对一、一对多和多对多。 这三种关联关系的具体说明如下。 ●一对一:在任意一方引入对方主键作为外键。 ●一对多:在“多”的一方,添加“一”的一方的主…

el-image组件预览图片同时操作页面

背景:el-image组件打开预览效果不能滑动页面。 Q:那么如何才能在打开遮罩层后还能操作页面呢? A:改变遮罩层的大小。CSS3有一个属性width:fit-content;可以解决这个问题。 打开F12看看饿了么的原生样式如下 加上width&#xff1…

git lab 2.7版本修改密码命令

1.gitlab-rails console -e production Ruby: ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-linux] GitLab: 14.9.0-jh (51fb4a823f6) EE GitLab Shell: 13.24.0 PostgreSQL: 12.7 2根据用户名修改密码 user User.find_by(username: ‘username’) # 替换’use…

07节-51单片机-矩阵键盘

文章目录 1矩阵键盘原理2.扫描的概念3.弱上拉4.实战-实现矩阵键盘对应按钮按下显示对应值4.1配置代码模板 5.键盘锁 1矩阵键盘原理 在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式 采用逐行或逐列的“扫描”,就可以读…

前端补充18(JS)

一、数据类型的转换 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </…

CentOS 7离线升级OpenSSH至9.1p1操作过程及遇上的问题

在文章顶部下载适用于CentOS7的OpenSSH 9.1p1 rpm包&#xff0c;包含了服务器和客户端。 默认全部以root用户权限执行命令。 简单版 懒得看的话&#xff0c;复制以下4行命令执行即可。 tar -zxvf centos7-openssh-9.1p1.tar.gz rpm -Uvh openssh-9.1p1-1.tl2.x86_64.rpm op…

39. UE5 RPG角色释放技能时转向目标方向

在上一篇&#xff0c;我们实现了火球术可以向目标方向发射&#xff0c;并且还可以按住Shift选择方向进行攻击。技能的问题解决&#xff0c;现在人物释放技能时&#xff0c;无法朝向目标方向&#xff0c;接下来我们解决人物的问题。 实现思路&#xff1a; 我们将使用一个官方的…