[英]Java NIO reading and writing from distant machine
I'd like to use the NIO to send/receive data to/from a distant machine. 我想使用NIO向/从远程机器发送/接收数据。 I can send or receive data at any time, when i need to send data i just send it without any queries from the distant machine, and the distant machine send me data at regular interval. 我可以随时发送或接收数据,当我需要发送数据时,我只是发送它而没有来自远程计算机的任何查询,并且远程计算机会定期发送给我数据。 I don't understand the NIO mechanism. 我不了解NIO机制。 What generates and read or write event on the Selector SelectionKey ? 什么在选择器SelectionKey上生成和读取或写入事件? Is it possible to use only one ServerSocketChannel on my side, to read data from the distant machine et to write data to it ? 是否可以仅在我这一边使用一个ServerSocketChannel从远程计算机读取数据并将数据写入其中? That is what i understand but i don't see how the writing event can be triggered... Thank you for your explanation. 这就是我的理解,但我不知道如何触发书写事件……谢谢您的解释。
I already did some coding and i can read data coming in from the distant machine, but cannot write. 我已经做过一些编码,可以读取来自远程计算机的数据,但无法写入。 I use Selector and i don't know how can i write data. 我使用选择器,我不知道该如何写数据。 The logged message "handle write" is never written, but in wireshark i can see my packet. 记录的消息“ handle write”从未被写入,但是在wireshark中我可以看到我的数据包。
public class ServerSelector {
private static final Logger logger = Logger.getLogger(ServerSelector.class.getName());
private static final int TIMEOUT = 3000; // Wait timeout (milliseconds)
private static final int MAXTRIES = 3;
private final Selector selector;
public ServerSelector(Controller controller, int... servPorts) throws IOException {
if (servPorts.length <= 0) {
throw new IllegalArgumentException("Parameter(s) : <Port>...");
}
Handler consolehHandler = new ConsoleHandler();
consolehHandler.setLevel(Level.INFO);
logger.addHandler(consolehHandler);
// Create a selector to multiplex listening sockets and connections
selector = Selector.open();
// Create listening socket channel for each port and register selector
for (int servPort : servPorts) {
ServerSocketChannel listnChannel = ServerSocketChannel.open();
listnChannel.socket().bind(new InetSocketAddress(servPort));
listnChannel.configureBlocking(false); // must be nonblocking to register
// Register selector with channel. The returned key is ignored
listnChannel.register(selector, SelectionKey.OP_ACCEPT);
}
// Create a handler that will implement the protocol
IOProtocol protocol = new IOProtocol();
int tries = 0;
// Run forever, processing available I/O operations
while (tries < MAXTRIES) {
// Wait for some channel to be ready (or timeout)
if (selector.select(TIMEOUT) == 0) { // returns # of ready chans
System.out.println(".");
tries += 1;
continue;
}
// Get iterator on set of keys with I/O to process
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while (keyIter.hasNext()) {
SelectionKey key = keyIter.next(); // Key is a bit mask
// Server socket channel has pending connection requests?
if (key.isAcceptable()) {
logger.log(Level.INFO, "handle accept");
protocol.handleAccept(key, controller);
}
// Client socket channel has pending data?
if (key.isReadable()) {
logger.log(Level.INFO, "handle read");
protocol.handleRead(key);
}
// Client socket channel is available for writing and
// key is valid (i.e., channel not closed) ?
if (key.isValid() && key.isWritable()) {
logger.log(Level.INFO, "handle write");
protocol.handleWrite(key);
}
keyIter.remove(); // remove from set of selected keys
tries = 0;
}
}
}
}
The protocol 协议
public class IOProtocol implements Protocol {
private static final Logger logger = Logger.getLogger(IOProtocol.class.getName());
IOProtocol() {
Handler consolehHandler = new ConsoleHandler();
consolehHandler.setLevel(Level.INFO);
logger.addHandler(consolehHandler);
}
/**
*
* @param key
* @throws IOException
*/
@Override
public void handleAccept(SelectionKey key, Controller controller) throws IOException {
SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
clntChan.configureBlocking(false); // Must be nonblocking to register
controller.setCommChannel(clntChan);
// Register the selector with new channel for read and attach byte buffer
SelectionKey socketKey = clntChan.register(key.selector(), SelectionKey.OP_READ | SelectionKey.OP_WRITE, controller);
}
/**
* Client socket channel has pending data
*
* @param key
* @throws IOException
*/
@Override
public void handleRead(SelectionKey key) throws IOException {
Controller ctrller = (Controller)key.attachment();
try {
ctrller.readData();
} catch (CommandUnknownException ex) {
logger.log(Level.SEVERE, null, ex);
}
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
/**
* Channel is available for writing, and key is valid (i.e., client channel
* not closed).
*
* @param key
* @throws IOException
*/
@Override
public void handleWrite(SelectionKey key) throws IOException {
Controller ctrl = (Controller)key.attachment();
ctrl.writePendingData();
if (!buf.hasRemaining()) { // Buffer completely written ?
// Nothing left, so no longer interested in writes
key.interestOps(SelectionKey.OP_READ);
}
buf.compact();
}
}
The controller 控制器
/**
* Fill buffer with data.
* @param msg The data to be sent
* @throws IOException
*/
private void writeData(AbstractMsg msg) throws IOException {
//
writeBuffer = ByteBuffer.allocate(msg.getSize() + 4);
writeBuffer.putInt(msg.getSize());
msg.writeHeader(writeBuffer);
msg.writeData(writeBuffer);
logger.log(Level.INFO, "Write data - message size : {0}", new Object[]{msg.getSize()});
logger.log(Level.INFO, "Write data - message : {0}", new Object[]{msg});
}
/**
* Write to the SocketChannel
* @throws IOException
*/
public void writePendingData() throws IOException {
commChannel.write(writeBuffer);
}
ServerSocketChannel
is used to make a connection, but not send data. ServerSocketChannel
用于建立连接,但不发送数据。 You need one ServerSocketChannel
and one SocketChannel
per each connection. 每个连接需要一个ServerSocketChannel
和一个SocketChannel
。
Examples of reading and writing using SocketChannel
: 使用SocketChannel
进行读写的示例:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
Your program will sleep at second line until data will come. 您的程序将在第二行进入睡眠状态,直到数据到来。 You need to put this code in infinite loop and run it in background Thread
. 您需要将此代码置于无限循环中,并在后台Thread
运行它。 When data came you can process it from this thread, then wait for another data to come. 当数据到来时,您可以从该线程处理它,然后等待其他数据到来。
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put("Hello!".getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}
There is no blocking methods, so if you sending small byte buffer you can call this from your main Thread
. 没有阻塞方法,因此,如果发送小字节缓冲区,则可以从主Thread
调用此方法。
ADD: Don't set OP_WRITE
key on new connection. 添加:不要在新连接上设置OP_WRITE
键。 Only OP_READ
. 仅OP_READ
。 When you want to write some data you need to notify selector that you want to send something and send it in events loop. 当您要写入一些数据时,需要通知选择器您要发送某些内容并在事件循环中发送它。 Good solution is to make a Queue
of outcoming messages. 好的解决方案是将待发邮件放入Queue
。 Then follow this steps: 然后按照以下步骤操作:
Queue
向Queue
添加数据 OP_WRITE
to channel's key 将OP_WRITE
设置为通道的键 while (keyIter.hasNext())
loop you'll have writable key
, write all data from queue and remove OP_WRITE
key. 在while (keyIter.hasNext())
循环中,您将具有writable key
,从队列写入所有数据并删除OP_WRITE
密钥。 It's hard for me to understand your code, but I think you'll find out what's the problem. 我很难理解您的代码,但我想您会发现问题所在。 Also if you want to have only one connection there is no need to use Selector
. 另外,如果只想建立一个连接,则无需使用Selector
。 And this is weird that you binding few ServerSocketChannels
. 绑定几个ServerSocketChannels
很奇怪。
I would suggest you use blocking NIO (which is the default behaviour for SocketChannel) You don't need to use a Selector but you can use one thread for reading and another for writing. 我建议您使用阻塞NIO(这是SocketChannel的默认行为),您无需使用选择器,但可以使用一个线程进行读取,而使用另一个线程进行写入。
Based on your example. 根据您的示例。
private final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(1024*1024);
private void writeData(AbstractMsg msg) {
writeBuffer.clear();
writeBuffer.putInt(0); // set later
msg.writeHeader(writeBuffer);
msg.writeData(writeBuffer);
writeBuffer.putInt(0, writeBuffer.position());
writeBuffer.flip();
while(writeBuffer.hasRemaining())
commChannel.write(writeBuffer);
}
What generates and read or write event on the Selector SelectionKey? 什么在选择器SelectionKey上生成和读取或写入事件?
OP_READ: presence of data or an EOS in the socket receive buffer. OP_READ:套接字接收缓冲区中存在数据或EOS。
OP_WRITE: room in the socket send buffer. OP_WRITE:在套接字发送缓冲区中的空间。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.