在Java中使用NIO进行网络编程

在JDK中,有一个非常有意思的库:NIO(New I/O)。这个库中有3个重要的类,分别是java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

本篇文章我们首先了解一下为什么需要NIO来进行网络编程,然后看看一步一步来讲解如何在网络编程中使用NIO。

为什么需要NIO

使用Java编写过Socket程序的同学一定都知道Socket和SocketServer。当调用某个调用的时候,调用的地方就会阻塞,等待响应。这种方式对于小规模的程序非常方便,但是对于大型的程序就有点力不从心了,当有大量的连接的时候,我们可以为每一个连接建立一个线程来操作。但是这种做法带来的缺陷也是显而易见的:

  1. 硬件能够支持大量的并发。

  2. 并发的数量始终有一个上限。

  3. 各个线程之间的优先级不好控制。

  4. 各个Client之间的交互与同步困难。

我们也可以使用一个线程来处理所有的请求,使用不阻塞的IO,轮询查询所有的Client。这种做法同样也有缺陷:无法迅速响应Client端,同时会消耗大量轮询查询的时间。

所以,我们需要一种poll的模式来处理这种情况,从大量的网络连接中找出来真正需要服务的Client。这正是NIO诞生的原因:提供一种Poll的模式,在所有的Client中找到需要服务的Client。

回到我们刚刚说到的3个最最重要的Class:java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

Channel代表一个可以被用于Poll操作的对象(可以是文件流也可以使网络流),Channel能够被注册到一个Selector中。通过调用Selector的select方法可以从所有的Channel中找到需要服务的实例(Accept,read ..)。Buffer对象提供读写数据的缓存。相对于我们熟悉的Stream对象,Buffer提供更好的性能以及更好的编程透明性(人为控制缓存的大小以及具体的操作)。

配合Buffer使用Channel

与传统模式的编程不用,Channel不使用Stream,而是Buffer。我们来实现一个简单的非阻塞Echo Client:

package com.cnblogs.gpcuster;

import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class TCPEchoClientNonblocking {
public static void main(String args[]) throws Exception {
if ((args.length < 2) || (args.length > 3))// Testforcorrect#ofargs throw new IllegalArgumentException(
"Parameter(s): <Server> <Word> [<Port>]");
String server = args[0];// ServernameorIPaddress // ConvertinputStringtobytesusingthedefaultcharset byte[] argument = args[1].getBytes();
int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7;
// Createchannelandsettononblocking SocketChannel clntChan = SocketChannel.open(); clntChan.configureBlocking(false);
// Initiateconnectiontoserverandrepeatedlypolluntilcomplete if (!clntChan.connect(new InetSocketAddress(server, servPort))) {
while (!clntChan.finishConnect()) {
System.out.print(".");// Dosomethingelse } } ByteBuffer writeBuf = ByteBuffer.wrap(argument); ByteBuffer readBuf = ByteBuffer.allocate(argument.length); int totalBytesRcvd = 0;// Totalbytesreceivedsofar int bytesRcvd;// Bytesreceivedinlastread while (totalBytesRcvd < argument.length) {
if (writeBuf.hasRemaining()) {
clntChan.write(writeBuf);
}
if ((bytesRcvd = clntChan.read(readBuf)) == -1) {
throw new SocketException("Connection closed prematurely");
}
totalBytesRcvd += bytesRcvd;
System.out.print(".");// Dosomethingelse } System.out.println("Received:" + // converttoStringperdefaultcharset new String(readBuf.array(), 0, totalBytesRcvd));
clntChan.close();
}
}

这段代码使用ByteBuffer来保存读写的数据。通过clntChan.configureBlocking(false); 设置后,其中的connect,read,write操作都不回阻塞,而是立刻放回结果。

使用Selector

 

 

Selector的可以从所有的被注册到自己Channel中找到需要服务的实例。

我们来实现Echo Server。

首先,定义一个接口:

package com.cnblogs.gpcuster;

import java.nio.channels.SelectionKey;
import java.io.IOException;

public interface TCPProtocol {
void handleAccept(SelectionKey key) throws IOException;

void handleRead(SelectionKey key) throws IOException;

void handleWrite(SelectionKey key) throws IOException;
}
我们的Echo Server将使用这个接口。然后我们实现Echo Server:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;

