Android向C++ REST API发送大体积POST数据失败问题排查
看起来你遇到的问题是Android端复用DefaultHttpClient发送大体积POST请求时,C++后端仅收到请求头,缺失POST数据,但用Insomnia测试正常、减少POST字段也能正常工作。结合你提供的代码和现象,我整理了两个核心问题点和对应的解决办法:
一、C++ Socket接收逻辑的致命缺陷
你的receiveDataOnSocket方法的循环条件是:
while (bytesReceived == this->bufferLength)
这个逻辑的问题在于:只有当本次读取的字节数刚好填满缓冲区时,才会继续读取;如果某次读取的字节数小于缓冲区长度(比如请求头刚好读完、或者最后一段body数据不足缓冲区长度),循环会直接终止,导致后续的数据(比如POST body)完全没有被读取。
小请求能正常工作,是因为头+body的总长度刚好在一次recv中就读完了(字节数等于缓冲区长度);Insomnia可能是一次性发送了所有数据,刚好填满缓冲区,所以能完整接收;但Android端的DefaultHttpClient可能会分开发送请求头和body,这时候读完头后bytesReceived < bufferLength,循环停止,body就丢失了。
修复方案:根据HTTP的Content-Length来读取完整数据
HTTP请求头里有Content-Length字段,标明了body的总长度,你应该:
- 先读取到完整的HTTP头(直到遇到
\r\n\r\n分隔符) - 解析出
Content-Length的值,计算出需要读取的body总长度 - 循环读取,直到读完所有body数据,而不是依赖缓冲区是否读满
修改后的接收逻辑大致如下:
string LinuxSocket::receiveDataOnSocket(int *socket) { string receiveData = ""; char buffer[this->bufferLength]; int bytesReceived = 0; // 第一步:读取完整的HTTP头(直到\r\n\r\n分隔符) bool headerComplete = false; while (!headerComplete) { bytesReceived = recv(*socket, buffer, this->bufferLength, 0); if (bytesReceived <= 0) { stringstream logstream; logstream << "Failed to read data on socket. Error: " << strerror(errno); this->bitsLibrary->writeToLog(logstream.str(), "LinuxSocket", "receiveDataOnSocket"); this->closeSocket(socket); throw SocketException(strerror(errno)); } receiveData.append(buffer, bytesReceived); // 检查是否出现头和body的分隔符 size_t sepPos = receiveData.find("\r\n\r\n"); if (sepPos != string::npos) { headerComplete = true; } } // 第二步:解析Content-Length字段 size_t contentLenPos = receiveData.find("Content-Length: "); if (contentLenPos == string::npos) { // 没有Content-Length,直接返回已读取的内容(比如GET请求) return receiveData; } contentLenPos += strlen("Content-Length: "); size_t endOfLine = receiveData.find("\r\n", contentLenPos); int contentLength = stoi(receiveData.substr(contentLenPos, endOfLine - contentLenPos)); // 第三步:读取剩余的body数据 size_t bodyStartPos = receiveData.find("\r\n\r\n") + 4; int receivedBodyLength = receiveData.size() - bodyStartPos; while (receivedBodyLength < contentLength) { // 计算还需要读取的字节数,避免超过缓冲区长度 int needRead = min(this->bufferLength, contentLength - receivedBodyLength); bytesReceived = recv(*socket, buffer, needRead, 0); if (bytesReceived <= 0) { stringstream logstream; logstream << "Failed to read body data. Error: " << strerror(errno); this->bitsLibrary->writeToLog(logstream.str(), "LinuxSocket", "receiveDataOnSocket"); this->closeSocket(socket); throw SocketException(strerror(errno)); } receiveData.append(buffer, bytesReceived); receivedBodyLength += bytesReceived; } return receiveData; }
二、Android端DefaultHttpClient的潜在问题
DefaultHttpClient是Android API 22就已经废弃的类,官方早就推荐使用HttpURLConnection或者OkHttp替代。复用它的时候,可能存在连接池、请求缓冲的隐藏问题,导致大请求的body没有被正确发送。
修复方案:
- 替换为OkHttp(推荐):OkHttp是现代、稳定的HTTP客户端,自动处理连接复用、请求发送等问题,彻底避免旧API的坑。示例代码:
// 先添加OkHttp依赖(Gradle) // implementation 'com.squareup.okhttp3:okhttp:4.12.0' // 创建全局OkHttpClient实例(可复用) private OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(3, TimeUnit.SECONDS) .readTimeout(3, TimeUnit.SECONDS) .build(); // 发送POST请求的方法 private JSONObject sendPostRequest(String serverURL, String authorisationToken, List<BasicNameValuePair> postData) { FormBody.Builder formBuilder = new FormBody.Builder(); for (BasicNameValuePair pair : postData) { formBuilder.add(pair.getName(), pair.getValue()); } RequestBody requestBody = formBuilder.build(); Request request = new Request.Builder() .url(serverURL) .addHeader("Authorisation-Token", authorisationToken) .addHeader("Connection", "close") .post(requestBody) .build(); try (Response response = okHttpClient.newCall(request).execute()) { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); String responseBody = response.body().string(); Log.d("Response", responseBody); return new JSONObject(responseBody); } catch (Exception e) { e.printStackTrace(); return null; } }
- 如果必须使用
DefaultHttpClient:每次发送大请求时,不要复用HttpClient实例,而是每次创建新的实例,避免连接复用带来的问题:
// 移除全局httpClient,每次请求都创建新实例 DefaultHttpClient httpClient = new DefaultHttpClient(); HttpParams httpParams = httpClient.getParams(); HttpConnectionParams.setConnectionTimeout(httpParams, 3000); HttpConnectionParams.setSoTimeout(httpParams, 3000); // 后续请求代码不变...
总结
优先修复C端的接收逻辑,这是最根本的问题——即使Android端没问题,当前的C代码也无法正确处理分块发送的HTTP请求。然后建议替换Android端的HTTP客户端为OkHttp,彻底避免旧API的潜在问题。
内容的提问来源于stack exchange,提问作者Boardy

