Java custom class loader issue - java

I'm sending a Class object from client to server side. Every time the server needs to load the Class object sent by the client instead of reusing it by parent delegation model (when it was loaded during the 1st iteration).
I'm trying to use a custom class loader on the server side whose loadClass(String) simply calls findClass() instead of checking with parent hierarchy.
To achieve this, I'm doing following:
Generate byte[] by reading the .class file on the client side as following:
Class cl = com.example.XYZ.class;
String path = cl.getName().replace('.', '/') + ".class";
InputStream is = cl.getClassLoader().getResourceAsStream(path);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int data = -1;
while((data=is.read())!=-1)
bos.write(data);
byte[] classBinaryData = bos.toByteArray();
I'm sending classBinaryData to the server side.
On the server side, every time I retrieve the byte[], verify if it's the same as on client side by matching MD5 checksum, then I create a new instance of my custom class loader and pass the byte array so it could be used in calling defineClass from within findClass.
However, I'm getting either of the errors (depending on the way I create byte[] out of .class)
Incompatible magic value ..... in class file <Unknown>
OR
com/example/XYZ (wrong name: com/example/XYZ) coming from defineClass
I need help in figuring out the mistake in my approach/code.

Your byte[] generation code looks fine.
When I used the byte array generated form your code to load the class with following class loader code, it was able to load the class successfully.
class CustomClassLoader extends ClassLoader {
public Class loadTheClass(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length);
}
}
Using this classloader like this
CustomClassLoader ccl = new CustomClassLoader();
Class cz = ccl.loadTheClass("com.example.XYZ", classBinaryData);
Object o = cz.newInstance();
I think you must use '.' instead of '/' in the name when you are loading the class at server side.
And ensure that the byte array data is not changed in your other code.

Your code looks fine. Your error is somewhere else.
You are, in some way, returning bad class files from your class loader.
The first error means the byte array is totally garbled; the first 4 bytes are wrong. You can check them easily (they have to be 0xCAFEBABE), to catch this error earlier.
The other error, I think, means that you are returning the definition of a different class than was requested.

1. Missing Dot Notation
com/example/XYZ (wrong name: com/example/XYZ) coming from defineClass
You should be using dot notation, i.e., com.example.XYZ
Class clazz = classLoader.loadCustomClass("com.example.XYZ", bytes);
2. Invalid Magic Number (Corrupt Class Bytes)
Incompatible magic value ..... in class file
You are getting the above error because the start of the class byte array is corrupted. It's complaining about Incompatible magic value by throwing a java.lang.ClassFormatError. It usually happens when the class loader doesn't find 0xCAFEBABE (magic number) at the beginning of the class bytes.
Here is a simple example by which you can recreate the error.
In this example, the com.basaki.model.Book class file is saved as a Base64 encoded string.
The method testLoadingClassWithCorrectMagicNumber tries to load the class from the Base64 encoded string after decoding it to a byte array. It loads normally without any incident.
In method testLoadingClassWithIncorrectCorrectMagicNumber, the byte array (after the Base64 string is decoded) is corrupted by replacing the first character from c to b. Now instead of the magic number being 0xCAFEBABE, it is 0xBAFEBABE. The class loader now throws the following exception while trying to load the corrupt binary array,
java.lang.ClassFormatError: Incompatible magic value 3137256126 in class file com/basaki/model/Book
public class LoadingBookFromBinaryArrayTest {
private static class MyCustomClassLoader extends ClassLoader {
public Class loadCustomClass(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length);
}
}
public static String BOOK_CLAZZ = "yv66vgAAADQAHQoABQAYCQAEABkJAAQAGgcAGwcAHAEABXRpdGxlAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGYXV0aG9yAQAGPGluaXQ-AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMY29tL2Jhc2FraS9tb2RlbC9Cb29rOwEACGdldFRpdGxlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhzZXRUaXRsZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEACWdldEF1dGhvcgEACXNldEF1dGhvcgEAClNvdXJjZUZpbGUBAAlCb29rLmphdmEMAAkACgwABgAHDAAIAAcBABVjb20vYmFzYWtpL21vZGVsL0Jvb2sBABBqYXZhL2xhbmcvT2JqZWN0ACEABAAFAAAAAgACAAYABwAAAAIACAAHAAAABQABAAkACgABAAsAAAAvAAEAAQAAAAUqtwABsQAAAAIADAAAAAYAAQAAAAMADQAAAAwAAQAAAAUADgAPAAAAAQAQABEAAQALAAAALwABAAEAAAAFKrQAArAAAAACAAwAAAAGAAEAAAAJAA0AAAAMAAEAAAAFAA4ADwAAAAEAEgATAAEACwAAAD4AAgACAAAABiortQACsQAAAAIADAAAAAoAAgAAAA0ABQAOAA0AAAAWAAIAAAAGAA4ADwAAAAAABgAGAAcAAQABABQAEQABAAsAAAAvAAEAAQAAAAUqtAADsAAAAAIADAAAAAYAAQAAABEADQAAAAwAAQAAAAUADgAPAAAAAQAVABMAAQALAAAAPgACAAIAAAAGKiu1AAOxAAAAAgAMAAAACgACAAAAFQAFABYADQAAABYAAgAAAAYADgAPAAAAAAAGAAgABwABAAEAFgAAAAIAFw==";
#Test
public void testLoadingClassWithCorrectMagicNumber() throws IllegalAccessException, InstantiationException, DecoderException {
byte[] bytes = Base64.getUrlDecoder().decode(BOOK_CLAZZ);
MyCustomClassLoader classLoader = new MyCustomClassLoader();
Class clazz = classLoader.loadCustomClass("com.basaki.model.Book", bytes);
}
#Test(expected = ClassFormatError.class)
public void testLoadingClassWithIncorrectCorrectMagicNumber() throws IllegalAccessException, InstantiationException, DecoderException {
byte[] bytes = Base64.getUrlDecoder().decode(BOOK_CLAZZ);
String hex = Hex.encodeHexString(bytes);
System.out.println(hex);
// changing magic number 0xCAFEBABE to invalid 0xBAFEBABE
String malHex = "b" + hex.substring(1, hex.length());
System.out.println(malHex);
byte[] malBytes = Hex.decodeHex(malHex.toCharArray());
MyCustomClassLoader classLoader = new MyCustomClassLoader();
Class clazz = classLoader.loadCustomClass("com.basaki.model.Book", bytes9);
}
}

