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,一经查实,立即删除!

相关文章

ASPICE 追溯性实践分享

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

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

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

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

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

性能优化工具

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-------"# 下方命令可以…

算法刷题记录2

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

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

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

嵌入式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;} …

python中中英文打印对齐解决方案

在python中&#xff0c;有时候会出现中英文混合输出的情形&#xff0c;但是由于中文默认是全角格式&#xff08;一个中文字符占用两个字符宽度&#xff09;&#xff0c;这会对python原生的print函数带来一些障碍。尤其是用户用print对齐输出的时候&#xff0c;这种差异会导致文…

顺序表链表经典算法题

1.链表反转 typedef struct ListNode listnode; struct ListNode* reverseList(struct ListNode* head) {if(head NULL){return head;}listnode* p1 NULL;listnode* p2 head;listnode* p3 head->next;while(p2){p2->next p1;p1 p2;p2 p3;if(p3)p3 p3->next;}…

ASP.NET MVC企业级程序设计 (商品管理:小计,总计,删除,排序)

目录 效果图 实现过程 1创建数据库 2创建项目文件 3创建控制器&#xff0c;右键添加&#xff0c;控制器 ​编辑 注意这里要写Home​编辑 创建成功 数据模型创建过程之前作品有具体过程​编辑 4创建DAL 5创建BLL 6创建视图&#xff0c;右键添加视图 ​编辑 7HomeCont…

ST-GCN模型详解(+openpose)

ST-GCN模型详解&#xff08;openpose&#xff09; 一、什么是ST-GCN呢 基于骨架的动作识别&#xff08;Skeleton-Based Action Recognition&#xff09;主要任务是从一系列时间连续的骨骼关键点&#xff08;2D/3D&#xff09;中识别出正在执行的动作。因为牵涉到骨骼框架这种…

智慧公厕解决方案易集成好使用的智能硬件

在现代城市建设中&#xff0c;智慧公厕的需求日益增长。为了提供更好的用户体验和更高效的管理&#xff0c;易集成、好使用的智能硬件成为智慧公厕解决方案的关键组成部分。 1. 蹲位有人无人感应器&#xff1a;是用于检测厕位有人无人的设备&#xff0c;根据现场不同的安装条件…

Flask前端页面文本框展示后端变量,路由函数内外两类

一、外&#xff01;路由函数外的前后端数据传输 Flask后端 ↓ 首先导入包&#xff0c;需要使用 后端&#xff1a;flask_socketio来进行路由外的数据传输&#xff0c; from flask_socketio import SocketIO, emit 前端&#xff1a;还有HTML头文件的设置。 <!DOCTYPE …

DevOps是什么?

DevOps是一系列实践、工具和文化理念的组合&#xff0c;旨在自动化并整合软件开发和信息技术运维团队之间的流程。以下是DevOps的几个关键点&#xff1a; 沟通与协作&#xff1a;DevOps强调开发和运维团队之间的沟通与合作&#xff0c;通过改善这两个部门间的协作关系&#xff…

使用clickhouse-backup备份和恢复数据

作者&#xff1a;俊达 介绍 clickhouse-backup是altinity提供的一个clickhouse数据库备份和恢复的工具&#xff0c;开源项目地址&#xff1a;https://github.com/Altinity/clickhouse-backup 功能上能满足日常数据库备份恢复的需求&#xff1a; 支持单表/全库备份支持备份上…

电脑桌面便签软件哪个好?好用的电脑桌面便签

电脑作为我们日常工作的重要工具&#xff0c;承载着大量的任务和项目。当工作任务繁重时&#xff0c;如何在电脑桌面上高效管理这些任务就显得尤为重要。这时&#xff0c;选择一款优秀的桌面便签软件&#xff0c;无疑会给我们带来极大的便利。 一款好的桌面便签软件&#xff0…

JDK11安装教程

文章目录 1、安装2、配置环境变量 1、安装 双击安装包&#xff0c;点击下一步 更改安装目录&#xff0c;点击下一步 等待安装完成 安装完成 2、配置环境变量 此电脑右键属性 -> 高级系统设置 -> 环境变量 -> 系统变量 -> 新建 变量名&#xff1a;JAVA_HOME变量…

kubernetes学习

1、应用部署方式演变 2、kubernetes介绍 3、kubernetes组件 4、kubernetes概念 5、环境搭建-环境规划 6、环境搭建-主机安装 7、环境搭建-环境初始化 8、环境搭建-集群所需组件安装 9、环境搭建-集群安装 10、环境搭建-网络插件安装 11、环境搭建-环境测试 12、资源管理…

Databend 开源周报第 140 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 支持 EXECUTE I…

3D模型格式转换工具HOOPS Exchange:3D CAD数据的快速导入与导出

在当今的工程设计领域中&#xff0c;快速且可靠地处理3D CAD数据是至关重要的。HOOPS Exchange SDK通过提供一组C软件库&#xff0c;为开发团队提供了实现这一目标的有效工具。 什么是HOOPS Exchange&#xff1f; HOOPS Exchange是一组C软件库&#xff0c;旨在为开发团队提供…