【发布时间】:2015-07-11 05:25:45
【问题描述】:
通过从套接字的输入流中读取字节,我得到了一个非常奇怪的行为。
在我的项目中,客户向服务发出请求。对于每个请求,都会建立一个新连接。
首先发送的字节告诉服务将遵循什么样的请求。
然后发送请求本身。
服务接收字节并继续请求。这确实适用于所有请求的至少 95%。剩下的 5% 有一个奇怪的行为,我想不通。
字节并不是发送的所有字节。但是关于这个话题最奇怪的事情是丢失的字节不在流的开头或结尾。它们遍布整个溪流。
很遗憾,我无法在此处提供完整代码,因为它与工作相关。但我可以提供显示问题本身的测试代码。
为了弄清楚发生了什么,我写了 2 个类。一个来自java.net.Socket,另一个来自java.net.ServerSocket。
代码如下:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class DebugSocket extends Socket
{
private class InputStreamWrapper extends InputStream
{
private int
availables,
closes,
marksupporteds,
resets;
private List<Integer>
marks = new ArrayList<Integer>(),
reads = new ArrayList<Integer>();
private List<Long>
skips = new ArrayList<Long>();
@Override
public int available() throws IOException
{
availables++;
return DebugSocket.this.origininput.available();
}
@Override
public void close() throws IOException
{
closes++;
DebugSocket.this.origininput.close();
}
@Override
public synchronized void mark(int readlimit)
{
marks.add(readlimit);
DebugSocket.this.origininput.mark(readlimit);
}
@Override
public boolean markSupported()
{
marksupporteds++;
return DebugSocket.this.origininput.markSupported();
}
@Override
public synchronized void reset() throws IOException
{
resets++;
DebugSocket.this.origininput.reset();
}
@Override
public int read() throws IOException
{
int read = DebugSocket.this.origininput.read();
reads.add(read);
if ( read != -1 )
{
DebugSocket.this.inputdebugbuffer.write(read);
}
return read;
}
@Override
public int read(byte[] b) throws IOException
{
int read = DebugSocket.this.origininput.read(b);
DebugSocket.this.inputdebugbuffer.write(b, 0, read);
return read;
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
int read = DebugSocket.this.origininput.read(b, off, len);
DebugSocket.this.inputdebugbuffer.write(b, off, read);
return read;
}
@Override
public long skip(long n) throws IOException
{
long skipped = DebugSocket.this.origininput.skip(n);
skips.add(skipped);
return skipped;
}
}
private class OutputStreamWrapper extends OutputStream
{
private int
flushes,
closes;
@Override
public void close() throws IOException
{
closes++;
DebugSocket.this.originoutput.close();
}
@Override
public void flush() throws IOException
{
flushes++;
DebugSocket.this.originoutput.flush();
}
@Override
public void write(int b) throws IOException
{
DebugSocket.this.outputdebugbuffer.write(b);
DebugSocket.this.originoutput.write(b);
DebugSocket.this.originoutput.flush();
}
@Override
public void write(byte[] b) throws IOException
{
DebugSocket.this.outputdebugbuffer.write(b);
DebugSocket.this.originoutput.write(b);
DebugSocket.this.originoutput.flush();
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
DebugSocket.this.outputdebugbuffer.write(b, off, len);
DebugSocket.this.originoutput.write(b, off, len);
DebugSocket.this.originoutput.flush();
}
}
private static final Object
staticsynch = new Object();
private static long
idcounter = 0;
private final long
id;
private final ByteArrayOutputStream
inputdebugbuffer,
outputdebugbuffer;
private final InputStream
inputwrapper;
private final OutputStream
outputwrapper;
private InputStream
origininput;
private OutputStream
originoutput;
public InputStream getInputStream() throws IOException
{
if ( origininput == null )
{
synchronized ( inputdebugbuffer )
{
if ( origininput == null )
{
origininput = super.getInputStream();
}
}
}
return inputwrapper;
}
public OutputStream getOutputStream() throws IOException
{
if ( originoutput == null )
{
synchronized ( outputdebugbuffer )
{
if ( originoutput == null )
{
originoutput = super.getOutputStream();
}
}
}
return outputwrapper;
}
public DebugSocket()
{
id = getNextId();
inputwrapper = new InputStreamWrapper();
outputwrapper = new OutputStreamWrapper();
inputdebugbuffer = new ByteArrayOutputStream();
outputdebugbuffer = new ByteArrayOutputStream();
}
private static long getNextId()
{
synchronized ( staticsynch )
{
return ++idcounter;
}
}
}
import java.io.IOException;
import java.net.ServerSocket;
public class DebugServerSocket extends ServerSocket
{
public DebugServerSocket() throws IOException
{
super();
}
public DebugSocket accept() throws IOException
{
DebugSocket s = new DebugSocket();
implAccept(s);
return s;
}
}
DebugSocket 类接收与InputStream 和OutputStream 的每次交互的通知
现在当问题发生时,我总是可以看到字节丢失。
这里是一个例子:
客户端发送 1758 个字节。我从DebugSocket 中的成员outputdebugbuffer 获得了前23 个字节。
Bytes: 0,0,0,0,0,0,0,2,0,0,6,-46,31,-117,8,0,0,0,0,0,0,0,-83
服务器收到 227 个字节。对于调试问题,我总是会读取输入流,直到我得到 -1,以便所有字节都得到处理。现在,我从 DebugSocket 中的成员 inputdebugbuffer 获得的服务器端的 16 个前导字节。
Bytes: 0,0,0,6,-46,31,-117,8,0,0,0,0,0,0,0,-83
如图所示,缺少 7 个字节。前 8 个字节是一个长值,我将其更改为一个字节值以进行调试。所以我认为第一个字节总是正确的。
如果是代码失败,则不会进行任何请求,但正如我之前所说,这种情况最多只发生在所有连接的 5% 上。
有人知道这里发生了什么吗?
我还使用DataInputStream 和DataOutputStream 发送数据。正如您在DebugSocket 的OutputStreamWrapper 中看到的那样,我总是在每次写入操作后刷新。
我错过了什么吗?
如果需要其他代码,我会尝试发布它。
附:该服务是多线程的,可以并行处理 100 个请求。此外,客户端是多线程的,并行执行 20 个请求。如前所述,每个请求都使用其一个连接,并在请求进行后立即关闭该连接。
我希望有人对这个问题有所了解。
编辑:
没有主要的方法可以显示它在 cmets 中所做的任何事情,但这里是使用的客户端和服务器的代码块。
客户端:(以 20 个线程并行运行)
public void sendRequest(long _requesttype, byte[] _bytes)
{
Socket socket = null;
DataInputStream input = null;
DataOutputStream output = null;
InputStream sinput = null;
OutputStream soutput = null;
try
{
socket = new DebugSocket();
socket.connect(serveraddress);
sinput = socket.getInputStream();
soutput = socket.getOutputStream();
input = new DataInputStream(sinput);
output = new DataOutputStream(soutput);
output.writeLong(_requesttype);
output.flush();
soutput.flush();
output.write(_bytes);
output.flush();
soutput.flush();
// wait for notification byte that service had received all data.
input.readByte();
}
catch (IOException ex)
{
LogHelper.log(ex);
}
catch (Error err)
{
throw err;
}
finally
{
output.flush();
soutput.flush();
input.close();
output.close();
finishSocket(socket);
}
}
服务器:(每个请求在一个线程中运行。最多 100 个线程)
public void proceedRequest(DebugSocket _socket)
{
DataInputStream input = null;
DataOutputStream output = null;
InputStream sinput = null;
OutputStream soutput = null;
try
{
sinput = _socket.getInputStream();
soutput = _socket.getOutputStream();
input = new DataInputStream(sinput);
output = new DataOutputStream(soutput);
RequestHelper.proceed(input.readLong(), input, output);
// send notification byte to the client.
output.writeByte(1);
output.flush();
soutput.flush();
}
catch (IOException ex)
{
LogHelper.log(ex);
}
catch (Error err)
{
throw err;
}
finally
{
output.flush();
soutput.flush();
input.close();
output.close();
}
}
在服务器代码中,readLong() 已经失败,原因是缺少字节。
【问题讨论】:
-
您发布了一些代码,但我不确定您发布的代码是否与您的问题相关 - 也许它与您的调试相关,但在这种情况下,您可以说“我收到字节 [...]" 并没有准确解释你用来找出它的代码。
-
如果您可以将问题缩小到minimal, complete and verifiable example,那会非常有用。
-
你就在那里,但我试图避免在与工作相关的代码中出现关于我的读写行为的问题。使用这 2 个类并在最低级别显示问题。用 DebugSocket 完成什么操作并不重要,它都会被记录下来。
-
不过,您的代码应该是完整的。应该有一个
main,如果这是客户端和服务器,可能有两个。应该有显示问题的示例输入。应该有一些显示问题的东西,这样我们就可以在运行它时检查我们是否遇到了同样的问题。关键是我们能够重现您的问题,然后我们可以提供帮助。 -
根据您的字节“值”,我猜您没有正确读取流。可能使用了错误的编码。所有对字节的读取都应返回 0 到 255 之间的值,包括 0 到 255。你不会得到负值,除了 -1 流关闭时。