Java Forum / General / February 2006
Send files over the network using encapsulated serialized class
Chris - 23 Feb 2006 15:08 GMT Hi,
I'm trying to extend a client/server program so that I can send/receive files. It uses serializable Message objects in order to send messages. The Message class has a private byte array field which holds the file being sent. Before sending and receiving a message, the Message object either gets converted to a byte array or a byte array is converted back to a Message object. The code for that class:-
import java.io.*;
public class ConvertData{
//Convert a message object into a byte array public static byte[] messageToBytes (Object object) throws IOException{ java.io.ByteArrayOutputStream bs = new java.io.ByteArrayOutputStream(); java.io.ObjectOutputStream out = new java.io.ObjectOutputStream (bs); out.writeObject(object); out.flush(); out.close (); byte [] bytes = bs.toByteArray(); System.out.println("Bytes sending = " + bytes.length); return bytes; }
//Convert a byte array into a message object public static Object bytesToMessage (byte bytes[]) throws IOException, ClassNotFoundException{
try{ System.out.println("Bytes received = " + bytes.length); Object object; java.io.ObjectInputStream in; java.io.ByteArrayInputStream bs; bs = new java.io.ByteArrayInputStream (bytes); in = new java.io.ObjectInputStream(bs); object = in.readObject(); in.close (); bs.close (); return object; } catch(StreamCorruptedException sce){ System.out.println("Stream corrupted Exception "); sce.printStackTrace(); Object o = new Object(); return o; } catch(java.lang.ClassCastException cce){ System.out.println("Class Cast Exception "); cce.printStackTrace(); Object o = new Object(); return o; } } }
I run my own protocol whereby I create the Message object with the file and obtain the file size. I then send a warning to the server (ClientThread helper class) to accept a larger file (default is 2048 bytes). The server gets ready to accept the file and sends back an acknowledgement. The client then sends the file after receiving the acknowledgement. I've tried various size byte arrays in the Message object and anything over 1225 bytes I get the following exception thrown on the server side:-
java.io.StreamCorruptedException at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1326) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1912) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1836) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1713) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:339) at ConvertData.bytesToMessage(ConvertData.java:39) at ClientThread.run(Server.java:459) at java.lang.Thread.run(Thread.java:595) java.lang.ClassCastException: java.lang.Object
Does anyone have an idea as to what may be the cause?
Kind regards,
Chris
robert - 23 Feb 2006 17:10 GMT > Hi, > [quoted text clipped - 4 lines] > either gets converted to a byte array or a byte array is converted back > to a Message object. The code for that class:- <snip>
Why are you writing your own protocol? Using a web service you can use wsdl type base64Binary which accepts a byte array. Or at least use NIO if you choose to use sockets. Take a look at ByteBuffer.wrap(byte[] array) . The example below passes in a String - but could just as easily be byte [] since in the end you work with ByteBuffer.
private static final Charset ascii = Charset.forName("US-ASCII"); /** Write data to socket server
@param sChannel Pre-configured, connected Socket @param send The String to send to socket server @throws IOException on SocketChannel problems */ public static void writeToChannel (SocketChannel sChannel, final String send) throws IOException { CharBuffer cbuf = CharBuffer.wrap(send); ByteBuffer buf = ascii.encode(cbuf); while(buf.hasRemaining()) { sChannel.write(buf); } }
HTH, Robert http://www.braziloutsource.com/
Chris - 23 Feb 2006 19:01 GMT Hi Robert,
I'm going to have to go all newbie on you now, sorry! Basically the program is for a Chat tool for a team project. By sending the Message object back and forth between clients and server we have cracked the chat element, sending user lists, log on/off etc. Ideally we want to extend the Message class so it encompasses a byte array containing a file so we don't have to back to square one and start again. The protocol I mentioned is a protocol in the very loose sense of the word, just an indication to the server and receiving client that the size of the file is large therefore up the receiving capacity. As I've said, byte arrays under 1225 bytes long are ok but that is naff. Just wondering why it goes to rats at anything over that.
Regards,
Chris
Chris Uppal - 24 Feb 2006 11:23 GMT > I'm going to have to go all newbie on you now, sorry! In that case I trust you won't be offended if I suggest you go back to your sending and recieving code and look for a couple of common errors.
One is using the InputStream.available() method at all. There are /very/ few valid reasons to use it, and it frequently misleads people.
The other is using InputStream.read(byte[]) (or Reader.read(char[])) without checking the return value.
-- chris
Chris - 24 Feb 2006 13:12 GMT Hi Chris,
InputStream.available()? I'm assuming that you are on about the second method in the conversion class and perhaps on about in.readObject(). Could you suggest a rehash of the code please. Sorry to be a pain.
Chris
Chris Uppal - 24 Feb 2006 13:42 GMT > InputStream.available()? I'm assuming that you are on about the second > method in the conversion class and perhaps on about in.readObject(). No, I'm talking about the code you /didn't/ show -- the code that pulled the byte array off the network.
-- chris
Chris - 24 Feb 2006 15:21 GMT OK Chris. I get you now. Right, when the user connects to the server there are a few authentication checks that take place and on success a ClientThread is run which listens to subsequent messages from the client. The code for it is below, if that is any help.
class ClientThread implements Runnable, ApplicationConstants{
private DataInputStream dis; private Socket socket; private boolean isDone = false; private Thread thread; private User user; private int uploadSize = MAXIMUM_MESSAGE_SIZE;
public ClientThread(Socket socket, User user){
System.out.println("This is " + user.getUserName() + "'s Thread! and upload size is " + uploadSize);
try{ //set the socket this.socket = socket; //set the user this.user = user; //create the input stream to listen for incoming messages dis = new DataInputStream(socket.getInputStream()); //create the thread thread = new Thread(this,"ClientThread"); //start the thread thread.start(); } catch(Exception e){ System.out.println("service constructor"+e); } }
//thread is running and continuously listening for a message from its client public void run(){ byte [] data;
//read in the data while(!isDone){ try{ //set max size of message data = new byte[uploadSize]; System.out.println("Upload size is " + uploadSize); //read in the byte array dis.read(data); //convert to a message Message message = (Message)ConvertData.bytesToMessage(data);
//System.out.println("Message header of " + message.getMessageHeader() + " sent by " + message.getUser().getUserName());
//Checks if a file is going to be sent. If so, increase the //size of the byte array to accomodate the file size and the //message objects. if(message.getMessageHeader() == FILE_WARNING){ System.out.println("I received a FILE_WARNING"); //increase size of upload uploadSize = ((int)message.getFileSize()); //acknowledge to sender that server is ready for the file message.setMessageHeader(FILE_ACKNOWLEDGE); //output the message back to the sender of the file Server.outputToSingleClient(message); } //file received ok so set array back to default level and pass //the message to the Server to deal else if(message.getMessageHeader() == FILE_SENDING){ System.out.println("I received a FILE_SENDING"); //set size of array back to default uploadSize = MAXIMUM_MESSAGE_SIZE; //now process the message Server.processClientMessage(message); } //pass to the server for processing else{ Server.processClientMessage(message); } } //Error in the thread catch(Exception e){ System.out.println("In the exception block of the thread " + e);
uploadSize = MAXIMUM_MESSAGE_SIZE; //set isDone to true isDone = true; //remove the user from the list Server.removeFromUserList(user); //send a CLIENT_LOGOUT message to all other users Message message = new Message(CLIENT_LOGOUT); //set the user as this user message.setUser(user); //get Server to xend this message to all clients left on the list Server.outputToAllClients(message); //try to close the socket try{ socket.close(); } catch(Exception se){ System.out.println("Error closing the socket " + se); } } } }
Just on the client side I have serialized and deserialized the object without sending it and it remains intact. For clarity as well the method for sending data to the server is:-
public void sendMessageToServer(Message message) throws java.io.IOException{
//send details of the user each time message.setUser(clientUser); byte [] data; data = ConvertData.messageToBytes(message); //create a byte array //write the data to the socket dos.write(data,0,data.length); //flush the data out of the socket dos.flush(); }
where dos is DataOutputStream.
Cheers,
Chris
Chris Uppal - 25 Feb 2006 13:15 GMT > The code for it is below, if that is any help. Sigh...
Actually I suggested that /you/ review your code for the common errors I mentioned; I didn't offer to find 'em for you !
;-)
But still, I'll give you a hint. You are indeed committing the second of the them.
-- chris
Chris - 25 Feb 2006 23:00 GMT int i; while(i = dis.read(data) && i != uploadSize){ dis.read(data); } or something like that.......?
Sorry, away for the weekend so no chance of trying. Just shows you though how addictive this Java malarky can be ;-)
Chris Uppal - 26 Feb 2006 10:50 GMT > int i; > while(i = dis.read(data) && i != uploadSize){ > dis.read(data); > } No. Think about what that would do if (or rather, when) you got a partial read from the network. Say that you want to read 1000 bytes, and read() actually supplies 120 in the first chunk.
-- chris
Chris - 27 Feb 2006 07:47 GMT while(dis.read(data) != -1) perhaps?
Chris Uppal - 27 Feb 2006 09:01 GMT > while(dis.read(data) != -1) perhaps? No. read() is /not/ guaranteed (or even likely in this case) to read as many bytes as you asked for. (I've just checked back with the documentation and I admit this is not made very clear there). It will read as many bytes (>= 1) as it happens to feel like, and then return to your application to tell you how many you've actually got. If you want to read 1000 bytes then you have to loop until all 1000 bytes have arrived.
-- chris
Chris - 27 Feb 2006 09:32 GMT *Clutches at straws*
while(dis.read(data)< uploadSize){ dis.read(data); }
LOL
Chris Uppal - 27 Feb 2006 10:48 GMT > *Clutches at straws* > > while(dis.read(data)< uploadSize){ > dis.read(data); > } Now you are just thrashing around randomly. Take a deep breath, put down those straws (they'll only get into the keyboard), and /think/...
And for something to think about, try the three-argument form of read() (URL will wrap):
http://java.sun.com/j2se/1.5.0/docs/api/java/io/InputStream.html#read(byte[], int, int)
(BTW, You may think I'm refusing to explain in detail because it would be better for your education to work through the details. Not a bit of it. I'm doing it out of sheer sadistic pleasure in tantalising someone who doesn't follow the Usenet posting conventions of quoting appropriately in replies.)
-- chris
Chris - 27 Feb 2006 20:40 GMT Hi sadist, lol! A newbie to Usenet as well I'm afraid.
Right then, part success.
I now have an if else loop here. Basically for any message without a file the previous code I had written done the trick. I've now added a boolean value to test if a file is being received. If that is true I use dis.readFully(data,0,uploadSize) and that works great and the whole message is received. On receipt of this message I then reverse my protocol and inform the receiver of the file being sent. The receiver sends an acknowledgement but the server never receives it. Is there any chance that the changes in upload size affect each thread?
Another point is that it would be nice to be able to stick to just dis.readFully etc. To be honest though, I'm not quite sure how it will work. All messages are of varying length and a warning message, informing the server of a particular size message can also change in size by a byte here or there. I was thinking of padding out all sent messages under 2048 bytes to 2048 bytes so that readFully would work but would imagine that it would be a real waste of bandwidth. Do you know of any particular way around it?
Thanks for all your time and patience,
Chris
Chris - 27 Feb 2006 21:47 GMT Hi Chris,
Further to the last post I now have the following acting in the thread, and the whole file transfer thing works great. However if I try to perform an ordinary chat session, the server hangs. The real thing that is bugging me is that when I send a file, all control messages get through (i.e. FILE_WARNING, FILE_ACKNOWLEDGE), so why not CHAT messages? The code I have is:-
class ClientThread implements Runnable, ApplicationConstants{
private DataInputStream dis; private Socket socket; private boolean isDone = false; private boolean uploadingFile = false; private Thread thread; private User user; private int uploadSize = MAXIMUM_MESSAGE_SIZE;
public ClientThread(Socket socket, User user){
System.out.println("This is " + user.getUserName() + "'s Thread! and upload size is " + uploadSize);
try{ //set the socket this.socket = socket; //set the user this.user = user; //create the input stream to listen for incoming messages dis = new DataInputStream(socket.getInputStream()); //create the thread thread = new Thread(this,"ClientThread"); //start the thread thread.start(); } catch(Exception e){ System.out.println("service constructor"+e); } }
//thread is running and continuously listening for a message from its client public void run(){ byte [] data;
//read in the data while(!isDone){ try{ //if an ordinary message is being sent do this if(!uploadingFile){ System.out.println("Listening for normal messages in " + user.getUserName() + "'s Thread"); //set max size of message data = new byte[uploadSize]; System.out.println("Upload size is " + uploadSize); //read in the byte array dis.read(data,0,uploadSize); } //if a file is being sent then do this else{ System.out.println("Listening for file messages in " + user.getUserName() + "'s Thread"); data = new byte[uploadSize]; dis.readFully(data,0,uploadSize); }
//Convert into a message object Message message = (Message)ConvertData.bytesToMessage(data);
//Checks if a file is going to be sent. If so, increase the //size of the byte array to accomodate the file size and the //message objects. if(message.getMessageHeader() == FILE_WARNING){ System.out.println("I received a FILE_WARNING"); //increase size of upload uploadSize = ((int)message.getFileSize()); //set uploadingFile to true uploadingFile = true; //acknowledge to sender that server is ready for the file message.setMessageHeader(FILE_ACKNOWLEDGE); //output the message back to the sender of the file Server.outputToSingleClient(message); } //file received ok so set array back to default level and pass //the message to the Server to deal else if(message.getMessageHeader() == FILE_SENDING){ System.out.println("I received a FILE_SENDING"); //set size of array back to default uploadSize = MAXIMUM_MESSAGE_SIZE; //set uploadingFile back to false uploadingFile = false; //now process the message Server.processClientMessage(message); } //pass to the server for processing else{ Server.processClientMessage(message); } } //Error in the thread catch(Exception e){ System.out.println("In the exception block of the thread " + e);
uploadSize = MAXIMUM_MESSAGE_SIZE; //set isDone to true isDone = true; //remove the user from the list Server.removeFromUserList(user); //send a CLIENT_LOGOUT message to all other users Message message = new Message(CLIENT_LOGOUT); //set the user as this user message.setUser(user); //get Server to xend this message to all clients left on the list Server.outputToAllClients(message); //try to close the socket try{ socket.close(); } catch(Exception se){ System.out.println("Error closing the socket " + se); } } } } }
Any light that can be shed on this will really be appreciated.
Kind regards,
Chris
Chris - 28 Feb 2006 09:11 GMT Sorry I'm just being a numpty. My fault that the CHAT message wasn't being sent. It now does everything I require. Do you think that it could be made more elegant at all?
Chris
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|