如何在JavaNIO中更改socketchannel键的选择器
我对java nio的使用有疑问,希望有很多java nio知识的人能帮助我澄清一些误解 我正在使用JavaNIOSocket。写入缓冲区可能是使用socketchannel.write填充的。在这种情况下,剩余的缓冲区排队,密钥更改为OP_WRITE。我的一个场景是队列长度相当长。每次调用selector.select之前,我将另一个名为pendingRequest的队列中的键更改为OP_WRITE。但我发现,由于读取速度非常慢,在发送处理完成后,有许多未写入的消息仍然在队列中。如何处理这个问题 在我的代码中,我有两个书写位置。一个来自生成器:当它有消息要发布时,它直接写入通道。如果缓冲区已满,数据将排队。第二个位置在dispatcher中:当键是可写的时,它回调write以写入排队的数据。我想这两部分可以竞争写作。我只是觉得我的代码缺少一些处理来配合两次编写如何在JavaNIO中更改socketchannel键的选择器,java,queue,nio,Java,Queue,Nio,我对java nio的使用有疑问,希望有很多java nio知识的人能帮助我澄清一些误解 我正在使用JavaNIOSocket。写入缓冲区可能是使用socketchannel.write填充的。在这种情况下,剩余的缓冲区排队,密钥更改为OP_WRITE。我的一个场景是队列长度相当长。每次调用selector.select之前,我将另一个名为pendingRequest的队列中的键更改为OP_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;
}
//==此写入方法用于写入队列缓冲区
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.iswriteable为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);
}
}
如果您的消费者速度太慢,唯一的选择可能是断开他们的连接以保护您的服务器。你不希望一个坏消费者影响你的其他客户 我通常会将发送缓冲区的大小增加到一个点,如果它填满了,我就会关闭连接。这避免了在Java代码中处理未写入数据的复杂性,因为您真正要做的就是进一步扩展缓冲区。如果增加发送缓冲区的大小,则可以透明地执行此操作。您甚至可能不需要使用发送缓冲区大小,默认值通常约为64 KB 您必须确保新数据在已经等待写入的数据之后进入队列 如果这种行为持续存在,您实际上只有两种选择:要么以行为不端为由断开客户机的连接,要么在积压清除之前停止为其生成输出。可能两者都有 您可以通过熟练地使用较长的选择超时来实现第一个。如果select返回零,则意味着在超时期间没有注册的通道或任何通道都没有发生任何事情,在这种情况下,您可能需要考虑断开与所有客户端的连接。如果您有很多并发客户机太费劲而无法工作,那么您必须跟踪每个通道最后一次被选中的时间,并断开上次活动时间过长的任何通道 在这段超时时间内,您可能还希望在那个家伙阅读缓慢时停止为他生成输出
“longish”的精确定义留给读者作为练习,但十分钟作为第一个近似值出现在脑海中。请发布一些代码,最好是演示您遇到的问题的代码。最后一个方法,即编写队列的方法,总是从队列中移除缓冲区,即使n==0。您应该在删除缓冲区之前进行测试并中断,事实上,您应该仅在缓冲区为空时删除它。目前您正在丢失数据。在我当前的代码中,删除0是在测试写入的字节数之后放置的。目前代码运行正常,但性能不如预期的好。缓冲区已满后,下一个可写时间需要多长时间?我使用以下方法更改socketchannel buffersize。但是为什么serverSocket只能设置receivebuffer大小。socket.socket.setReceiveBufferSize256*1024;socket.socket.setSendBufferSize256*1024@susan ServerSocket允许您更改接收缓冲区大小,该大小由接受的套接字继承,因此您可以将其设置为>64k。如果您尝试对接受的套接字执行此操作,它将失败,因为>64k需要TCP“窗口缩放”选项,该选项在连接握手期间协商。出于同样的原因,如果要在客户端套接字上设置大于64k的接收缓冲区,则必须在连接之前进行设置。您可以随时设置发送缓冲区,因为它不需要协议的帮助。@susan更正:它不会“失败”,但64k以上的部分不会被使用,除非在连接之前设置大小。有什么解决方案可以解决我上面提到的问题吗?我发现在我的代码中,许多排队的数据无法写出。当密钥可写时,生成器可能会再次写入数据,这会导致队列数据的写入更改较少。如何使这部分正确?谢谢。另一个提出建议的理由是
您更改发送缓冲区大小的前提是您对使用者的控制较少,这就是为什么它的执行速度不够快,无法使用您发送的数据。我的解决办法是;记录警告并关闭与慢速耗电元件的连接。可能增加发送方缓冲区的原因是为了减少误报,也就是说,除非真的出了问题,否则这种情况不会发生。我非常感谢您的帮助。它的工作原理是让消息在生成器端生成下一条消息之前写出。但对于另一个进程,如果缓冲区已满,则等待unitl,因为还有空间再次写入。