如果在写入后读取过快,则java.net.Socket会阻止超时
AWS机器上Java 7上运行的Java套接字代码的奇怪行为: 我的服务器有一个自定义协议,我们在其中打开一个套接字,然后发送和接收BSON消息。测试客户端创建并打开一个套接字,发送一个请求,然后在套接字的InputStream上驻留,等待响应。当收到响应或读取超时时,将发送下一个请求 我发现,如果在通过如果在写入后读取过快,则java.net.Socket会阻止超时,java,sockets,Java,Sockets,AWS机器上Java 7上运行的Java套接字代码的奇怪行为: 我的服务器有一个自定义协议,我们在其中打开一个套接字,然后发送和接收BSON消息。测试客户端创建并打开一个套接字,发送一个请求,然后在套接字的InputStream上驻留,等待响应。当收到响应或读取超时时,将发送下一个请求 我发现,如果在通过OutputStream发送请求后,我过快地触摸套接字的InputStream,套接字偶尔会阻塞,直到读取超时。我已经尝试了socket.getInputStream().read(…)和soc
OutputStream
发送请求后,我过快地触摸套接字的InputStream
,套接字偶尔会阻塞,直到读取超时。我已经尝试了socket.getInputStream().read(…)
和socket.getInputStream().available()代码>两个调用都会导致问题。如果我只是在发送后等待200毫秒左右,我就可以从服务器上获得近100%的成功读取。如果在同一子网上的系统上,如果我在写入后立即触摸套接字(socket.getOutputStream().write(…);
socket.getOutputStream().flush()
),则套接字会阻塞,直到达到其20秒超时,占所有尝试的1%到7%
还有人见过这种行为吗?你知道是什么引起的吗?你对如何处理这件事有什么建议吗?我预计,在高速网络上,大多数读取都会在20到40毫秒之间返回(除了那些阻塞和超时的读取之外,大多数都是这样)
实际使用的代码相当复杂,但以下是一些相关的代码片段:
高级读/写:
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());
套接字写入:
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();
}
公共字符串读取(字符串消息ID、InputStream套接字、LatencyTimer LatencyTimer、int requiredResponseCount、int socketTimeoutMillis)
抛出ReadException{
字符串threadName=“thread”+thread.currentThread().getId();
StringBuilder结果=新的StringBuilder(“传递”+messageId+“\n”);
int nBytesThisRead=-1;
字节[]缓冲区=空;
试一试{
int nResponses=0;
//只要我们有字节或需要响应,就继续生成消息。我们不使用的消息将被缓存。
//套接字对象在超时(20秒)时抛出异常,以在“未读取”时终止。
//
//TODO:重构此文件,使其可读性和可理解性
//
而((!interrupted)&(nResponses0)){
//清除“缓冲区”以减少日志消息的混乱(因为在0字节读取时,如果不这样做,将记录剩余数据)
缓冲区=新字节[0];
//读取大小字节
int totalBytesRead=0;
字节[]sizeBuffer=新字节[4];
while(totalBytesRead<字节/整数){
试一试{
nBytesThisRead=socket.read(sizeBuffer,totalBytesRead,字节/INTEGER-totalBytesRead);
如果(nBytesThisRead>0){
latencyTimer.stop()
totalBytesRead+=nBytesThisRead;
}
}
//
//如果我不等待约100毫秒,我们将获得约5%的超时时间
//
catch(java.net.SocketTimeoutException e){
log.error(threadName+“等待大小字节超时”);
latencyTimer.stop();
返回“”;
}
}
int messageSize=getLittleEndianInteger(sizeBuffer);
log.debug(threadName+“:消息大小:“+messageSize”);
buffer=Arrays.copyOf(sizeBuffer,字节/整数+消息大小);
//重置;现在阅读消息正文
totalBytesRead=0;
while(totalBytesRead0)
totalBytesRead+=nBytesThisRead;
}
if(totalBytesRead!=消息大小){
log.error(String.format(“%s放弃读取id%s的%d个响应的尝试。读取%d个字节;需要%d.”,
threadName、requiredResponseCount、messageId、TotalByteRead、messageSize);
抛出新的ReadException(
“无法读取完整的胶子消息。”,null,result.toString()+toHex(缓冲区,字节/整数+nBytesThisRead));
}
message=MessageRewriter.反序列化(缓冲区);
字符串hexString=toHex(缓冲区,字节/整数+消息大小);
字符串uuid=message.getPassthrough();
if(messageId.equals(uuid)){
++答复;
}
否则{
log.debug(String.format(“读取:%s消息类型%s,要缓存的msgId为%s”),
threadName,message.getClass().getSimpleName(),uuid));
put(uuid,newmessagetoclient(message,hexString));
}
//甚至被忽略的消息也会被发送到验证器
if(log.isDebugEnabled()){
log.debug(String.format(“读取%s的消息(%d字节):%s)”,uuid,字节/u整数+消息大小,hexString));
log.debug(String.format)(“%s已读:消息类型%s,msgId为%s,来自套接字;仍需要%d条响应消息。”,
threadName、message.getClass().getSimpleName()、messageId、requiredResponseCount(响应次数));
}
result.append(十六进制字符串);
结果。追加(“\n”);
}
}捕获(IOE异常){
//TODO:清除套接字?我们需要在UI中添加一个新的“打开新套接字”复选框,如果不选中,则会快速失败。
字符串msg=result.toString()+“partial:”+toHex(缓冲区,字节/整数+nBytesThisRead);
log.error(threadName+“引发读取异常;读取
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();
}