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

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

需求:

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

问题:

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

上代码:

客户端:

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);}}
}

服务端代码:

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 调整客户端获取用户输入数据的方式,把用户的换行符直接读取过来后,用原来的方式发送

    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类似方式序列号和反序列话;

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

客户端:

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);}}
}

服务端:

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网络编程 BufferedReader的readLine方法读不到数据的原因_java后台服务端bufferedreader不能读全数据 前台出现超时提示-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/825889.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7、docker 集群

docker集群 1、docker file参数设置 2、docker composeCompose和Docker兼容性常用参数Docker-compose.yml配置文件Docker-compose命令大全compose常用调试命令compose文件格式示例应用python flask-rediscpu和gpu配置 附件参考: 1、docker file 参考:ht…

ASPICE 追溯性实践分享

01前言 接着之前的分享,遗留的追溯性ASPICE 认证实践及个人理解分享-CSDN博客文章浏览阅读961次,点赞22次,收藏17次。ASPICE是Automotive 和SPICE的组合,全英文为(Automotive Software ProcessImprovement and Determ…

树的遍历算法题总结(第二十六天)

144. 二叉树的前序遍历 题目 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 答案 class Solution {List<Integer> res new ArrayList(); public List<Integer> preorderTraversal(TreeNode root) {deal(root);return res;}void deal(TreeN…

C++修炼之路之继承<二>

目录 一&#xff1a;子类的六大默认成员函数 二&#xff1a;继承与友元 三&#xff1a;继承与静态成员 四&#xff1a;复杂的继承关系菱形继承菱形虚拟继承 1.单继承 2.多继承 3.菱形继承&#xff1b;一种特殊的多继承 4.菱形虚拟继承 5.虚拟继承解决数据冗余和二…

华为OD-C卷-内存冷热标记[100分]C++-100%

题目描述 现代计算机系统中通常存在多级的存储设备,针对海量 workload 的优化的一种思路是将热点内存页优先放到快速存储层级,这就需要对内存页进行冷热标记。 一种典型的方案是基于内存页的访问频次进行标记,如果统计窗口内访问次数大于等于设定阈值,则认为是热内存页,…

小程序 前端如何用wx.request获取 access_token接口调用凭据

在微信小程序中,获取access_token通常是通过wx.request方法来实现的。以下是一个简单的示例代码: 1.获取小程序的appID 与 secret(小程序密钥) 登录之后,请点击左侧的"开发管理"==>点击"开发设置" 就可以找到 2. 在javascript 中的代码: // 定…

【大模型完全入门手册】——大模型入门理论(基于Transformer的预训练语言模型)

博主作为一名大模型开发算法工程师,很希望能够将所学到的以及实践中感悟到的内容梳理成为书籍。作为先导,以专栏的形式先整理内容,后续进行不断更新完善。希望能够构建起从理论到实践的全流程体系。 助力更多的人了解大模型,接触大模型,一起感受AI的魅力! Transformer架构…

性能优化工具

CPU 优化的各类工具 network netperf 服务端&#xff1a; $ netserver Starting netserver with host IN(6)ADDR_ANY port 12865 and family AF_UNSPEC$ cat netperf.sh #!/bin/bash count$1 for ((i1;i<count;i)) doecho "Instance:$i-------"# 下方命令可以…

Rust 语言使用 SQLite 数据库

SQLite 是一种广泛使用的轻量级数据库&#xff0c;它通过简单的文件来承载数据&#xff0c;无需复杂的服务器配置。正因如此&#xff0c;它成为了许多桌面和移动应用的首选数据库。在 Rust 生态中&#xff0c;rusqlite 库为开发者提供了操作 SQLite 数据库的简洁且有效的方法。…

如何用Redis高效实现12306的复杂售票业务

12306的售票业务是一个复杂的系统&#xff0c;需要考虑高并发、高可用、数据一致性等问题。使用Redis作为缓存和持久化存储&#xff0c;可以提高系统的性能和可扩展性&#xff0c;以下是一些可能的实现方式&#xff1a; 1 票源信息缓存&#xff1a;将票源信息&#xff08;如车次…

算法刷题记录2

4.图 4.1.被围绕的区域 思路&#xff1a;图中只有与边界上联通的O才不算是被X包围。因此本题就是从边界上的O开始递归&#xff0c;找与边界O联通的O&#xff0c;并标记为#&#xff08;代表已遍历&#xff09;&#xff0c;最后图中剩下的O就是&#xff1a;被X包围的O。图中所有…

SQC、SQA

QC 品质控制/质量控制&#xff08;QC即英文Quality Control的简称&#xff0c;中文意义是品质控制&#xff09;其在ISO8402&#xff1a;1994的定义是“为达到品质要求所采取的作业技术和活动”。有些推行ISO9000的组织会设置这样一个部门或岗位&#xff0c;负责ISO9000标准所要…

Spring Boot 中 Controller 接口参数注解全攻略与实战案例详解

引言 在 Spring Boot 应用程序中&#xff0c;Controller 是 MVC 架构模式中的核心组件之一&#xff0c;负责处理 HTTP 请求并返回响应结果。为了更好地映射请求、解析请求参数、执行业务逻辑和生成视图或 JSON 数据&#xff0c;Controller 中广泛使用了各种注解。本文将全面梳…

温湿度传感器(DHT11)以及光照强度传感器(BH1750)的使用

前言 对于一些单片机类的环境检测或者智能家居小项目中&#xff0c;温湿度传感器&#xff08;DHT11&#xff09;以及光照强度传感器&#xff08;BH1750&#xff09;往往是必不可少的两个外设&#xff0c;下面我们来剖析这两个外设的原理&#xff0c;以及使用。 1. 温湿度传感…

C语言中,__attribute__关键字

在C语言中&#xff0c;__attribute__是一种特殊的关键字&#xff0c;用于提供关于变量、函数或类型的附加信息。这些信息可以用于编译器优化、代码检查或其他目的。 以下是一些常见的C语言attribute及其用法&#xff1a; 1. __attribute__((const))&#xff1a;表示一个变量的…

Prometheus指标

文章目录 Prometheus指标主要参数解释一、可用性监测(0代表存在异常或未启动,1代表运行中)二、节点监测三、服务监测1.HDFS监测2.Yarn监测3.Hive监测4.Kafka监测5.Zookeeper监测Prometheus指标 主要参数解释 # 节点IP和端口(instance) 例如:192.168.1.226:9100、192.168.1.…

java篇-Springboot解决跨域问题的三种方式

第一种&#xff1a;添加CrossOrigin注解 在Controller层对应的方法上添加CrossOrigin或者类上添加CrossOrigin package com.example.controller;import com.example.model.Book; import com.example.service.InBookService; import org.springframework.beans.factory.anno…

软件工程的生命周期

软件工程的生命周期 1.市场调研用户的需求&#xff0c;并进行可行性分析&#xff08;从多个角度分析能否达到预期收益&#xff09;。 2.立项&#xff1a;确定项目组核心骨干成员&#xff0c;以及各阶段的里程碑。 3.需求调研&#xff1a;产品经理深度挖掘用户需求&#xff0c;将…

简明 Python 教程(第14章 Python的多线程)

Python多线程是指在Python程序中可以同时运行多个线程&#xff0c;每个线程可以执行不同的任务。Python提供了两个标准库来支持多线程&#xff1a;threading和_thread。通常&#xff0c;推荐使用threading模块&#xff0c;因为它提供了更高级别的API&#xff0c;更易于使用。 …

嵌入式4-18

做一个简单数据库终端操作系统 #include <myhead.h> int main(int argc, const char *argv[]) {int id;char name[16];float score;sqlite3 *pNULL;if(sqlite3_open("./my.db",&p)!SQLITE_OK){printf("sqlite3_open error\n");return -1;} …