以下类作为线程运行,从相当空的 Android 应用程序调用(即,该应用程序实际上仅用于生成线程,然后将消息发布到 URLSender)。观察到的行为是建立了连接(即调用
socket.isConnected()
返回 true),但是 while(socket.isConnected){}
只是循环而没有在 inputStream.read()
上接收到任何内容,所以 while (bytesRead != -1){}
永远不会被输入。
添加备注:
我将
ServerSocketHost
和 URLSender
移动到 vanilla Java 控制台驱动程序中,行为看起来是一样的。因此,问题似乎是从 URLConnection
发送方到 ServerSocket
接收方的实际转移。
有什么建议吗?
服务器端接收客户端通信的实现...
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() {
/* will cause run() to terminate execution on evaluation of "while (okToRun)", either after
input has been read or a timeout has occurred
*/
okToRun = false;
}
@Override
public void run() {
BufferedInputStream inputStream = null;
ByteArrayOutputStream byteArrayOS = null;
int initialArraySize = 1024;
byte[] byteInput = new byte[8]; // '8' is an arbitrary number
int bytesRead = -1;
while (okToRun) {
Socket socket = null;
try {
if(serverSocket.isClosed()) {
// ServerSocket is closed on timeout, so reopen if needed
openServerSocket();
}
socket = serverSocket.accept();
while (socket.isConnected()) {
inputStream = new BufferedInputStream(socket.getInputStream());
byteArrayOS = new ByteArrayOutputStream(initialArraySize);
/* BEGIN CONSTRAINED IMPLEMENTATION
the following implementation (using InputStream.read() vice readAllBytes()) is
constrained by the target Android API. InputStream.readAllBytes() is not supported
until API 33, and this implementation targets API 29.
*/
bytesRead = inputStream.read(byteInput);
while (bytesRead != -1) {
byteArrayOS.write(byteInput, 0, bytesRead);
bytesRead = inputStream.read(byteInput);
}
byteArrayOS.flush();
}
} catch(IOException e) {
// should handle the exception, but for now just swallow it
e.printStackTrace();
}
}
}
}
客户端的实现,将消息发布到内部队列,然后将这些消息发布到服务器/主机...
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) {
// should handle the exception, but for now just swallow it
e.printStackTrace();
}
while (synchronizedList.size() > 0) {
try {
String localMessage = synchronizedList.remove(0);
byte[] bytes2write = localMessage.getBytes();
outputStreamWriter.write(localMessage);
outputStreamWriter.flush();
} catch (MalformedURLException e) {
// ?? okay to swallow this exception, as it should have been thrown in constructor
e.printStackTrace();
} catch (IOException e) {
// should handle the exception, but for now just swallow it
e.printStackTrace();
}
}
try {
outputStreamWriter.close();
((HttpURLConnection) urlConnection).disconnect();
} catch(IOException e) {
// should handle the exception, but for now just swallow it
e.printStackTrace();
}
}
} catch(InterruptedException e) {
// swallow the exception (i.e., assume thread is being stopped)
}
}
}
Android 清单,包含使用 HTTP 支持明文的条目
<?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"/>
<!--
Entries "android:networkSecurityConfig" and "android:usesCleartextTraffic" added to
support use of http communications
-->
<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>
<!-- * * * FOR DEMONSTRATION PURPOSES ONLY * * *
Network configuration exists solely to allow the URL socket to be used with plain HTTP
protocol. In a real-world implementation, communication using HTTPS protocol should be
employed, with appropriate certificates.
-->
<base-config cleartextTrafficPermitted="true"></base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
</network-security-config>