As has been stated above the issue seems to be somewhere else besides the gathering of the byte array. It is possible that the bytes are not being processed properly on the server side. I've created a fairly simple example that is similar to what you are doing but it shows how I send and receive the class byte array.
package org.valhalla.classloader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class RemoteClassLoader extends ClassLoader {
private Socket socket;
private DataOutputStream dos;
private DataInputStream dis;
public RemoteClassLoader(Socket socket, ClassLoader parent) {
super(parent);
this.socket = socket;
OutputStream os;
InputStream is;
try {
os = socket.getOutputStream();
is = socket.getInputStream();
} catch (IOException e) {
throw new RuntimeException("Unable to get Socket output stream", e);
}
dos = new DataOutputStream(os);
dis = new DataInputStream(is);
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clz = null;
System.out.println("Looking up class: " + name);
synchronized(this.getClassLoadingLock(name)) {
try {
System.out.println("Sending request for class: " + name);
dos.writeUTF(name);
boolean success = dis.readBoolean();
System.out.println("Action was " + success);
if (success) {
// Get bytes;
System.out.println("Reading size of class file");
int len = dis.readInt();
System.out.println("Size of class is " + len);
byte data[] = new byte[len];
int cur, size = 0;
for (cur = 0 ; cur < len ; cur += size) {
size = dis.read(data, cur, len - cur);
System.out.println("Read size: " + size);
}
System.out.println("Completed reading class file for class " + name);
return defineClass(name, data, 0, len);
}
} catch (IOException e) {
throw new ClassNotFoundException("Class: " + name + " was not found", e);
}
}
return clz;
}
public void close() {
try {
if (socket != null && socket.isClosed() == false) {
this.socket.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
This class will read the byte array and load it within the server side of the code. Note that I use a simple protocol to determine how many bytes are being sent over the wire and insure that I have read the correct amount of bytes.
Here is the client side code that will send the information over the wire. It is an extension of what you mentioned above.
package org.valhalla.client;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.Socket;
public class ClientConnection {
private Socket socket;
public ClientConnection(Socket socket) {
this.socket = socket;
}
public void process() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
String name = null;
while ((name = dis.readUTF()) != null && name.length() > 0) {
System.out.println("Looking up class: " + name);
InputStream resource = ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + ".class");
if (resource == null) {
System.out.println("Class not found: " + name);
dos.writeBoolean(false);
continue;
}
System.out.println("Found class: " + name);
try {
byte buf[] = new byte[1024];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int size = 0;
while ((size = resource.read(buf)) > 0) {
bos.write(buf, 0, size);
}
byte clz[] = bos.toByteArray();
dos.writeBoolean(true);
System.out.println("Sendding class size: " + clz.length);
dos.writeInt(clz.length);
System.out.println("Sending class bytes");
dos.write(clz);
System.out.println("Sent class bytes");
} catch (Throwable t) {
t.printStackTrace();
dos.writeBoolean(false);
}
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
if (socket != null && socket.isClosed() == false) {
try { socket.close(); } catch(Throwable t) {}
}
}
}
}
As you see it just sends some information to the server that lets it know how much data is expected to be transferred. The following classes can be used with the above classes to show how this works.
package org.valhalla.classloader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
public class RemoteClassLoaderServer {
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("syntax error: missing port");
System.exit(1);
}
int port = 0;
try {
port = Integer.parseInt(args[0]);
} catch(NumberFormatException nfe) {
System.out.println("Invalid port number: " + args[1]);
System.exit(2);
}
if (port < 0) {
System.out.println("Port cannot be negative: " + port);
}
ServerSocket server = null;
try {
server = new ServerSocket(port);
} catch (IOException e) {
System.out.println("Unable to create server socket for port: " + port);
System.exit(3);
}
Socket s = null;
try {
s = server.accept();
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
System.out.println("Waiting for class name");
String name = dis.readUTF();
System.out.println("Received class name: " + name);
RemoteClassLoader rcl = new RemoteClassLoader(s, RemoteClassLoaderServer.class.getClassLoader());
System.out.println("Finding class: " + name);
Class<?> clz = rcl.loadClass(name);
Method m = clz.getMethod("main", String[].class);
System.out.println("Executing main method");
m.invoke(null, new Object[] { new String[0] });
System.out.println("done");
new DataOutputStream(s.getOutputStream()).writeUTF("");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (s != null && s.isClosed() == false) {
try { s.close(); } catch(Throwable t) {}
}
}
}
}
Here are the client side classes
package org.valhalla.client;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class ClientMain {
public static void main(String[] args) {
int port = Integer.parseInt(args[0]);
try {
Socket socket = new Socket("localhost", port);
System.out.println("Opened socket at port: " + port);
String name = Main.class.getName();
new DataOutputStream(socket.getOutputStream()).writeUTF(name);
System.out.println("Sent Class name: " + name);
ClientConnection conn = new ClientConnection(socket);
conn.process();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
This class will be run within the server side.
package org.valhalla.client;
public class Main {
public static void main(String args[]) {
Client client = new Client();
client.execute();
}
}
with this class.
package org.valhalla.client;
public class Client {
public void execute() {
System.out.println("######### We are calling the Client class execute method #####");
}
}
Hope this helps.

Related

SocketChannel. invalid stream header: 00000000

I want to serialize 'Message' object, I can successfully transfer it as bytes array through socketChannel. After that, I change the object's properties (so that it may have larger size), and then there's a problem in sending object back to the client.
Once I try to obtain the object on the client side, I get an exception, it occurs when I deserealize Message obj in getResponse() method:
org.apache.commons.lang3.SerializationException: java.io.StreamCorruptedException: invalid stream header: 00000000
But, somehow, this applies only for the first client (After the exception is thrown, connection with the first client is over) and when I start a new client (not closing server) I can successfully transfer the object back and forth, furthermore, it works for any new clients.
This is my minimal debuggable version:
import org.apache.commons.lang3.SerializationUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class Client {
private SocketChannel server;
public void start() throws IOException {
try {
server = SocketChannel.open(new InetSocketAddress("localhost", 5454));
server.configureBlocking(false);
} catch (IOException e) {
System.err.println("Server isn't responding");
System.exit(0);
}
Scanner scRequest = new Scanner(System.in);
Scanner scState = new Scanner(System.in);
System.out.println("Enter request:");
String request = scRequest.nextLine();
while (!request.equals("exit")) {
try {
// In my actual project class Person is a way different (But it's still a POJO)
// I included it here to make sure I can get it back after sending to the server
System.out.println("Enter a number:");
Person person = new Person(scState.nextInt());
sendRequest(request, person);
System.out.println("\nEnter request:");
request = scRequest.nextLine();
} catch (Exception e) {
e.printStackTrace();
}
}
stop();
}
public void sendRequest(String sMessage, Person person) {
Message message = new Message(sMessage, person);
ByteBuffer requestBuffer = ByteBuffer.wrap(SerializationUtils.serialize(message));
try {
server.write(requestBuffer);
requestBuffer.clear();
getResponse();
} catch (Exception e) {
System.out.println(e.getMessage());
System.err.println("Connection lost");
System.exit(0);
}
}
public void getResponse() throws Exception {
ByteBuffer responseBuffer = ByteBuffer.allocate(1024 * 1024 * 64);
int read = server.read(responseBuffer);
responseBuffer.clear();
if(read == -1) {
throw new Exception();
}
byte[] bytes = new byte[responseBuffer.limit()];
responseBuffer.get(bytes);
Message message = SerializationUtils.deserialize(bytes);
System.out.println(message);
}
public void stop() throws IOException {
server.close();
}
public static void main(String[] args) throws IOException {
Client client = new Client();
client.start();
}
}
import org.apache.commons.lang3.SerializationUtils;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Server {
public void start() throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress("localhost", 5454));
serverSocket.configureBlocking(false);
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started");
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) {
register(selector, serverSocket);
}
if (key.isReadable()) {
try {
getRequest(key);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
iter.remove();
}
}
}
private void getRequest(SelectionKey key) throws Exception {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer requestBuffer = ByteBuffer.allocate(1024 * 1024);
int read = client.read(requestBuffer);
requestBuffer.clear();
if(read == -1) {
key.cancel();
throw new Exception("Client disconnected at: " +
((SocketChannel) key.channel()).socket().getRemoteSocketAddress());
}
byte[] bytes = new byte[requestBuffer.limit()];
requestBuffer.get(bytes);
Message message = SerializationUtils.deserialize(bytes);
sendResponse(client, message);
}
private void sendResponse(SocketChannel client, Message message) throws IOException {
message.setResult("Some result");
ByteBuffer responseBuffer = ByteBuffer.wrap(SerializationUtils.serialize(message));
while (responseBuffer.hasRemaining()) {
client.write(responseBuffer);
}
responseBuffer.clear();
}
private void register(Selector selector, ServerSocketChannel serverSocket) throws IOException {
SocketChannel client = serverSocket.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("New client at: " + client.socket().getRemoteSocketAddress());
}
public static void main(String[] args) throws Exception {
new Server().start();
}
}
I try to send this object as a bytes array:
import java.io.Serializable;
import java.util.Formatter;
public class Message implements Serializable {
private String command;
private Person person;
private String result;
public Message(String command, Person person) {
this.command = command;
this.person = person;
}
public String getCommand() {
return command;
}
public void setCommand(String executedCommand) {
this.command = executedCommand;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
#Override
public String toString() {
return new Formatter()
.format("Command: %s\nAttached object: %s\nResult: %s",
command, person, result)
.toString();
}
}
I include instance of this class inside Message obj:
public class Person implements Serializable {
private final int state;
public Person(int state) {
this.state = state;
}
#Override
public String toString() {
return "Person state: " + state;
}
}
I have no idea what is going wrong, hope for your help.
UPD: I used 'org.apache.commons:commons-lang3:3.5' dependency to serialize an object into bytes array
I have never used Java NIO channels before, so I am not an expert. But I found out several things:
General:
In order to debug your code, it is helpful to use e.printStackTrace() instead of just System.out.println(e.getMessage()).
Client:
SocketChannel server in the client should be configured as blocking, otherwise it might read 0 bytes because there is no server response yet, which causes your problem.
You should always call ByteBuffer.clear() before reading something, not afterwards.
After reading, the position in the byte buffer has to be reset to 0 via responseBuffer.position(0) before calling get(byte[]), otherwise it will read undefined bytes after the ones just read.
You should size your byte arrays according to the number of bytes read, not the byte buffer size. It might work the other way around, but it is inefficient.
Server:
You should always call ByteBuffer.clear() before reading something, not afterwards.
After reading, the position in the byte buffer has to be reset to 0 via responseBuffer.position(0) before calling get(byte[]), otherwise it will read undefined bytes after the ones just read.
When catching exceptions during getRequest(key) calls, you should close the corresponding channel, otherwise after a client disconnects the server will indefinitely try to read from it, spamming your console log with error messages. My modification handles that case and also prints a nice log message telling which client (remote socket address) was closed.
Caveat: There is nothing in your code dealing with the situation that a request or response written into the channel on the one side is bigger than the maximum ByteBuffer size on the other side. Similarly, in theory a (de)serialised byte[] could also end up being bigger than the byte buffer.
Here are my diffs:
Index: src/main/java/de/scrum_master/stackoverflow/q65890087/Client.java
===================================================================
--- a/src/main/java/de/scrum_master/stackoverflow/q65890087/Client.java (revision Staged)
+++ b/src/main/java/de/scrum_master/stackoverflow/q65890087/Client.java (date 1612321383172)
## -15,7 +15,7 ##
public void start() throws IOException {
try {
server = SocketChannel.open(new InetSocketAddress("localhost", 5454));
- server.configureBlocking(false);
+ server.configureBlocking(true);
}
catch (IOException e) {
System.err.println("Server isn't responding");
## -56,22 +56,24 ##
getResponse();
}
catch (Exception e) {
- System.out.println(e.getMessage());
+ e.printStackTrace();
+// System.out.println(e.getMessage());
System.err.println("Connection lost");
System.exit(0);
}
}
public void getResponse() throws Exception {
- ByteBuffer responseBuffer = ByteBuffer.allocate(1024 * 1024 * 64);
+ ByteBuffer responseBuffer = ByteBuffer.allocate(1024 * 1024);
+ responseBuffer.clear();
int read = server.read(responseBuffer);
- responseBuffer.clear();
if (read == -1) {
- throw new Exception();
+ throw new Exception("EOF, cannot read server response");
}
- byte[] bytes = new byte[responseBuffer.limit()];
+ byte[] bytes = new byte[read];
+ responseBuffer.position(0);
responseBuffer.get(bytes);
Message message = SerializationUtils.deserialize(bytes);
Index: src/main/java/de/scrum_master/stackoverflow/q65890087/Server.java
===================================================================
--- a/src/main/java/de/scrum_master/stackoverflow/q65890087/Server.java (revision Staged)
+++ b/src/main/java/de/scrum_master/stackoverflow/q65890087/Server.java (date 1612323386278)
## -35,7 +35,11 ##
getRequest(key);
}
catch (Exception e) {
- System.err.println(e.getMessage());
+ e.printStackTrace();
+// System.err.println(e.getMessage());
+ SocketChannel client = (SocketChannel) key.channel();
+ System.err.println("Closing client connection at: " + client.socket().getRemoteSocketAddress());
+ client.close();
}
}
iter.remove();
## -45,15 +49,16 ##
private void getRequest(SelectionKey key) throws Exception {
SocketChannel client = (SocketChannel) key.channel();
- ByteBuffer requestBuffer = ByteBuffer.allocate(1024 * 1024 * 64);
+ ByteBuffer requestBuffer = ByteBuffer.allocate(1024 * 1024);
+ requestBuffer.clear();
int read = client.read(requestBuffer);
- requestBuffer.clear();
if (read == -1) {
key.cancel();
throw new Exception("Client disconnected at: " +
((SocketChannel) key.channel()).socket().getRemoteSocketAddress());
}
- byte[] bytes = new byte[requestBuffer.limit()];
+ byte[] bytes = new byte[read];
+ requestBuffer.position(0);
requestBuffer.get(bytes);
Message message = SerializationUtils.deserialize(bytes);
sendResponse(client, message);
Just for completeness' sake, here are the full classes after I changed them:
import org.apache.commons.lang3.SerializationUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class Client {
private SocketChannel server;
public void start() throws IOException {
try {
server = SocketChannel.open(new InetSocketAddress("localhost", 5454));
server.configureBlocking(true);
}
catch (IOException e) {
System.err.println("Server isn't responding");
System.exit(0);
}
Scanner scRequest = new Scanner(System.in);
Scanner scState = new Scanner(System.in);
System.out.println("Enter request:");
String request = scRequest.nextLine();
while (!request.equals("exit")) {
try {
// In my actual project class Person is a way different (But it's still a POJO)
// I included it here to make sure I can get it back after sending to the server
System.out.println("Enter a number:");
Person person = new Person(scState.nextInt());
sendRequest(request, person);
System.out.println("\nEnter request:");
request = scRequest.nextLine();
}
catch (Exception e) {
e.printStackTrace();
}
}
stop();
}
public void sendRequest(String sMessage, Person person) {
Message message = new Message(sMessage, person);
ByteBuffer requestBuffer = ByteBuffer.wrap(SerializationUtils.serialize(message));
try {
server.write(requestBuffer);
requestBuffer.clear();
getResponse();
}
catch (Exception e) {
e.printStackTrace();
// System.out.println(e.getMessage());
System.err.println("Connection lost");
System.exit(0);
}
}
public void getResponse() throws Exception {
ByteBuffer responseBuffer = ByteBuffer.allocate(1024 * 1024);
responseBuffer.clear();
int read = server.read(responseBuffer);
if (read == -1) {
throw new Exception("EOF, cannot read server response");
}
byte[] bytes = new byte[read];
responseBuffer.position(0);
responseBuffer.get(bytes);
Message message = SerializationUtils.deserialize(bytes);
System.out.println(message);
}
public void stop() throws IOException {
server.close();
}
public static void main(String[] args) throws IOException {
Client client = new Client();
client.start();
}
}
import org.apache.commons.lang3.SerializationUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Server {
public void start() throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress("localhost", 5454));
serverSocket.configureBlocking(false);
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started");
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) {
register(selector, serverSocket);
}
if (key.isReadable()) {
try {
getRequest(key);
}
catch (Exception e) {
e.printStackTrace();
// System.err.println(e.getMessage());
SocketChannel client = (SocketChannel) key.channel();
System.err.println("Closing client connection at: " + client.socket().getRemoteSocketAddress());
client.close();
}
}
iter.remove();
}
}
}
private void getRequest(SelectionKey key) throws Exception {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer requestBuffer = ByteBuffer.allocate(1024 * 1024);
requestBuffer.clear();
int read = client.read(requestBuffer);
if (read == -1) {
key.cancel();
throw new Exception("Client disconnected at: " +
((SocketChannel) key.channel()).socket().getRemoteSocketAddress());
}
byte[] bytes = new byte[read];
requestBuffer.position(0);
requestBuffer.get(bytes);
Message message = SerializationUtils.deserialize(bytes);
sendResponse(client, message);
}
private void sendResponse(SocketChannel client, Message message) throws IOException {
message.setResult("Some result");
ByteBuffer responseBuffer = ByteBuffer.wrap(SerializationUtils.serialize(message));
while (responseBuffer.hasRemaining()) {
client.write(responseBuffer);
}
responseBuffer.clear();
}
private void register(Selector selector, ServerSocketChannel serverSocket) throws IOException {
SocketChannel client = serverSocket.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("New client at: " + client.socket().getRemoteSocketAddress());
}
public static void main(String[] args) throws Exception {
new Server().start();
}
}

