2

I'm trying to write a server in Java. I know very little Java. I've found an example using Selector.

It looks good, but it behaves strangely. When I do my_socket_output_stream.writeBytes("hello world") in client code, the server reads this message one byte at a time. Shouldn't I be notified only when the complete message is sent? Now I'd have to check my buffer after getting every byte to know if I can already work with it. Seems terribly inefficient.

I wonder if that's due to Selector or is that just how sockets work (it's been a long time since I used them). Could I make them wait for the full message somehow? Also, can I associate some objects with a channel? Right now all sockets use the same buffer. I'm sure you see how that is a problem..

The reason I want to use a Selector is that my server is only going to do io with a HashTable. Multiple threads would just be constantly waiting. And I only have one core anyway. Though maybe a combination of ThreadPoolExecutor and ConcurrentHashMap would be a good choice? It would surely enable me to have a buffer per socket..

I'd appreciate suggestions.

Karolis Juodelė
  • 3,708
  • 1
  • 19
  • 32
  • Given that you know "very little java", it might be a lot simpler _not_ use NIO classes when writing your server. Was there a reason why you did it that way as opposed to just having a thread handling each socket? – Gray Nov 25 '11 at 14:16
  • @Gray: So long as he's an experienced programmer generally, I don't see the barrier to writing a server in Java. – Boann Nov 25 '11 at 14:19
  • Oh I agree @Boann. I'm just saying that he is jumping into the deep end of Java with NIO. – Gray Nov 25 '11 at 14:21
  • If you don't have any restrictions, you could use ready-made network application frameworks to ease development. Take a look at apache mina project http://mina.apache.org/ . I've used that in one project, it's very easy to start with – WeMakeSoftware Nov 25 '11 at 14:24
  • Maybe your write method flushes after every single byte in the passed array. Did you try creating a channel from the outputstream and write to that channel via a bytebuffer? as in http://stackoverflow.com/questions/579600/how-to-use-a-bytebuffer-with-an-outputstream? – fasseg Nov 25 '11 at 14:43
  • TCP sends a stream of bytes, not messages. If you want to know where a message starts/ends you have to send this information yourself. If you implement HTTP 1.0 for example, the end of the message is when the connection closes. – Peter Lawrey Nov 25 '11 at 15:35
  • 1
    Why do you want to write a server? That is quite a complex task to get it right! I recommend to use the Java EE framework and to use one of the existing application servers (such as GlassFish). – Puce Nov 25 '11 at 14:15
  • The first part yes, but the second part does: I recommend solving the issue by taking a completly different route. – Puce Nov 25 '11 at 14:25
  • I'm mostly writing this for experience so I'd rather do it the hard way. I'll definitely take a look at GlassFish though. – Karolis Juodelė Nov 25 '11 at 14:38
  • You can do this, of course, if you like, but from my experience one rarely uses this kind of skill in regular application development since there are higher level APIs (such as Java EE) which abstract all these complexities. – Puce Nov 25 '11 at 14:43
  • Unless you want to specialize in writing servers/ low-level network libraries/ frameworks, of course. – Puce Nov 25 '11 at 14:47
  • Don't forget, writing a simple server like the OP is describing is also one of the best ways of learning. – Kelly S. French Jan 10 '12 at 20:24

3 Answers3

1

I faced the same problem a long time ago. I solved by first sending the number of bytes of the message, then sending the message itself byte by byte. Then I expanded it to line by line.

At the sender's side:

// code at sender side
StreamConnectionNotifier service =  (StreamConnectionNotifier) Connector.open( url );
//System.out.println("opened");
StreamConnection con = (StreamConnection) service.acceptAndOpen();
OutputStream outputStream = con.openOutputStream();


// file to send
Scanner in = new Scanner(inFile);

//just count lines
String s=null;
int countLines=0;
while(in.hasNext()) {
    s=in.nextLine();
    countLines++;
}

//send num of lines
outputStream.write(Integer.toHexString(countLines).getBytes());
try{Thread.sleep(100);} catch(InterruptedException e){}

//send lines
in = new Scanner(inFile);
for(int i=0; i<countLines; i++) {
    s=in.nextLine()+"\n";
    outputStream.write(s.getBytes());
    Thread.sleep(100);
}

At the receiver's side:

// code at receiver side
byte buffer[] = new byte[80];
int bytes_read = inputStream.read( buffer );
String received = new String(buffer, 0, bytes_read);
try{Thread.sleep(100);} catch(InterruptedException e){}
int receiveLines = Integer.parseInt(received);

PrintWriter out = new PrintWriter(new FileOutputStream("received.txt"));

for(int i=0; i<receiveLines; i++) {
  bytes_read = inputStream.read( buffer );
  received = new String(buffer, 0, bytes_read);
  out.println(received);    
  Thread.sleep(100);
} 

I hope this helps :)

omarzd
  • 66
  • 6
1

Unless you have gained some skill in understanding of the issues of multi-threading and synchronization, avoid NIO. It is good stuff, but you are (currently) not properly equipped to debug it, much less fully appreciate and understand its synchronization needs.

Write a Runnable class that wraps a ServerSocket in a while loop, allowing the loop to block on the accept method. Then grab that return socket and construct a "client handler" thread which will handle whatever data came in the NIC.

This resource will give you some pointers on writing this older, slightly slower, and much more understandable server listening loop. I linked to a "middle" page in the article as that's the code listing, but you might want to read the entire article.

This uses the older "one Thread to handle the request" model of network processing. It's not terribly bad, but it can encounter scalability issues.

The alternative is to take the deep dive and do it with non-blocking NIO. It's not terribly hard, but it does require you to completely structure your server code in a manner that's not straightforward. Effectively you get "pools" of worker Threads than can perform various tasks, and then you synchronize on passing the data from worker to worker.

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
0

Shouldn't I be notified only when the complete message is sent?

No. Without specifying how messages should be separated from each other, the API can only give you one byte at a time (or all available bytes). The easiest way to separate strings would be to use a java.io.PrintStream on the side that is sending the message and a java.io.BufferedReader on the side that is receiving, like so:

// code that sends strings
OutputStream out = ...; // get the output stream from the socket
PrintStream sender = new PrintStream(out);
sender.println("Hello, world.");

// code that receives strings
InputStream in = ...; // get the input stream from the socket
BufferedReader receiver = new BufferedReader(new InputStreamReader(in));
String message = receiver.readLine(); // reads "Hello, world."
Flaise
  • 571
  • 3
  • 11