我对使用java nio有疑问,希望有很多java nio知识的人可以帮我澄清一些误解。
我正在使用java nio socket。可以使用socketchannel.write()填充写缓冲区。在这种情况下,剩余的缓冲区排队,密钥更改为OP_WRITE。我的一个场景是队列长度很长。每次调用selector.select()之前,我都会从另一个名为pendingRequest的队列中将键更改为OP_WRITE。但我发现由于读取速度很慢,在发送处理完成后,有许多消息未写入,它们仍然在队列中。如何处理这个问题?
在我的代码中,我有两个写作地点。一个是来自发生器:当它有发布消息时,它直接写入通道。如果缓冲区已满,则数据将排队。第二个位置在调度程序中:当密钥可写时,它会回调write()来写入排队的数据。我想这两部分可以争夺写作。我只觉得我的代码缺乏一些合作两次写入的处理。
有没有解决方案来解决我上面提到的问题?我在我的代码中发现许多排队的数据无法写出来。当密钥可写时,生成器可以再次写入数据,这导致排队的数据具有较少的写入变化。如何使这部分正确?感谢
//在WriteListener()中,编写代码分为以下三部分
public synchronized int writeData(EventObject source) {
int n = 0;
int count = 0;
SocketChannel socket = (SocketChannel)source.getSource();
ByteBuffer buffer = ((WriteEvent)source).getBuffer();
try {
write(socket);
} catch (IOException e1) {
e1.printStackTrace();
}
while (buffer.position()>0) {
try {
buffer.flip();
n = socket.write(buffer);
if(n == 0) {
key.interestOps(SelectionKey.OP_WRITE); synchronized (this.pendingData) {
List<ByteBuffer> queue = (List<ByteBuffer>) this.pendingData.get(socket);
if(queue == null) {
queue = new ArrayList<ByteBuffer>();
this.pendingData.put(socket, queue);
}
queue.add(buffer);
logger.logInfo("queue length:" + queue.size());
}
break;
}
count += n;
} catch (IOException e) {
e.printStackTrace();
} finally {
buffer.compact();
}
}
if(buffer.position()==0) {
key.interestOps(SelectionKey.OP_READ);
}
return count;
}
// ====此write方法用于写入排队缓冲区
public synchronized int write(SocketChannel sc, ByteBuffer wbuf) {
int n = 0;
int count = 0;
SelectionKey key = sc.keyFor(this.dispatcher.getDemultiplexer().getDemux());
while (wbuf.position()>0) {
try {
wbuf.flip();
n = sc.write(wbuf);
if(n == 0) {
key.interestOps(SelectionKey.OP_WRITE);
synchronized (this.pendingData) {
List<ByteBuffer> queue = (List<ByteBuffer>) this.pendingData.get(sc);
if(queue == null) {
queue = new ArrayList<ByteBuffer>();
this.pendingData.put(sc, queue);
}
queue.add(wbuf);
}
break;
}
count += n;
} catch (IOException e) {
e.printStackTrace();
} finally {
wbuf.compact();
}
}
if(wbuf.position()==0) {
wbuf.clear();
key.interestOps(SelectionKey.OP_READ);
}
return n;
}
// ====当key.isWritable()为true时,此方法是Dispatch的回调
public void write(SocketChannel socketChannel) throws IOException {
SelectionKey key = socketChannel.keyFor(this.dispatcher.getDemultiplexer().getDemux());
synchronized (this.pendingData) {
List<ByteBuffer> queue = (List<ByteBuffer>) this.pendingData.get(socketChannel);
if(queue == null || queue.isEmpty()) {
// We wrote away all data, so we're no longer interested
// in writing on this socket. Switch back to waiting for data.
try {
if (key!=null)
key.interestOps(SelectionKey.OP_READ);
} catch(Exception ex) {
if (key!=null)
key.cancel();
}
}
// Write until there's not more data ...
int n = 0;
while (queue != null && !queue.isEmpty()) {
ByteBuffer buf = (ByteBuffer) queue.get(0);
// zero length write, break the loop and wait for next writable time
n = write(socketChannel, buf);
logger.logInfo("queue length:" + queue.size() + " used time: " + (t2-t1) + " ms.");
if(n==0) {
break;
}
queue.remove(0);
}
}
答案 0 :(得分:0)
如果您的消费者太慢,唯一的选择可能是断开它们以保护您的服务器。您不希望一个不良消费者影响您的其他客户。
我通常会将发送缓冲区大小增加到填充的位置,然后关闭连接。这样可以避免在Java代码中处理未写入数据的复杂性,因为您所做的只是将缓冲区扩展一点。如果增加发送缓冲区大小,则会透明地执行此操作。您可能甚至不需要使用发送缓冲区大小,默认值通常约为64 KB。
答案 1 :(得分:0)
您必须确保在已经待处理的数据之后将新数据排入队列。
如果行为仍然存在,那么您实际上只有两个选择:根据不当行为断开客户端连接,或者在积压清除之前停止为其生成输出。可能两者都有。
您可以通过熟练使用long的select()超时来实现第一个。如果select()返回零,则表示在超时期间没有注册的通道或者没有发生任何任何通道,在这种情况下,您可能需要考虑与所有客户端断开连接。如果你有很多并发客户端过于繁琐而无法工作,那么你必须跟踪每个频道最后一次被选中的时间,并断开上次活动时间过早的任何频道。
在超时期限内,您可能 想要在阅读速度慢的情况下停止为该家伙制作输出。
“longish”的精确定义留给读者作为练习,但作为第一个近似,我想到了十分钟。