The following classes are running as threads, invoked from a fairly empty Android application (i.e., the app is really only being used to spawn the threads and then post a message to URLSender). The observed behavior is that the connection is made (i.e., a call to socket.isConnected() returns true), but while(socket.isConnected){} simply loops without any content ever being received on inputStream.read(), so while (bytesRead != -1){} is never entered.
Add Note:
I moved ServerSocketHost and URLSender into a vanilla Java console driver, and the behavior appears to be the same. So, the issue appears to be the actual transfer from the URLConnection sender to the ServerSocket receiver.
Any suggestions?
Implementation of the server receiving communication from the client...
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();
}
}
}
}
Implementation of the client, which is posting messages to an internal queue and then posting those messages to the server/host...
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 manifest, with entries to support cleartext using 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>
Associated network-security-config...
<?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>