Hi,
I'm building a battleship game that has multiple sessions over an apache server.
It's a server/client containing servlets & jsp's.
Let's say i have a jsp page that shows all the boards, for each session i want this page to be refreshed as soon another player makes a move.
Is there a way to do this with ajax?
i tried this function and it doesn't seem to work when gamestatus attribute is changed.

$(document).ready(function(){window.setInterval(checkPlayerStatus,500);})

function checkPlayerStatus(){
$.ajax ({
data:"ajaxrequest=gamestatus",
url:"/Battleship/playGame.jsp",
timeout: 2000,
error:function(){},
success:function(r){
if(r=="moveMade"){
window.location=window.location;
}
}
});
}

this code seemed to work with a servlet containing an html response.
Could it be there's a problem when using it with jsp?

What you ideally need to do is something called "long-poling" (Google it for a definition), but Apache/JSP (in fact Apache/anything) are not best suited to a long-polling solution.

You have two choices:

  • Rhythmic polling (eg. ajax in a function invoked with setInteval).
  • Ditch Apache/JSP completely in favour of a server-side architecture that supports long-polling, notably Node. (Client-side you still do ajax but the server doesn't necessarily respond immediately)

Node is a strange beast that spans what you would normally consider to be HTTP server (Apache/IIS) plus the server-side application level (JSP/ASP/PHP). Node's scripting language is javascript.

That's about the sum total of my knowledge of long-polling.

Airshow

I've done this with both javascript and jQuery on the client side and JSP on the server side for my own Board game Webapp. Works fine so far :)
Shameless plug: http://apps.facebook.com/sovereign - Play a 3 player game against yourself to see it in action.

I'll try to explain how I do it - if anyone has suggestions for improvements I'd be happy to make use of them:

On the Client side I make an AJAX connection to a particular JSP page. It's pretty similar to what you've already described so I won't go into details there.

Then, on the Server side, I have the JSP page (dataUpdate.jsp) and a Controller (DataUpdateController.java) which the page initializes. The Controller does verification stuff to make sure that whoever called the page is who he says he is, and then the page calls the controllers waitForUpdate() method.

    <!-- dataUpdate.jsp by JohannSig 11.05.2012 -->
    <%        
        DataUpdateController controller = new DataUpdateController(request, session);

        controller.waitForUpdate()  // <-- This method will hang until ready.

        %><jsp:include page="index.jsp" /><%   // <-- New stuff to be returned
    %>

Within the waitForUpdate() method, I make use of a nifty little utility called CountDownLatch that causes the method to hang, like so:

public class DataUpdateController {

    private CountDownLatch latch = null;   // This guy is aweseome!
    private int userId = -1;               // Something to identify this User with

    public DataUpdateController(HttpServletRequest request, HttpSession session) {
        // Initialize, verify, yackety-yack        
    }


    public boolean waitForUpdate() {        

        // Set the Latch up as a switch (Counting down from 1 --> 0):
        latch = new CountDownLatch(1);

        // Make note of this latch so that the Application can toggle it later:
        Application.addToLatchCollection(userId, latch);

        // Wait until Application toggles the Latch, or 30 minuets have passed:
        latch.await(30, TimeUnit.MINUTES);        
    }
}

So when the Application (a singleton class containing the WebApp's servletContext object - the "heart" of the servlet) decides that it's got an update ready for this User, it does something like the following:

public class Application {

    private Application instance;
    private Map<Integer, List<CountDownLatch>> latchCollectionByUserId;

    public static addToLatchCollection(userId, latch) {

        // Add this to the collection of Latches we already have for this User Id:
        latchCollectionByUserId.get(userId).add(latch));
    }

    public static updateUser(int userId) {

        // Toggle all Latches belonging to this User:
        for (CountDownLatch latch: latchCollectionByUserId.get(userId))
            latch.countDown();
    }
}

Of course my implementation involves a lot more details and nuances, but that's the general approach I've taken, and it works :) Again, experts may balk at the way I do it and I'll be more than happy to listen if anyone is willing to politely improve it, but so far I have yet to find a metter method.

-JS

