You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
            }
        }
    }
}

其他细节修复

  1. 服务端循环逻辑优化:原代码中while (socket.isConnected())会无限循环,因为socket.isConnected()在连接建立后除非主动关闭,否则一直为true。应该改为读取数据直到流结束,或者添加超时判断。
  2. 资源自动关闭:使用try-with-resources语法(Android API 19+支持),确保InputStream、OutputStream等资源自动关闭,避免内存泄漏。
  3. 超时设置:给客户端Socket添加连接超时和读取超时,避免无限等待:
    Socket socket = new Socket();
    socket.connect(new InetSocketAddress(hostIp, hostPort), 5000); // 5秒连接超时
    socket.setSoTimeout(5000); // 5秒读取超时
    

内容的提问来源于stack exchange,提问作者SoCal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 11:24:58