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

news2024/9/20 0:53:49

最近在整理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 {
        // 连接server
        Socket 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;
    }

    @Override
    public 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 {
        // 连接server
        Socket 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;
    }

    @Override
    public 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.coloradmin.cn/o/1617621.html

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

相关文章

​「Python绘图」绘制皮卡丘

python 绘制皮卡丘 一、预期结果 二、核心代码 import turtle print("开始绘制皮卡丘") def getPosition(x, y):turtle.setx(x)turtle.sety(y)print(x, y)class Pikachu:def __init__(self):self.t turtle.Turtle()t self.tt.pensize(3)t.speed(190)t.ondrag(getP…

隋总分享:Temu选品师算不算是蓝海项目?

在当今日新月异的互联网经济浪潮中,跨境电商正成为一股不可忽视的力量。最近,网红隋总对Temu选品师这一职业进行了深入介绍,引发了广泛关注。那么,Temu选品师是否真的可以视为一个蓝海项目呢?本文将对此进行一番细致的探讨。 首先…

python学习笔记B-08:序列结构之列表--列表的遍历操作

列表的遍历操作主要通过以下三种方法实现。 lst list("hello") print("第一种遍历方式,使用for循环,循环变量item直接就是lst中的元素") for item in lst:print(item,end"\t")print("\n\n第二种遍历方式&#xff0…

【HarmonyOS】Stage 模型 - 基本概念

一、项目结构 如图1所示: 图1 从项目结构来看,这个应用的内部包含了一个子模块叫 entry,模块是应用的基本功能单元,它里面包含源代码、资源、配置文件等。 像这样的模块在应用内部可以创建很多。但模块整体来讲就分成两大类&am…

unity中重构(分拆)输入代码

codemonkey的混乱厨房第14节,讲述了怎么来重构代码。 目的:是减少和管理的复杂性,每个类只做一件事,只能做一件事 重构思路分析: 空对象 挂着新类脚本 新类{ 公开方法 public 带返回值 } --------------------------…

【大模型开源篇1】彦宏您怎么看LLaMA3的开源

Meta LLaMA是Meta公司开源的大模型,作为大模型开源界得鼻祖, 刚刚发布LLaMA3。从ChatGPT 拉开了大模型竞赛的序幕,Meta 选择了开源,至此大模型也开始百花齐放的时期,但是开源模型一直无法超过必源模型,如今…

2024最新Notepad++ 的下载、安装及配置

Notepad是一款免费且开源的文本和源代码编辑器,支持多种编程语言。它运行在MS Windows环境下,使用GPL许可证发布。由于其轻量级和高效性,成为了许多开发者的首选工具。 下载地址:「Notedpad」, 链接:https…

报销管理如何秒变高效神器?

一、客户介绍 易快报作为业界知名的企业报销管理平台,一直致力于为企业提供高效、便捷的报销服务。随着业务的不断扩展,易快报面临着与多个外部系统进行数据交互的需求,以提升工作效率和减少数据差错。中信建投作为国内领先的金融机构&#…

GLID: Pre-training a Generalist Encoder-Decoder Vision Model

1 研究目的 现在存在的问题是: 目前,尽管自监督预训练方法(如Masked Autoencoder)在迁移学习中取得了成功,但对于不同的下游任务,仍需要附加任务特定的子架构,这些特定于任务的子架构很复杂&am…

关于C++STL的总结(基础使用和底层原理)

STL是什么? STL即(Standard Template Library)标准模板库,提供了常见的数据结构和算法函数等,其下共包含六大组件: 容器算法迭代器仿函数适配器空间配置器 本篇重点介绍容器的使用和简单的底层实现原理&…

不容忽视的听力“杀手”

音乐与短视频已成为现代生活的重要组成部分,许多人在繁忙的日常中依赖耳机来获得片刻的宁静或激情。然而,我们也需要警惕,不当的耳机使用可能对我们的听力造成风险。长时间以高音量使用耳机,无疑会对听力构成挑战。 *图片源于网络…

贪吃蛇项目实战——学习详解

前言:贪吃蛇是一个经典的游戏, 本节将使用c语言实现一个简易的的贪吃蛇小游戏。 本节内容适合已经学完c语言还有数据结构链表的友友们。 我们要实现的贪吃蛇是在控制台进行游戏的。 它运行起来是这样的: 贪吃蛇 那么, 为了实现这个小游戏。 我…

图解KMP算法——字符串搜索

原文:最初发布地址 一、问题描述 来源:Leetcode 难度:中等 给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 hays…

2024年最新版云开发cms开通步骤,开始开发微信小程序前的准备工作,认真看完奥!

小程序官方有改版了,搞得石头哥不得不紧急的再新出一版,教大家开通最新版的cms网页管理后台 一,技术选型和技术点 1,小程序前端 wxml css JavaScript MINA原生小程序框架 2,数据库 云开发 云数据库 云…

SOTAX溶出测试系统PC触摸屏维修三部曲

SOTAX溶出测试系统作为一款广泛应用于制药行业的知名品牌,具有高精度、操作简便、稳定性好等特点。它适用于各种类型的药品研发和生产环节,为科研人员提供可靠的数据支持。瑞士SOTAX溶出仪是实验室中常用的设备,其触摸屏是用户交互的重要界面…

程序员读的经典著作有哪些?

一、程序员读的经典著作有哪些? 在编程的世界里,阅读经典著作不仅能够帮助我们深入理解编程的本质,也能为我们提供解决问题的新思路和方法。以下是几本被广大程序员推崇的经典著作,每本书都有其独特的价值和深远的影响。 1. 《代…

【C++】双指针算法:快乐数

1.题目 题目中一定要理解快乐数的含义,否则题目难度直逼困难。 在示例1中n19,经过几步操作后结果变成1。 那么示例2中n2是什么情况呢: 2->4->16->37->58->89->145->42->20->4(与前面的4形成闭环) 在计算机中in…

苍穹外卖day11 Apache ECharts 数据统计-图形报表

文章目录 前言一、Apache ECharts二、营业额统计1. 业务规则2. 接口设计3. 代码实现 三、用户统计1. 业务规则2. 接口设计3. 代码实现 四、订单统计1. 业务规则2. 接口设计3. 代码实现 五、销量排名Top101. 业务规则2. 接口设计3. 代码实现 前言 作为后端开发人员使用Echarts&…

【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 | 再谈构造函数:初始化列表,隐式类型转换,缺省值)

🔥个人主页:Forcible Bug Maker 🔥专栏:C 目录 前言 取地址及const取地址操作符重载 再谈构造函数 初始化列表 隐式类型转换 explicit关键字 成员变量缺省值 结语 前言 本篇主要内容:类的六个默认成员函数中…

算法训练营day15

一、层序遍历 参考链接7.2 二叉树遍历 - Hello 算法 (hello-algo.com) 层序遍历本质上属于广度优先遍历,也称广度优先搜索, BFS通常借助队列的先入先出的特性实现 参考链接102. 二叉树的层序遍历 - 力扣(LeetCode) 像这种较为…