简体   繁体   English

我的套接字连接是否丢失了一些通过它发送的数据?

[英]Is my socket connection dropping some data sent over it?

I'm writing a Connection class that sends and receives data going both ways via Command s and CommandResult s. 我正在编写一个Connection类, CommandResult通过CommandCommandResult双向发送和接收数据。 However, when multiple requests are sent quickly over the Connection , some do not make it through properly. 但是,当多个请求通过Connection快速发送时,有些请求无法正确通过。

It sound like a race condition of sorts, but I feel like I've prepared for that by: 这听起来像是种比赛条件,但我觉得我已经为此做好了准备:

  • Locking and unlocking writes to the socket, 锁定和解锁写入套接字,
  • having a table of sent Command s whose CommandResult s haven't been received, 有一个已发送Command的表,但尚未收到CommandResult
  • and locking and unlocking changes to said table. 并锁定和解锁对该表的更改。

Command s are received and processed on a single thread, so that shouldn't be the issue. Command是在单个线程上接收和处理的,因此这不应该成为问题。

I've looked over the code enough times that I feel like the problem has to be elsewhere, but my team is very confident that Connection is the culprit. 我已经查看了足够多次的代码,以至于我觉得问题一定存在于其他地方,但是我的团队非常有信心Connection是罪魁祸首。

This sample is a little long, but this was as small as I feel I could make a complete example. 这个样本有点长,但是我觉得我可以做一个完整的例子,它是如此之小。 I did make sure it was well documented though. 我确实确保已对其进行了充分的记录。 The important things to know are: 要了解的重要事项是:

  • AwaitWrapper s are just a Future. AwaitWrapper只是未来。 Getting the resource will block until it is actually filled in, 获取资源将一直阻塞,直到它被实际填充为止,
  • Message s just wrap requests and responses, Message只是包装请求和响应,
  • a Serializer is basically a gson wrapper, Serializer器基本上是gson包装器,
  • Command s and CommandResult s are tracked with a common UUID, 使用通用的UUID跟踪CommandCommandResult
  • and ICommandHandler s take in a Command and output a CommandResult . ICommandHandler接受一个Command并输出一个CommandResult The contents of Command s and CommandResult s shouldn't matter for this. CommandCommandResult的内容对此无关紧要。

Connection.java: Connection.java:

public class Connection {

    private Socket socket;
    private ICommandHandler handler;
    private Serializer ser;
    private Lock resultsLock;
    private Lock socketWriteLock;
    private Map<UUID,AwaitWrapper<CommandResult>> reservations;

    public Connection(Socket socket) {
        ser = new Serializer();
        reservations = new TreeMap<UUID,AwaitWrapper<CommandResult>>();
        handler = null;
        this.socket = socket;

        // Set up locks
        resultsLock = new ReentrantLock();
        socketWriteLock = new ReentrantLock();
    }

    public Connection(String host, int port) throws UnknownHostException, IOException {
        socket = new Socket(host, port);
        ser = new Serializer();
        reservations = new TreeMap<UUID,AwaitWrapper<CommandResult>>();
        handler = null;

        // Set up locks
        resultsLock = new ReentrantLock(true);
        socketWriteLock = new ReentrantLock(true);
    }