Java file transfer over Sockets trim last bytes

I have been trying to create a Messenger with file transfer capabilities, but I keep having too many null characters at the end. any time I use the file length to strip them, for some reason more of the file gets stripped and it just becomes a total mess. I'm not using any Java 7 or higher elements as I want it compatible with Java 6, and Windows 98 (grandma's PC).
I also get a lot of random null characters added into the file, im not sure how to avoid this
This is my code:
Transmit
package com.androdome.lunacoffee.management;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import com.androdome.lunacoffee.ErrorScreen;
import com.androdome.lunacoffee.SendScreen;
public class FileTransmitter implements Runnable{
String adds;
FileInputStream message;
int filecut = 4096;
byte[] fileName;
long fileSize;
SendScreen send;
public FileTransmitter(String address, FileInputStream msg, byte[] fnme, SendScreen snd, long l) {
adds = address;
send = snd;
message = msg;
fileName = fnme;
fileSize = l;
}
public void run()
{
try {
InetAddress add = InetAddress.getByName(adds);
Socket sock = new Socket(add, 11001);
DataOutputStream da = new DataOutputStream(sock.getOutputStream());
PrintWriter output = new PrintWriter(da);
da.write(fileName);
da.writeLong(message.getChannel().size());
byte[] filebuffer = new byte[filecut];
int g = 0;
int back = 0;
while((g = message.read(filebuffer)) != -1)
{
if(g != filecut && g > 0)
{
back = g;
}
da.write(filebuffer);
filebuffer = new byte[filecut];
}
da.writeInt(back);
System.out.print(back);
output.flush();
output.close();
send.incrementSent();
} catch (UnknownHostException e) {
send.incrementError();
// TODO Auto-generated catch block
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
new ErrorScreen("Unable to send file", "Your file was not able to send because the host \"" + adds + "\" was not availible!", sw.toString());
pw.close();
try {
sw.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} catch (IOException e) {
send.incrementError();
// TODO Auto-generated catch block
new ErrorScreen("Unable to send file", "Your file was not able to send due to a bad output stream!", e.getMessage());
}
}
}
Recieve:
package com.androdome.lunacoffee.management;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import com.androdome.lunacoffee.ErrorScreen;
import com.androdome.lunacoffee.FileScreen;
import com.androdome.lunacoffee.Main;
public class FileReciever implements Runnable {
int bufferSize = 4096;
int headerSize = 32;
byte[] filebuffer = new byte[bufferSize];
byte[] fileheader = new byte[headerSize];
Main main;
File downloadfile = new File("tmp");
File transferFile = new File("dnl.ldf");
public FileReciever(Main mn)
{
main = mn;
}
static byte[] trim(byte[] bytes)
{
int i = bytes.length - 1;
while (i >= 0 && bytes[i] == 0)
{
--i;
}
return Arrays.copyOf(bytes, i + 1);
}
public void run() {
try {
ServerSocket recieveSocket = new ServerSocket(11001);
while (this != null) {
try{
downloadfile.createNewFile();
Socket connectionSocket = recieveSocket.accept();
DataInputStream reader = new DataInputStream(connectionSocket.getInputStream());
reader.read(fileheader);
long fileSize = reader.readLong();
System.out.println(bufferSize);
filebuffer = new byte[bufferSize];
String fileName = new String(fileheader);
fileheader = new byte[headerSize];
FileOutputStream fw = new FileOutputStream(downloadfile);
while(reader.read(filebuffer) != -1)
{
fw.write(filebuffer);
filebuffer = new byte[bufferSize];
}
//reader.readInt();
reader.close();
fw.close();
//RandomAccessFile file = new RandomAccessFile(downloadfile, "Rwd");
//file.setLength(fileSize); // Strip off the last _byte_, not the last character
//file.close();
connectionSocket.close();
FileScreen fs = new FileScreen(downloadfile, fileName, connectionSocket.getInetAddress().getHostName());
fs.setVisible(true);
fs.setLocationRelativeTo(null);
}
catch(Exception ex)
{}
}
} catch (IOException e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
new ErrorScreen("Unable to start the File Recieve Thread", "Luna Messenger may already be running, or another program is using port 11001. Please close any program running on port 11001.", sw.toString());
pw.close();
try {
sw.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
In your above code,i think it makes following mistake.
The first,you should add a number which indicate the filename's length.like this:
da.writeInt(fileName.length); //the added code
da.write(fileName);
On the FileReciever,the receive code is :
int fileNameLength = reader.readInt();
fileheader=new byte[fileNameLength];
read(reader,fileheader,0,fileNameLength);
the read method can read up to length bytes from input stream into a array of bytes until the stream is end.
public static int read(InputStream in, byte[] b, int off, int len) throws IOException {
if (len < 0) {
throw new IndexOutOfBoundsException("len is negative");
}
int total = 0;
while (total < len) {
int result = in.read(b, off + total, len - total);
if (result == -1) {
break;
}
total += result;
}
return total;
}
The second,it is not correctly that the FileTransmitter translate the file date to the FileReciever,and should not add a number at end.The appropriate approach is just writing file data to socket outputstream in FileTransmitter,and don't do any other things.Like this:
while((g = message.read(filebuffer)) != -1)
{
da.write(filebuffer,0,g);
}
On the orther hand,and how many bytes should be readed depend on the length of bytes that your readed from the socket's inputstream ago, when you read the bytes from socket oupustream into filebuffer.the receiver code:
int readLength;
int sumLength=0;
while((readLength=reader.read(filebuffer,0,(int)(fileSize-sumLength>filebuffer.length?filebuffer.length:fileSize-sumLength))) != -1){
sumLength+=readLength;
fw.write(filebuffer,0,readLength);
if(sumLength==fileSize){
break;
}
}

can't instantiate ObjectInputStream to read input from user

I can't add a ObjectInputStream to read input from the user, it always blocks at that point. This code works fine if I remove the ObjectInputStream in the Server that is supposed to read input from user and then send hardcoded String instead. What is happening behind the scenes? I'am aware that when a ObjectOutputStream is created it sends a header and when a ObjectInputStream is created it reads that header. Do I need to flush something in System before I try to instantiate oOISUser?
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public Server() {
ServerSocket oSS = null;
Socket oS = null;
ObjectOutputStream oOOS = null; // to write to socket
ObjectInputStream oOIS = null; // to read from socket
ObjectInputStream oOISUser = null; // to read input from user
try {
oSS = new ServerSocket(1025);
oS = oSS.accept();
oOOS = new ObjectOutputStream(oS.getOutputStream());
oOIS = new ObjectInputStream(oS.getInputStream());
oOISUser = new ObjectInputStream(System.in);`// doesn't get past this
String sToSend = (String) oOISUser.readObject();
System.out.println("server says: " + sToSend);
oOOS.writeObject(sToSend);
oOOS.flush();
System.out.println("server receives: " + (String) oOIS.readObject());
} catch (IOException e) {
System.out.println(e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println(e.getMessage());
} finally {
try {
if (oSS != null) oSS.close();
if (oS != null) oS.close();
if (oOOS != null) oOOS.close();
if (oOIS != null) oOIS.close();
if (oOISUser != null) oOISUser.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
public static void main(String[] args) {
Server s = new Server();
}
}
This is the code for the Client:
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class Client {
public Client() {
Socket oS = null;
ObjectOutputStream oOOS = null;
ObjectInputStream oOIS = null;
try {
oS = new Socket("127.0.0.1", 1025);
oOOS = new ObjectOutputStream(oS.getOutputStream());
oOIS = new ObjectInputStream(oS.getInputStream());
System.out.println("client receives: " + (String) oOIS.readObject());
String sToSend = "hello from client";
System.out.println("client says: " + sToSend);
oOOS.writeObject(sToSend);
oOOS.flush();
} catch (IOException e) {
System.out.println(e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println(e.getMessage());
} finally {
try {
if (oS != null) oS.close();
if (oOOS != null) oOOS.close();
if (oOIS != null) oOIS.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
public static void main(String[] args) {
Client c = new Client();
}
}
new ObjectInputStream(System.in)
You said it yourself in the question:
when a ObjectInputStream is created it reads that header
So you're effectively waiting for the user to enter an ObjectInputStream header in the console. That has a very very tiny chance to happen (unless a file is piped to System.in). It just makes very little sense to read serialized Java objects from System.in. The user can't possibly type valid serialized Java objets in the console. He/She can type text, though. So use a Reader or a Scanner.

Android - How to work with complex object via socket

I'm kind of new to Android and I have a task to open a TCP socket and listen to a specific port.
A client application is supposed to send me 2 images and a string that are related to each other so instead of sending each data alone we thought about putting all data in a json object and send this object.
My question is, how do I parse this json into saving 2 images and a string?
So this json is supposed to be like this:
data
{
FileName: "some string",
Image1: "Image encoded with encode base64",
Image2: "Image encoded with encode base64"
}
I'm using an AsyncTask so here is the code where I get the socket data:
public class DataRecord
{
String Image1;
String Image2;
String FileName;
}
protected DataRecord doInBackground(Socket... sockets) {
DataRecord dataRecord = null;
if (isExternalStorageWritable() && sockets.length > 0) {
Socket socket = sockets[0];
dataRecord = socket.getOutputStream(); // how to extract the data from the socket into this object ???
File file = new File(Environment.getExternalStorageDirectory(), dataRecord.FileName);
byte[] bytes = new byte[(int) file.length()];
BufferedInputStream inputStream;
try {
inputStream = new BufferedInputStream(new FileInputStream(file));
inputStream.read(bytes, 0, bytes.length);
OutputStream outputStream = dataRecord.Image1;
outputStream.write(bytes, 0, bytes.length);
outputStream.flush();
socket.close();
}
catch (Exception e) { }
finally {
try {
socket.close();
} catch (IOException e) { }
}
}
return dataRecord;
}
And I need to get it from the socket object and extract an object from it to save the 2 images to the SD card and extract the string to the UI.
I know this question probably have more than one answer, but still posting an answer is a good idea.
So, I found this link here A Simple Java TCP Server and TCP Client which helped me getting started with my solution.
I've also used Gson to parse my JSON string using this nice tutorial: Android JSON Parsing with Gson Tutorial.
Finally, my code looks like this:
ServerSockerThread.java - this is the java class for the listening server which waits for incoming files:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketThread extends Thread {
static final int SocketServerPORT = 6789;
ServerSocket serverSocket;
#Override
public void run() {
Socket socket = null;
try {
serverSocket = new ServerSocket(SocketServerPORT);
while (true) {
socket = serverSocket.accept();
new FileSaveThread().execute(socket);
}
}
catch (IOException e) { }
finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) { }
}
}
}
protected void onDestroy() {
if (serverSocket != null) {
try {
serverSocket.close();
}
catch (IOException e) { }
}
}
}
FileSaveThread.java - this is the java class that is being called by the above server class for each incoming file:
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Base64;
import com.google.gson.Gson;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.Socket;
public class FileSaveThread extends AsyncTask<Socket, Void, DataRecord> {
#Override
protected void onPostExecute(DataRecord dataRecord) {
super.onPostExecute(dataRecord);
}
#Override
protected DataRecord doInBackground(Socket... sockets) {
DataRecord dataRecord = null;
if (isExternalStorageWritable() && sockets.length > 0) {
Socket socket = sockets[0];
try {
Gson gson = new Gson();
Reader reader = new InputStreamReader(socket.getInputStream());
SocketObject socketObject = gson.fromJson(reader, SocketObject.class);
SaveFileToSDCard(socketObject.Image1, "Image1.png");
SaveFileToSDCard(socketObject.Image2, "Image2.png");
SaveFileToSDCard(socketObject.Image3, "Image3.png");
dataRecord = new DataRecord(socketObject.Name);
}
catch (Exception e) { }
finally {
try {
socket.close();
} catch (IOException e) { }
}
}
return dataRecord;
}
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
private void SaveFileToSDCard(String base64String, String fileName) throws IOException {
byte[] decodedString = Base64.decode(base64String.getBytes(), android.util.Base64.DEFAULT);
File file = new File(Environment.getExternalStorageDirectory(), fileName);
FileOutputStream fileOutputStream = new FileOutputStream(file, false);
fileOutputStream.write(decodedString);
fileOutputStream.close();
fileOutputStream.flush();
}
}

Sending and receiving DatagramPacket containing a double

What I want to do is 1) On the client side, convert a double (currently it's 1.75) to bytes[] and send it to the server using DatagramPacket; 2) On the server side, receive the request, get the bytes[] data, convert it to double and print it out.
UDPClient.java:
import java.net.*;
import java.io.*;
import java.nio.ByteBuffer;
public class UDPClient {
private static byte[] doubleToByteArray(double x) {
byte[] bytes = new byte[8];
ByteBuffer.wrap(bytes).putDouble(x);
return bytes;
}
public static void main(String args[]) {
DatagramSocket aSocket = null;
try {
aSocket = new DatagramSocket();
byte[] m = doubleToByteArray(1.75);
InetAddress aHost = InetAddress.getByName("localhost");
int serverPort = 6789;
DatagramPacket request
= new DatagramPacket(m, m.length, aHost, serverPort);
aSocket.send(request);
} catch (SocketException e) {
System.out.println("Socket: " + e.getMessage());
} catch (IOException e) {
System.out.println("IO: " + e.getMessage());
} finally {
if (aSocket != null) {
aSocket.close();
}
}
}
}
UDPServer.java:
import java.net.*;
import java.io.*;
import java.nio.ByteBuffer;
public class UDPServer {
private static double byteArrayToDouble(byte[] bytes) {
double d = 0.0;
ByteBuffer.wrap(bytes).putDouble(d);
return d;
}
public static void main(String args[]) {
DatagramSocket aSocket = null;
try {
aSocket = new DatagramSocket(6789);
// create socket at agreed port
byte[] buffer = new byte[1000];
while (true) {
DatagramPacket request = new DatagramPacket(buffer, buffer.length);
aSocket.receive(request);
System.out.println(new String(request.getData(), 0, request.getLength()));
// output: some messy characters
System.out.println(byteArrayToDouble(request.getData()));
// output: "0.0" (of course, unfortunately)
}
} catch (SocketException e) {
System.out.println("Socket: " + e.getMessage());
} catch (IOException e) {
System.out.println("IO: " + e.getMessage());
} finally {
if (aSocket != null) {
aSocket.close();
}
}
}
}
How should I modify my send/receive mechanisms so that the correct bytes are transmitted?
private static double byteArrayToDouble(byte[] bytes) {
double d = 0.0;
ByteBuffer.wrap(bytes).putDouble(d);
return d;
}
That's a strange way to convert a byte array to a double. It doesn't do anything to the double. It can't. No reference parameters in Java. It should be get. Essentially the whole method can be replaced with:
ByteBuffer.wrap(request.getData(), 0, request.getLength()).getDouble().
E&OE

Categories