如果你在写完后太快读了,java.net.Socket会暂停

时间:2014-06-04 22:15:47

标签: java sockets

在AWS机器上的Java 7上运行Java套接字代码的奇怪行为:

我的服务器有一个自定义协议,我们打开一个套接字,然后发送和接收BSON消息。测试客户端创建并打开套接字,发送请求,然后驻留在套接字的InputStream上,等待响应。当收到响应或读取超时时,将发送下一个请求。

我发现如果我在通过InputStream发送请求后过快地触摸套接字OutputStream,则套接字偶尔会阻塞,直到它的读取超时。我已经尝试了socket.getInputStream().read(...)socket.getInputStream().available();这两个电话都会导致问题。如果我只是在发送后等待200ms左右,我从服务器获得近100%的成功读取。如果在同一子网上的系统上,如果我在写入后立即触摸套接字(socket.getOutputStream().write(...); socket.getOutputStream().flush()),套接字将阻塞,直到达到20秒的超时时间为1%到7%之间。所有的尝试。

还有其他人看过这种行为吗?你知道造成它的原因吗?你有关于如何处理它的建议吗?我希望在快速网络上大多数读取都会在20到40ms之间返回(除了那些阻塞和超时的网络之外,它们大多数都会这样做。)

使用的实际代码非常复杂,但这里有一些相关的代码段:

高级读/写:

InputStream is = sock.getInputStream();
OutputStream os = sock.getOutputStream();
String req = getRequestData();

String uuid = UUID.randomUUID().toString();

long start = System.currentTimeMillis();
protocolHandler.write(uuid, getUsername(), os, req);

long dt = System.currentTimeMillis() - start;
if (dt < 125l) {
    try { Thread.sleep(125-dt); } catch (InterruptedException ex) {}
}

String in = protocolHandler.read(uuid, is, timer, getResponseCount(), getTimeout());

套接字创建:

private Socket newSocket(String socketKey) {
    Socket con = null;

    try {
        SocketAddress sockaddr = new InetSocketAddress(getServer(), getPort());
        con = new Socket();
        con.setKeepAlive(true);
        if (getPropertyAsString(SO_LINGER,"").length() > 0){
            con.setSoLinger(true, getSoLinger());
        }
        con.connect(sockaddr, getConnectTimeout());
        if(log.isDebugEnabled()) {
            log.debug("Created new connection " + con); //$NON-NLS-1$
        }
        Client client = new Client(con);
        Client.threadIdToClientMap.put(socketKey, client);
    } catch (UnknownHostException e) {
        log.warn("Unknown host for " + getLabel(), e);//$NON-NLS-1$
        e.printStackTrace(System.err);
        return null;
    } catch (IOException e) {
        log.warn("Could not create socket for " + getLabel(), e); //$NON-NLS-1$
        e.printStackTrace(System.err);
        return null;
    }

    // (re-)Define connection params - Bug 50977 
    try {
        con.setSoTimeout(getTimeout());
        con.setTcpNoDelay(getNoDelay());
    } catch (SocketException se) {
        log.warn("Could not set timeout or nodelay for " + getLabel(), se); //$NON-NLS-1$
        se.printStackTrace(System.err);
    }

    return con;
}

Socket写道:

public void write(String messageId, String playerId, OutputStream os, String hexEncodedBinary) throws IOException {
    String messageHexBytes = substituteVariables(hexEncodedBinary);

    AbstractMessage messageObject = MessageRewriter.parseRequestData(messageHexBytes);
    int seq = MessageRewriter.getSequence(messageHexBytes);

    messageObject.setPassthrough(messageId);
    byte[] messageBytes = MessageRewriter.serialize(seq, messageObject);

    os.write(messageBytes);
    os.flush();
}

插座阅读:

