沈阳双兴建设集团有限公司网站,网站建设策划方案范文,wordpress动态导航侧边栏,seo网站优化系统最近在整理Java IO相关内容#xff0c;会遇到一些以前没有注意的问题#xff0c;特此记录#xff0c;以供自查和交流。
需求#xff1a;
基于Java的BIO API#xff0c;实现简单的客户端和服务端通信模型#xff0c;客户端使用BufferedReader的readLine方法读取System.i…最近在整理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 falsereadLine方法就会回到 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博客