    /* Sends a command on the socket, and waits for the response
     *
     * @param com The command to be sent
     * @return The Result of the command operation.
     */
    public CommandResult sendCommand(Command com) {
        try {
            AwaitWrapper<CommandResult> delayedResult = reserveResult(com);
            write(new Message(com));

            CommandResult res = delayedResult.waitOnResource();
            removeReservation(com);
            return res;

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

    /* Sets handler for incoming Commands. Also starts listening to the socket
     *
     * @param handler The handler for incoming Commands
     */
    public void setCommandHandler(ICommandHandler handler) {
        if (handler == null) return;
        this.handler = handler;
        startListening();
    }

    /* Starts a thread that listens to the socket
     *
     * Note: don't call this until handler has been set!
     */
    private void startListening() {
        Thread listener = new Thread() {
            @Override
            public void run() {
                while (receiveMessage());
                handler.close();
            }
        };
        listener.start();
    }

    /* Recives all messages (responses _and_ results) on a socket
     *
     * Note: don't call this until handler has been set!
     *
     * @return true if successful, false if error
     */
    private boolean receiveMessage() {
        InputStream in = null;
        try {
            in = socket.getInputStream();

            Message message = (Message)ser.deserialize(in, Message.class);
            if (message == null) return false;

            if (message.containsCommand()) {
                // Handle receiving a command
                Command com = message.getCommand();
                CommandResult res = handler.handle(com);
                write(new Message(res));

            } else if (message.containsResult()) {
                // Handle receiving a result
                CommandResult res = message.getResult();
                fulfilReservation(res);
            } else {
                // Neither command or result...?
                return false;
            }

        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }


    //--------------------------
    // Thread safe IO operations

    private void write(Message mes) throws IOException {
        OutputStream out = socket.getOutputStream();
        socketWriteLock.lock();
        ser.serialize(out, mes);
        socketWriteLock.unlock();
    }


    //----------------------------------
    //Thread safe reservation operations

    private AwaitWrapper<CommandResult> reserveResult(Command com) {
        AwaitWrapper<CommandResult> delayedResult = new AwaitWrapper<CommandResult>();

        resultsLock.lock();
        reservations.put(com.getUUID(), delayedResult);
        resultsLock.unlock();

        return delayedResult;
    }

    private void fulfilReservation(CommandResult res) {
        resultsLock.lock();
        reservations.get(res.getUUID()).setResource(res);
        resultsLock.unlock();
    }

    private void removeReservation(Command com) {
        resultsLock.lock();
        reservations.remove(com.getUUID());
        resultsLock.unlock();
    }


    //-------------------------------------------------------------------
    // A Message wraps both commands and results for easy deserialization

    private class Message {
        ...
    }
}

When monitoring the receiving side of the Connection , the handler never gets triggered for some of the Command s sent. 在监视Connection的接收方时,处理程序永远不会为发送的某些Command触发。 It should be triggered by and process every incoming Command . 它应该由每个传入的Command触发并处理。

I'm considering ditching the reservation table and locking writes to the socket until the the response has been received, but I'm expecting that that won't come without significant performance penalties. 我正在考虑放弃预留表并锁定对套接字的写入,直到收到响应为止,但是我希望没有严重的性能损失就不会实现。

Am I missing some crucial step that would prevent race conditions? 我是否错过了一些阻止比赛条件的关键步骤?


EDIT: Adding the Serializer and ICommandHandler classes for those who are curious. 编辑:为那些好奇的人添加SerializerICommandHandler类。

Serializer.java: Serializer.java:

public class Serializer {

    private Gson gson;

    public Serializer() {
        gson = new Gson();
    }

    public Object deserialize(InputStream is, Class type) throws IOException {
        JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8));
        reader.setLenient(true);
        if (reader.hasNext()) {
            Object res = gson.fromJson(reader, type);
            return res;
        }
        return null;
    }

    public void serialize(OutputStream os, Object obj) throws IOException {
        JsonWriter writer = new JsonWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
        gson.toJson(obj, obj.getClass(), writer);
        writer.flush();
    }
}

ICommandHandler: ICommandHandler:

public interface ICommandHandler {
    public CommandResult handle(Command com);
    public void close();
}

The locks do nothing in your case, there is no race-condition there, if the same handler is used for multiple sockets then the locks need to be inside the handler, you're using a single-threaded client so locks do nothing there, Note: when using locks use a try-finally . 在您的情况下,锁不执行任何操作,那里没有竞争条件,如果将同一个处理程序用于多个套接字,则锁需要位于该处理程序中,您使用的是单线程客户端,因此锁在那里不执行任何操作,注意:使用锁时,请try-finally使用try-finally If you're just starting with Sockets you probably don't know this but SocketChannel is a lot more efficient than the Socket class which is extremely old. 如果您只是从Sockets开始,您可能不知道这一点,但是SocketChannel比极其古老的Socket类要高效得多。 I can't help you more than that without seeing Serializer and ICommandHandler . 如果没有看到SerializerICommandHandler ,我将为您提供更多帮助。 It is most likely a problem in the Serializer . 这很可能是Serializer的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM