立即取消正在运行的Java Websocket传输

时间:2020-06-12 07:39:48

标签: javascript java websocket java-websocket

问题描述稍长,所以首先我的问题:

通过网络套接字发送二进制数据时,如何立即取消正在运行的传输?

背景:

我有一个JavaScript客户端,可以用作GIS(地理信息系统)。它的外观和感觉类似于Google Maps,它具有一个地图窗口,用户可以通过使用鼠标拖动和缩放来导航。 例如,如果用户移动了地图,则新的坐标将通过WebSocket发送到远程Java进程。 Java流程现在将创建一个新的地图图像,并将其发送到客户端。在映像构建期间,它还会发送未完成的中间映像,因此客户端不必等待太久。 如果客户端现在快速连续移动地图几次,那么新的查询可能会到达Java后端,而先前的查询仍在处理中。现在,先前的过程会将过时的图像发送到客户端。因此,如果查询到达Java后端,则必须中止来自该客户端的先前查询的所有处理,并分摊结果。

我必须确保两件事。如果有新查询,

  • 必须取消旧查询的图像生成。
  • 从旧查询中发送图像必须取消。

尤其是后一个给我带来了麻烦。我当前的解决方案是这样的:

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/asyncImage")
public class AsynchronousImageWebSocket {

    private Map<Session, Future<?>> futures = new HashMap<>();

    @OnMessage
    public void onMessage(String message, Session session) throws Exception {
        
        // Start the image generating process, 
        // passing the current websocket session as client ID.
        startImageGeneratingProcess(session);
    }

    // Called by the image generating process to send intermediate or final images to 
    // the client.
    // It also passes the websocket session that has been passed to startImageGeneratingProcess.
    public void sendImage(BufferedImage img, Session session) {
        if (futures.containsKey(session)) {
            if (!futures.get(session).isDone()) {
                
                // If a Future is currently stored for that session and it's not done yet,
                // this means that there already is a running image generating process from
                // a previous query. Cancel it.
                futures.get(session).cancel(true);
                logger.info("SEND cancelled");
            }
            futures.remove(session);
        }
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
            ImageIO.write(img, "PNG", out);

            // Send the image, store the returned Future and associate it with the 
            // client's session.
            Future<?> f = session.getAsyncRemote().sendBinary(ByteBuffer.wrap(out.toByteArray()));
            futures.put(session, f);
        } catch (IOException e) {
            logger.error(e);
        } catch (Exception e) {
            logger.error(e);
        }
    }
}

不幸的是,Future的cancel方法似乎没有被评估。即使我在它的Future上调用cancel,一个正在运行的sendBinary方法也完成了。有没有办法立即取消较旧的处理作业的正在运行的sendBinary方法?

感谢您的输入,如果您还有其他需要,请告诉我。

p.s。另一个想法是简单地继续发送所有内容,并以某种方式让客户端识别并整理不赞成使用的图像。但是,生成和发送不推荐使用的图像会花费很多资源,我想节省这些资源。

1 个答案:

答案 0 :(得分:0)

对不起,那完全是我的坏事。 websocket没有被阻止。正是在另一个地方的同步导致发送同步执行。

如果您在没有任何同步代码的情况下执行上述的sendImage方法,则该方法应无阻塞地执行。相反,如果session.getAsyncRemote()。sendBinary由两个线程同时执行,则它们都将抛出IllegalStateException并中止发送。在这种情况下,可以捕获该异常,将其丢弃,然后重新发送最后一张图像即可。

基于此,我以以下方式更改了sendImage方法:

public void temporaryImageReady(BufferedImage img, Session session) {
    if (futures.containsKey(session)) {
        if (!futures.get(session).isDone()) {
            futures.get(session).cancel(true);
            logger.info("SEND cancelled");
        }
        futures.remove(session);
    }

    try {
        send(img, handle);
    } catch (IOException e) {
        logger.error(e);
    } catch (IllegalStateException e) {
        logger.info("Image send collision, resend last image.");
        try {
            send(img, handle);
        } catch (Exception e1) {
            logger.info("Image resend after collision failed.");
            logger.error(e);
        }
    } catch (Exception e) {
        logger.error(e);
    }
}
    
private void send(BufferedImage img) throws Exception {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
        ImageIO.write(img, "PNG", out);
        Future<?> f = session.getAsyncRemote().sendBinary(ByteBuffer.wrap(out.toByteArray()));
        futures.put(session, f);
    }
}