Android中ServerSocket无法接收URLConnection客户端内容的问题
问题描述
我将以下类作为线程运行,通过一个极简Android应用启动(该应用仅用于生成线程并向URLSender发送消息)。目前观察到的现象是:连接已建立(调用socket.isConnected()返回true),但while(socket.isConnected())循环持续执行,inputStream.read()始终未接收到任何内容,导致while (bytesRead != -1)循环从未进入。
补充说明:我把ServerSocketHost和URLSender迁移到普通Java控制台程序中,现象依然存在。因此问题似乎出在URLConnection发送端到ServerSocket接收端的实际传输环节。
服务端实现代码(ServerSocketHost)
public class ServerSocketHost implements Runnable { public static final int HOST_PORT = 45000; public static final byte[] HOST_IP = new byte[]{127, 0, 0, 1}; private static InetSocketAddress hostAddress = null; private static final int DEFAULT_TIMEOUT = 5000; private static final int CONNECT_BACKLOG = 50; private boolean okToRun = true; private ServerSocket serverSocket = null; private int timeoutValue = -1; public ServerSocketHost() throws IOException { this(DEFAULT_TIMEOUT); } public ServerSocketHost(int timeoutValue) throws IOException { this.timeoutValue = timeoutValue; getServerAddress(); openServerSocket(); } private void openServerSocket() throws IOException { serverSocket = new ServerSocket(HOST_PORT); serverSocket.setSoTimeout(timeoutValue); } public void shutdown() { okToRun = false; } @Override public void run() { BufferedInputStream inputStream = null; ByteArrayOutputStream byteArrayOS = null; int initialArraySize = 1024; byte[] byteInput = new byte[8]; int bytesRead = -1; while (okToRun) { Socket socket = null; try { if(serverSocket.isClosed()) { openServerSocket(); } socket = serverSocket.accept(); while (socket.isConnected()) { inputStream = new BufferedInputStream(socket.getInputStream()); byteArrayOS = new ByteArrayOutputStream(initialArraySize); bytesRead = inputStream.read(byteInput); while (bytesRead != -1) { byteArrayOS.write(byteInput, 0, bytesRead); bytesRead = inputStream.read(byteInput); } byteArrayOS.flush(); } } catch(IOException e) { e.printStackTrace(); } } } }
客户端实现代码(URLSender)
public class URLSender implements Runnable { private boolean okToRun = true; private URL hostURL = null; private List<String> synchronizedList = null; public URLSender() throws MalformedURLException { this.hostURL = new URL( "http://127.0.0.1:45000"); this.synchronizedList = Collections.synchronizedList(new LinkedList<String>()); } public void shutdown() { okToRun = false; } public void postMessage(String message) { synchronized (synchronizedList) { synchronizedList.add(message + " (1)\n"); synchronizedList.add(message + " (2)\n"); synchronizedList.add(message + " (3)\n"); synchronizedList.notify(); } } public void run() { URLConnection urlConnection = null; OutputStreamWriter outputStreamWriter = null; try { while (okToRun) { if (synchronizedList.isEmpty()) { synchronized (synchronizedList) { synchronizedList.wait(); } } try { urlConnection = hostURL.openConnection(); urlConnection.setDoOutput(true); outputStreamWriter = new OutputStreamWriter(urlConnection.getOutputStream()); } catch(IOException e) { e.printStackTrace(); } while (synchronizedList.size() > 0) { try { String localMessage = synchronizedList.remove(0); byte[] bytes2write = localMessage.getBytes(); outputStreamWriter.write(localMessage); outputStreamWriter.flush(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } try { outputStreamWriter.close(); ((HttpURLConnection) urlConnection).disconnect(); } catch(IOException e) { e.printStackTrace(); } } } catch(InterruptedException e) { } } }
Android清单文件
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <application android:networkSecurityConfig="@xml/xml_security_config" android:usesCleartextTraffic="true" android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.URLConnect" tools:targetApi="29"> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.URLConnect.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
网络安全配置文件
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true"></base-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">127.0.0.1</domain> </domain-config> </network-security-config>
解决建议
核心问题:协议不匹配
你的客户端使用HTTP协议(通过URLConnection访问http://地址),但服务端只是一个原始TCP Socket(ServerSocket),两者协议完全不兼容。HTTP是基于TCP的应用层协议,有固定的请求格式(比如请求行、请求头、请求体的结构),而你的服务端没有处理HTTP协议的逻辑,自然无法识别客户端发送的HTTP格式数据,导致读不到有效内容。
具体修复方案
方案1:客户端改用原始TCP Socket(推荐,适配现有服务端)
把URLSender的HTTP通信逻辑改成直接用Socket连接,和服务端的TCP Socket协议匹配:
public class URLSender implements Runnable { private boolean okToRun = true; private final String hostIp = "127.0.0.1"; private final int hostPort = 45000; private List<String> synchronizedList = null; public URLSender() { this.synchronizedList = Collections.synchronizedList(new LinkedList<String>()); } public void shutdown() { okToRun = false; } public void postMessage(String message) { synchronized (synchronizedList) { synchronizedList.add(message + " (1)\n"); synchronizedList.add(message + " (2)\n"); synchronizedList.add(message + " (3)\n"); synchronizedList.notify(); } } @Override public void run() { try { while (okToRun) { if (synchronizedList.isEmpty()) { synchronized (synchronizedList) { synchronizedList.wait(); } } // 直接建立TCP Socket连接 Socket socket = new Socket(hostIp, hostPort); try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(socket.getOutputStream())) { while (synchronizedList.size() > 0) { String localMessage = synchronizedList.remove(0); outputStreamWriter.write(localMessage); outputStreamWriter.flush(); } } finally { // 关闭Socket,告知服务端数据发送完毕 socket.close(); } } } catch (InterruptedException | IOException e) { e.printStackTrace(); } } }
方案2:服务端改成HTTP服务器
如果一定要使用HTTP协议,服务端需要处理HTTP请求的格式,比如用Java内置的HttpServer类(Android API 26+支持):
public class ServerSocketHost implements Runnable { public static final int HOST_PORT = 45000; private boolean okToRun = true; private HttpServer httpServer; public ServerSocketHost() throws IOException { httpServer = HttpServer.create(new InetSocketAddress(HOST_PORT), 0); // 处理根路径的HTTP请求 httpServer.createContext("/", exchange -> { // 读取HTTP请求体内容 InputStream inputStream = exchange.getRequestBody(); ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream(); byte[] byteInput = new byte[8]; int bytesRead; while ((bytesRead = inputStream.read(byteInput)) != -1) { byteArrayOS.write(byteInput, 0, bytesRead); } System.out.println("收到数据:" + byteArrayOS.toString()); // 发送HTTP响应告知客户端接收完成 exchange.sendResponseHeaders(200, 0); exchange.close(); }); httpServer.setExecutor(null); // 使用默认线程池 } public void shutdown() { okToRun = false; httpServer.stop(0); } @Override public void run() { httpServer.start(); while (okToRun) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
其他细节修复
- 服务端循环逻辑优化:原代码中
while (socket.isConnected())会无限循环,因为socket.isConnected()在连接建立后除非主动关闭,否则一直为true。应该改为读取数据直到流结束,或者添加超时判断。 - 资源自动关闭:使用try-with-resources语法(Android API 19+支持),确保
InputStream、OutputStream等资源自动关闭,避免内存泄漏。 - 超时设置:给客户端Socket添加连接超时和读取超时,避免无限等待:
Socket socket = new Socket(); socket.connect(new InetSocketAddress(hostIp, hostPort), 5000); // 5秒连接超时 socket.setSoTimeout(5000); // 5秒读取超时
内容的提问来源于stack exchange,提问作者SoCal
相关产品推荐
相关产品推荐