public class TCPServerSelector {
private static final int BUFSIZE = 256;// Buffersize(bytes) private static final int TIMEOUT = 3000;// Waittimeout(milliseconds) public static void main(String[] args) throws IOException {
if (args.length < 1) {// Testforcorrect#ofargs throw new IllegalArgumentException("Parameter(s):<Port>...");
}
// Createaselectortomultiplexlisteningsocketsandconnections Selector selector = Selector.open(); // Createlisteningsocketchannelforeachportandregisterselector for (String arg : args) {
ServerSocketChannel listnChannel = ServerSocketChannel.open();
listnChannel.socket().bind(
new InetSocketAddress(Integer.parseInt(arg)));
listnChannel.configureBlocking(false);// mustbenonblockingtoregister // Registerselectorwithchannel.Thereturnedkeyisignored listnChannel.register(selector, SelectionKey.OP_ACCEPT); } // Createahandlerthatwillimplementtheprotocol TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE);
while (true) {// Runforever,processingavailableI/Ooperations // Waitforsomechanneltobeready(ortimeout) if (selector.select(TIMEOUT) == 0) {// returns#ofreadychans System.out.print(".");
continue;
}
// GetiteratoronsetofkeyswithI/Otoprocess Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator(); while (keyIter.hasNext()) {
SelectionKey key = keyIter.next();// Keyisbitmask // Serversocketchannelhaspendingconnectionrequests? if (key.isAcceptable()) {
protocol.handleAccept(key);
}
// Clientsocketchannelhaspendingdata? if (key.isReadable()) {
protocol.handleRead(key);
}
// Clientsocketchannelisavailableforwritingand // keyisvalid(i.e.,channelnotclosed)? if (key.isValid() && key.isWritable()) {
protocol.handleWrite(key);
}
keyIter.remove();// removefromsetofselectedkeys } } } }
 

我们通过listnChannel.register(selector, SelectionKey.OP_ACCEPT); 注册了一个我们感兴趣的事件,然后调用selector.select(TIMEOUT)等待订阅的时间发生,然后再采取相应的处理措施。

最后我们实现EchoSelectorProtocol

package com.cnblogs.gpcuster;

import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.ByteBuffer;
import java.io.IOException;

public class EchoSelectorProtocol implements TCPProtocol {
private int bufSize;// SizeofI/Obuffer public EchoSelectorProtocol(int bufSize) {
this.bufSize = bufSize;
}

public void handleAccept(SelectionKey key) throws IOException {
SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
clntChan.configureBlocking(false);// Mustbenonblockingtoregister // Registertheselectorwithnewchannelforreadandattachbytebuffer clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer .allocate(bufSize)); } public void handleRead(SelectionKey key) throws IOException {
// Clientsocketchannelhaspendingdata SocketChannel clntChan = (SocketChannel) key.channel(); ByteBuffer buf = (ByteBuffer) key.attachment(); long bytesRead = clntChan.read(buf);
if (bytesRead == -1) {// Didtheotherendclose? clntChan.close(); } else if (bytesRead > 0) {
// Indicateviakeythatreading/writingarebothofinterestnow. key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } public void handleWrite(SelectionKey key) throws IOException {
/*
* Channelisavailableforwriting,andkeyisvalid(i.e.,clientchannel
* notclosed).
*/
// Retrievedatareadearlier ByteBuffer buf = (ByteBuffer) key.attachment(); buf.flip();// Preparebufferforwriting SocketChannel clntChan = (SocketChannel) key.channel(); clntChan.write(buf); if (!buf.hasRemaining()) {// Buffercompletelywritten? // Nothingleft,sonolongerinterestedinwrites key.interestOps(SelectionKey.OP_READ); } buf.compact();// Makeroomformoredatatobereadin } }

在这里,我们又进一步对Selector注册了相关的事件:key.interestOps(SelectionKey.OP_READ);

这样,我们就实现了基于NIO的Echo 系统。

参考

《TCP.IP.Sockets.in.Java.2nd.Edition》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用 Java NIO(New I/O)进行网络编程的简单示例: ```java import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class NIOExample { public static void main(String[] args) throws IOException { // 创建一个线程池用于处理客户端连接 ExecutorService executor = Executors.newFixedThreadPool(10); // 创建 ServerSocketChannel 并绑定端口 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress("localhost", 8080)); System.out.println("Server started on port 8080"); while (true) { // 接受客户端连接 SocketChannel socketChannel = serverSocketChannel.accept(); // 使用线程池处理客户端连接 executor.execute(() -> handleClient(socketChannel)); } } private static void handleClient(SocketChannel socketChannel) { try { ByteBuffer buffer = ByteBuffer.allocate(1024); // 读取客户端发送的数据 int bytesRead = socketChannel.read(buffer); while (bytesRead != -1) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); bytesRead = socketChannel.read(buffer); } // 响应客户端 String response = "Hello from server"; ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes()); socketChannel.write(responseBuffer); // 关闭连接 socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` 这个示例创建了一个简单的服务器,监听本地的 8080 端口。当客户端连接时,会使用线程池处理连接,并读取客户端发送的数据。然后,服务器会向客户端发送 "Hello from server" 的响应,并关闭连接。 请注意,这只是一个简单的示例,实际的网络编程可能涉及更复杂的逻辑和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值