Android Java客户端BufferedReader.readline()无法读取C++ TCP Server返回数据
问题分析与解决方案
你的问题核心在于BufferedReader.readLine()的工作机制和C++服务器的数据格式不匹配,再加上客户端代码的资源管理问题,导致了读取失败的异常。我来一步步拆解原因并给出修复方案:
为什么readLine()会失败?
readLine()的阻塞逻辑:这个方法会一直阻塞,直到它读取到**换行符(\n)**或者输入流被关闭。你的C++服务器只是原封不动回发收到的数据,没有添加换行符,所以客户端的readLine()会一直等待,直到Socket连接因为某种原因断开,这时候就会抛出IOException。- C++服务器的
send调用问题:你用了send(clientsocket, buf, bytesReceived + 1, 0),这里多发送的1字节是buf末尾的\0(字符串结束符),但readLine()并不把\0当作结束标志,反而会把它当作普通数据处理,这不仅解决不了问题,还可能引入垃圾数据。 - 客户端重复创建流资源:每次点击发送按钮,你都重新创建
PrintWriter和BufferedReader,这会导致旧的流被意外关闭或者资源冲突,进一步加剧连接问题。
具体解决措施
第一步:修改C++服务器,添加换行符
在回发数据时,手动添加换行符,让客户端的readLine()能识别结束位置:
char buf[4096]; while (true) { ZeroMemory(buf, 4096); int bytesReceived = recv(clientsocket, buf, 4096, 0); if (bytesReceived == SOCKET_ERROR) { cerr << "Error in recv " << endl; break; } if (bytesReceived == 0) { cout << "client disconnected" << endl; break; } cout << string(buf, 0, bytesReceived) << endl; // 修复:将原数据拼接换行符后回发 string response = string(buf, 0, bytesReceived) + "\n"; send(clientsocket, response.c_str(), response.size(), 0); }
第二步:重构Android客户端代码,优化流管理和线程逻辑
把流的初始化放到Socket连接建立的线程中,只创建一次;同时单独开一个线程持续读取服务器返回的数据,避免阻塞UI操作:
public class MainActivity extends Activity { private EditText e1; private SocketApp sockApp; private PrintWriter printWriter; private BufferedReader bufferedReader; // 替换成你的服务器IP和端口 private final String SERVER_IP = "192.168.1.100"; private final int SERVER_PORT = 8888; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); e1 = findViewById(R.id.editText); // 初始化Socket连接和流资源 sockApp = new SocketApp(SERVER_IP, SERVER_PORT); new Thread(() -> { try { sockApp.run(); if (sockApp.s.isConnected()) { // 只初始化一次流,避免重复创建 printWriter = new PrintWriter(sockApp.s.getOutputStream(), true); bufferedReader = new BufferedReader(new InputStreamReader(sockApp.s.getInputStream())); // 单独开线程读取服务器返回的数据,避免阻塞UI new Thread(() -> { String line; try { while ((line = bufferedReader.readLine()) != null) { // 注意:Android中子线程不能直接更新UI,用runOnUiThread切换 String finalLine = line; runOnUiThread(() -> { Log.d("ServerReply", finalLine); // 如果要显示在界面上,可以更新TextView }); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } }).start(); } public void send_text(View v) { String message = e1.getText().toString(); // 检查流是否可用,避免空指针 if (printWriter != null && !printWriter.checkError()) { // 用println自动添加换行符,方便服务器识别数据结束 printWriter.println(message); } } // 销毁时关闭所有资源,避免内存泄漏 @Override protected void onDestroy() { super.onDestroy(); try { if (bufferedReader != null) bufferedReader.close(); if (printWriter != null) printWriter.close(); if (sockApp != null && sockApp.s != null) sockApp.s.close(); } catch (IOException e) { e.printStackTrace(); } } } // SocketApp类保持基本功能,负责创建Socket连接 class SocketApp { Socket s; String ip; int port; public SocketApp(String ip, int port) { this.ip = ip; this.port = port; } public void run() throws IOException { s = new Socket(ip, port); } }
第三步:别忘了添加网络权限
在AndroidManifest.xml中添加互联网权限,否则App无法建立网络连接:
<uses-permission android:name="android.permission.INTERNET" />
额外说明
- 客户端用
println()发送数据,会自动在末尾添加换行符,这样C++服务器的recv也能更清晰地识别单条数据的边界(当然服务器如果需要处理多行数据,还要做额外判断,但当前场景下足够用)。 - 单独开读取线程是因为
readLine()是阻塞方法,如果放在UI线程会导致界面卡顿,放在发送按钮的点击事件里会阻塞用户操作,所以必须用独立线程处理。
内容的提问来源于stack exchange,提问作者Quazar
相关产品推荐
相关产品推荐