public String read(String messageId, InputStream socket, LatencyTimer latencyTimer, int requiredResponseCount, int socketTimeoutMillis) 
        throws ReadException {
    String threadName = "thread "+Thread.currentThread().getId();

    StringBuilder result = new StringBuilder("passthrough "+messageId+"\n");
    int nBytesThisRead = -1;
    byte[] buffer=null;
    try {
        int nResponses = 0;

        // As long as we have bytes or need a response, continue to produce messages.  Messages we don't use will be cached.
        // The socket object throws an exception on timeout (20 seconds-ish) to terminate on "nothing read".
        //
        // TODO: refactor this abortion so it's readable and understandable
        //
        while ((! interrupted) && (nResponses < requiredResponseCount || socket.available() > 0)) {
            // clear "buffer" to make log messages less confusing (because on 0-byte reads, residual data is logged if we don't do this)
            buffer = new byte[0];

            // read the size bytes
            int totalBytesRead = 0;
            byte[] sizeBuffer = new byte[4];
            while (totalBytesRead < BYTES_PER_INTEGER) {
                try {
                    nBytesThisRead = socket.read(sizeBuffer, totalBytesRead, BYTES_PER_INTEGER-totalBytesRead);
                    if (nBytesThisRead > 0) {
                        latencyTimer.stop()
                        totalBytesRead += nBytesThisRead;
                    }
                }
                //
                // this is the timeout we get ~5% of the time if I don't wait ~ 100ms
                //
                catch (java.net.SocketTimeoutException e) {
                    log.error(threadName+" timeout waiting for size bytes");
                    latencyTimer.stop();
                    return "";
                }
            }

            int messageSize = getLittleEndianInteger(sizeBuffer);
            log.debug(threadName+": message size: " + messageSize);

            buffer = Arrays.copyOf(sizeBuffer, BYTES_PER_INTEGER+messageSize);

            // reset; now read the message body
            totalBytesRead = 0;
            while (totalBytesRead < messageSize) {
                nBytesThisRead = socket.read(buffer, BYTES_PER_INTEGER+totalBytesRead, messageSize-totalBytesRead);
                if (nBytesThisRead > 0)
                    totalBytesRead += nBytesThisRead;
            }

            if (totalBytesRead != messageSize) {
                log.error(String.format("%s abandoning attempt to read %d responses for id %s. Read %d bytes; needed %d.", 
                        threadName, requiredResponseCount, messageId, totalBytesRead, messageSize));
                throw new ReadException(
                        "Unable to read complete Gluon message.", null, result.toString()+toHex(buffer, BYTES_PER_INTEGER + nBytesThisRead));
            }

            message = MessageRewriter.deserialize(buffer);
            String hexString = toHex(buffer, BYTES_PER_INTEGER + messageSize);
            String uuid = message.getPassthrough();

            if (messageId.equals(uuid)) {
                ++nResponses;
            }
            else {
                log.debug(String.format("Read: %s message type %s with msgId %s to cache", 
                        threadName, message.getClass().getSimpleName(), uuid));
                messageCache.put(uuid, new MessageToClient(message, hexString));
            }

            // even ignored messages get sent to the verifiers
            if (log.isDebugEnabled()) {
                log.debug(String.format("Read message for %s (%d bytes): %s", uuid, BYTES_PER_INTEGER + messageSize, hexString));
                log.debug(String.format("%s Read: message type %s with msgId %s from socket; still need %d response messages.", 
                        threadName, message.getClass().getSimpleName(), messageId, requiredResponseCount-nResponses));
            }
            result.append(hexString);
            result.append("\n");
        }

    } catch (IOException e) {
        // TODO: clear out the socket? We'd need a new "open new socket" checkbox in the UI, and fail-fast when unchecked.
        String msg = result.toString()+"partial:"+toHex(buffer, BYTES_PER_INTEGER + nBytesThisRead);
        log.error(threadName+" throwing read exception; read message so far is:\n"+msg,e);
        throw new ReadException("Unable to read expected result.", e, msg);
    }

    return result.toString();
}

0 个答案:

没有答案