That looks brilliant JS. As far as I can tell, you have found a way to achieve long polling - ie. waiting until something notable happens before sending an HTTP response.

Do you need to make any special settings in Apache to prevent it generating a 408 timeot error response?

Member Avatar for stbuchok

I believe you could also look at using webSockets. However, have fun trying to find good working examples.

Thanks, I'm glad you like it :) The CountDownLatch object is the key to the solution, I was very happy the day I discovered it within the java.util package.

Do you need to make any special settings in Apache to prevent it generating a 408 timeot error response?

I have it timing out on Tomcat after 40 minutes, at which point the Client receives a timeout notification. That's when I have the session set to run out on the Tomcat anyway and I haven't experimented with higher values (don't need longer timeouts than that for my purposes, aiming to suit Live players - not idlers). I made no special changes to Apache, neither Live nor on my own Dev machine, to achieve that.

-JS

Aha, so the latch is set to timeout after 30 mins and Tomcat has a 10 min margin above that. That sounds about right.

Presumably the client makes a new request as soon as it has handled each long-polled response but do you handle the situation where the client might need to make another request before the server has responded - ie. is there a mechanism for cancelling an outstanding long-polled request before making another one?

No, I didn't see the need to handle a situation like that (multiple long-pollings) in my implementation, though perhaps I'm overlooking something. Can you come up with a hypothetical situation that requires taking care of it?

Johann,

  • User makes an interaction (eg. a game move) giving rise to a long-poll request
  • While the long-poll is still unresolved (potentially any time in the next 30 minutes), user makes another, similar interaction.

In this case, I'm guessing that you would want, at the server, to cancel the first request such that a maximum of only one unresolved long-poll request exists at any moment in time. This seems reasonable given that a long-poll request is (at least normally) a generalised "let me know about anything noteworthy" device - hence two simultaneous unresolved long-polls should never be necessary and may even "double-report" to the client, spoiling the application.

Ideally, such cancellation should be initiated at the server on receipt of each new long-poll request, such that the client (javascript) needs simply to make the new request and not to expressly command the cancellation.

You could of course achieve (nearly) the same end by allowing a previous unresolved request to endure and to suppress a new request - the difference being that the timeout clock would not be reset.

Airshow

Maybe I don't understand well enough, but it sounds like you are concerned that a single Client may end up receiving multiple responses. Based on the following, I think that's highly unlikely to happen except maybe due to a severely borked connection. Here's how it currently works:

  1. User opens Client page, which initiates one long-polling connection in background
  2. Something (either a Game move or a timeout) happens on the Server side to trigger the Client's Latch, causing data to be transmitted to Client
  3. Client processes the new data, ending the long-polling connection.
  4. Client re-initiates one long-polling connection if data in step 3 was valid (not a timeout)

--> Result: 1x Client page with 1x Long-poll connection at all times

It also works with multiple Client pages for the same User. Consider the following:

  1. User opens Client page at home, which initiates long-polling connection in background
  2. User opens another Client page at work 10 minutes later, which also initiates long-polling connection in background
  3. Somewhere in Africa a second User makes a move, triggering an update for both Clients
  4. Both Clients receive new Data, both re-initialize their long-polling connections.

So hypothetically, if there's a great deal of Game activity during the day, the Client at home will still be connected and up to date when the User returns from work later that day :) And since Latches automatically time out in 30 minutes or so, there should be no way for them to unnecessarily pile up on the server side.

OK, from what you say, long-polling requests (for learning about other players' game moves) are independent of own-data "requests" (reporting of own game moves). That should be fine.

I was assuming a slightly different schema in which there was only one type of request, which provided own-data and initiated a long poll (server-side latch). This could also be made to work.

Your scheme has the advantage that own-data "requests" can have their own immediate HTTP responses but the disadavntage that latch timeout, however generous, is more likely to occur. This could be an issue in a low volume game type (eg. chess, as opposed to a shoot-em-up game).

Airshow

Yes, I reckon that, if given more time, I could further tweak it so that latches automatically clear if a User disrupts the connection from the Client side, for example when opening another webpage in the Game's browser window. Experimenting is what makes this stuff fun, after all

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.