Java ServerSocket question

KBTibbs

Senior member
May 3, 2001
226
0
0
Hello, I have some java code that just doesn't seem to be working properly, and I'm not sure why.

I'll post the segment, and then explain what's happening.


if (e.getSource() == startButton)//starting server
{

String temp1;

temp1 = JOptionPane.showInputDialog(
"What port do you want the server to listen to?");
sLog.append("Starting server on port: " + temp1 + newline
+ "Awaiting Connection." + newline);

try
{
port = Integer.parseInt(temp1);
}
catch (NumberFormatException NFExc)
{
sLog.append("Incorrect input: port must be a number" + newline);
startButton.setEnabled(true);
stopButton.setEnabled(false);
}


try
{
serverSkt = new ServerSocket(port);
}
catch (IOException err)
{
sLog.append("Error creating a socket on port " + port + newline);
} // end try
while (true)
{
try
{
s = serverSkt.accept();
...
out = s.getOutputStream();
in = s.getInputStream();

...
}
catch (IOException err)
{
...
}

This is within my action listener. sLog is a Jtext area on a JScrollPane, yadda yadda yadda. I get no errors or warnings at compile. When I run the app, and I click the button to start the server, it goes into the action listener (I've traced through the steps) the showInputDialog asks and properly stores the port #, but the "sLog.append" does not show up on the window, the port is properly converted to an int, and then the server stops at the "s = serverSkt.accept();" as it should (accept is a blocking command).

The problem is, I don't ever successfully connect. Now, if I comment out the while(true), the accept, and any use of s, everything works just fine (sans the network connection, of course).

Any ideas? I will be eternally grateful... or at least I'll be grateful until I get busy again.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
code for connecting


ip = JOptionPane.showInputDialog(
"What server do you want to connect to? (IP format xxx.xxx.xxx.xxx)");
try
{
InetAdd = InetAddress.getByName(ip);
try
{
port = Integer.parseInt(JOptionPane.showInputDialog(
"What port is the server listening to?"));
try
{
log.append("Attempting connection to server on " + ip + ":" + port + newline);
s = new Socket(InetAdd, port);
in = s.getInputStream();
out = s.getOutputStream();
disconnect.setEnabled(true);
openButton.setEnabled(true);
connect.setEnabled(false);
}
catch (IOException IOExp)
{
log.append("Error connecting to server on " + ip + ":" + port + newline);
}
}
catch (NumberFormatException t)
{
log.append("Port must be a number " + newline);
}
}
catch (UnknownHostException UHE)
{
log.append("Bad Ip address." + newline);
}

log.setCaretPosition(log.getDocument().getLength());
 

Kilrsat

Golden Member
Jul 16, 2001
1,072
0
0
There is a deadlock condition that can occur when the order of streams acquired from the sockets on both sides are not coordinated. I believe the standard procedure is that each socket should acquire the OutputStream prior to the InputStream, but it has been a few months since I've fiddle with this.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
interesting. I swapped the instance of getting the input first, but there doesn't seem to be any change.

Searching the internet, I found an old (and ultimately unresolved) thread with a similair situation. They seemed to think the problem could be that the blocking accept() is within the action listener. They thought that this halted any and all GUI activity, which would explain why the sLog.append doesn't appear, but doesn't (in my mind at least) explain why no connection could be made. I would think this would just delay any GUI actions until a connection started, then they would catch up...

thoughts?
 

Kilrsat

Golden Member
Jul 16, 2001
1,072
0
0
Its all around bad practice to block the event thread. The general setup for a ServerSocket involves putting the accept() call into its own thread. Really you should try to avoid doing any work that takes a "significant" amount of time in the event thread as it should be reserved for sending messages between components.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
I can see logic behind this argument.

I'm still pretty new to Java, and I've never used threads before...

I've got a good umber of new things to learn... This is going to be a busy weekend. ;)

I still want the server to be created when the button is clicked, so within the action listener I would create the thread that runs the accept() method?
 

kamper

Diamond Member
Mar 18, 2003
5,513
0
0
Yes, just be careful that you either have a way to kill that thread or a way to make it exit easily on it's own. When I'm doing socket programming (not that I do it often) I usually use telnet to test the server side, at least until I'm sure that it's working nicely. It's a stable and relatively bug free way to hit the server.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
Ok, I've stripped out the stuff and it's much better organized, and I think my thread setup is ok, but I'm still having trouble setting up the server.

The basic set up is now, a class for the GUI, a class for client mode, a class for Server mode, and a class for the threads created by the server.

The gui creates a new instance of server, and passes it the port number.

Server then executes this segment

ServerSocket serverSocket = null;
boolean listening = true;

try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
//return("Could not listen on port:" + port);
}

while (listening)
new ServerThread(serverSocket.accept()).start();

serverSocket.close();


This is the code within ServerThread


import java.net.*;
import java.io.*;
import java.io.File;

public class ServerThread extends Thread {
private Socket socket = null;
File file;

public ServerThread(Socket socket) {
super("ServerThread");
this.socket = socket;
}

public void run() {

try {
FileOutputStream out = new FileOutputStream("temp.dat");
ObjectOutputStream Oout= new ObjectOutputStream(out);
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
try
{
file = (File)in.readObject();
}
catch (Exception WER)
{}



out.close();
in.close();
Oout.close();
socket.close();

} catch (IOException e) {
e.printStackTrace();
}
}

}


Now, when this runs, and I click the start server button and enter the port, the screen freezes just like it did when the accept() was still in the action listener. Am I not doing this correctly?

Also, I'm not sure how to get file back from the thread...
 

kamper

Diamond Member
Mar 18, 2003
5,513
0
0
Your "while (listening)" loop looks a little suspect. The way it looks to me you're spawning off new threads in an infinite loop. Your event handler should not be looping at all. It should start the new thread and exit (the new thread will continue running on it's own). Also, I don't see a call to ServerSocket.accept() anywhere...
 

KBTibbs

Senior member
May 3, 2001
226
0
0
Oh sorry for the confusion... the event handler just creates the server, and runs the method (the first code segment). the .accept() is right after the while, inside the .start().

I got this loop setup from java's KnockKnock client/server example.
 

kamper

Diamond Member
Mar 18, 2003
5,513
0
0
Are you going from that "Supporting Multiple Clients" section? That's a different scenario altogether and would require the creation of even more threads. For your gui example, the thread that starts the event handler should not have loops and it should not call ServerSocket.accept(). The call to accept actually halts the execution of your thread until someone connects to the socket. The loop and the accept() should both happen from within the run() method of your ServerThread.

And btw, you don't need to subclass Thread. Just implementing Runnable is good enough.
 

Kilrsat

Golden Member
Jul 16, 2001
1,072
0
0
You're still blocking the event thread with what you have, because the call to serverSocket.accept() comes before the Thread.start() method is called.

The attached code is an example of ServerObject that should do what you want.

Inside the actionListener associated with the port number entry you would do something like this:

ServerObject so = new ServerObject(port);
Thread t = new Thread(so);
t.start();


Of course for "clean" shutdowns, you should preserve the reference to the thread (and the ServerObject) we create here. But this should at least give you a starting point and a quick idea of what direction you should be going.

If you need to be able to support multiple simultaneous clients, then you need 1 thread per client connection (since you're using standard io, different rules if you were using nio), but only after its connection has been accepted by the ServerSocket.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
I don't need multiple clients.

This is great Kilrsat, but if I may ask, what is "clients = new ArrayList();"?

Ok, another question...

After I start Thread t...
How do I call the "so.getFile();" function at the right time? How do I know if the object has finished transmitting before I call getfile()?

 

Kilrsat

Golden Member
Jul 16, 2001
1,072
0
0
Ignore the clients thing.

The other questions are going to come down to how everything is setup. If you just care about doing something only when the file transfer is finished, then you'd want to give the ServerObject a reference to the object that created it. Give that object something like a fileDone(File file) method, and have the ServerObject call originalObject.fileDone(file) when its ready. Basically you're pushing the file to the client as soon as its ready.

There are lots of other ways to do it, and the "right" one really comes down to your specific usage.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
Great.

Threads are working well (as far as can be seen).

Now the purpose of this is to transmit files (binary or text without discrimination -- hopefully). We are able to get the file from user selection with a JFileChooser.

Through reading, sending, and writing, we only end up with a file that is very small and not readable.

The only thing I can imagine is that the File object returned from the JFileChooser is just the file discripter or a pointer to the location on the disk and so casting it as an object and using readObject and writeObject just isn't cutting it...

Here is some of my code.
 

kamper

Diamond Member
Mar 18, 2003
5,513
0
0
I'm not quite sure that I understand what you are trying to do here, but I'm guessing that you're trying to send the contents of a file over the tcp socket? A File object is really nothing more than a file path and isn't necessarily even linked to a file on the file system. On the client side you would want to open an input stream on the file and write that to the socket and you don't want to use Object streams anywhere unless you really mean to write java objects to files (not extremely useful).
 

KBTibbs

Senior member
May 3, 2001
226
0
0
I am trying to create an application that will transmit a file from one computer to another.

I would like this to be able to handle more than just text files, but binary files as well (be they images or executibles or what have you). This project is due Tuesday, and if I cannot get the non-text transmission to work, I can drop it and just transfer text data.

I've been using Object streams because from object it's one small hop skip and a jump to SealedObjects which encrypts the Objects in transmission.
 

kamper

Diamond Member
Mar 18, 2003
5,513
0
0
I think then, that you will have to do some work in order to get the contents of the file into the object that you are sending across the wire. Slurping it into a String would be fine for text files but not for binary files. For that you might have to use a byte[] unless there's a more elegant solution that I'm not aware of (not highly unlikely). Whatever works for a binary file should also work for text files, so you don't have to write two implementations.

Anyways, after you get that object out of the socket, instead of writing it to file you'd want to gain a reference back to it, grab it's contents (String or byte[] or something) and write those to the file on the server.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
Yeah, that's exactly what I was thinking.

since byte[] b will be of a fixed size (right?), I'll have to create a loop to grab and transmit sections with ints keeping track of the offset and how far you are till the EOF.

FileOutputStream can be made to append, but...
1) How do I signal that the EOF has been sent?
and
2) What if the file size isn't a perfect % of the size of byte[] b... will I get junk at the end of my file? Can I

long length = file.length();
//break length up into equal pieces
//but what if the file size is prime?
//or has factors that are too large or too small, nothing nice in the middle
byte[] b = new byte[seg_length];?
 

kamper

Diamond Member
Mar 18, 2003
5,513
0
0
Allocating memory in chunks like that when you don't have any firm boundary on the array size is dangerous. Sometime somebody is going to try to send a movie and your program will throw an OutOfMemoryError or something to that effect. A String is a nice solution for large text files because internally String actually keeps a number of arrays so the chunks are broken up in memory (I believe). There might be something similar for binary data (although I haven't seen it) or you could roll your own. But regardless, you will still need to be able to hold the entire file in memory at one time which is not ideal.

If you go with a completely stream based approach the file is being written on the server side while the file is still being read on the client side so for a large file only a fraction of it is in memory at any given time. That's why I'd recommend sticking with streams and encrytping the stream at a later time if you want (has the added benefit that you don't have to worry about co-ordinating a Cipher between the two sides).

Edit: whoops, just realized that you were already thinking about transmitting the file in chunks. That's a hassle because then you have to worry about multiple objects coming down the pipe and deciding when to look for more. Determining an offset for the end of the file would be trivial because you could just include an integer that specifies how many bytes in the array are relevant (in whichever object contains the byte[]). At any rate, you'd also have to transmit a boolean with each chunk to specify if this one is the last one or not.
 

KBTibbs

Senior member
May 3, 2001
226
0
0
yeah, I've found that file sizes are a problem. I cut out the files in chunks bit, it wasn't working out so well...

Bit of an update, I could transmit files so long as they were under 64Kb. Never found out why.

I added in the encryption CipherStream stuff, got the server and client to generate identical secret keys (that was a tough one!!!),

Now I can encrypt, transmit, decrypt, and save files...

so long as they are 504 bytes long. :::blush:::

Not sure why, but the project is due in two hours so I don't have time to sus it out.

I want to thank both of you. Kamper. Kilrsat... I would not have made it this far without you. I'm going to make note of you in my term paper.