一个简单入门案例,贴代码:
1.客户端
package cn.itcast.nio;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
public class TcpNioClient extends Thread {
private InetSocketAddress address;
private SocketChannel channel;
private String msg;
public TcpNioClient(String hostname,int port,String msg){
address = new InetSocketAddress(hostname, port);
this.msg = msg;
System.out.println("------客户端发送数据----"+msg);
}
@Override
public void run() {
try {
channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(address);
if(channel.finishConnect()){
//两个buffer,区分用,异步的
ByteBuffer buffer = ByteBuffer.allocate(1024);
if(buffer.hasRemaining()){
channel.write(ByteBuffer.wrap(msg.getBytes()));
}
//程序会在这里阻塞,因为服务端没有写回数据
while(true){
buffer.clear();
int readnum = channel.read(buffer);
if(readnum>0){
buffer.flip();
System.out.println("客户端 ---receive data:"+new String(buffer.array(), 0, readnum));
channel.close();
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for(int i=0;i<5;i++){
TcpNioClient client = new TcpNioClient("127.0.0.1", 23000, "hello world"+i);
client.start();
}
}
}
2. 服务端
package cn.itcast.nio;
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;
import javax.rmi.CORBA.Tie;
public class NioTcpServer extends Thread {
private final InetSocketAddress socketAddress;
private Handler handler = new ServerHandler();
public NioTcpServer(String localname,int port){
socketAddress = new InetSocketAddress(localname, port);
}
@Override
public void run() {
try {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(socketAddress);
serverChannel.configureBlocking(false);
Selector select = Selector.open();
serverChannel.register(select, SelectionKey.OP_ACCEPT);
while(true){
//选择信道,开始操作
int num =select.select();
if(num>0){
Iterator<SelectionKey> iterator = select.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey ky = iterator.next();
if(ky.isAcceptable()){
handler.handleAccept(ky);
}else if(ky.isReadable()){
handler.readMsg(ky);
}else if(ky.isWritable()){
handler.writerMsg(ky);
}
//将键从键集中移除,新连接会阻塞,无法连接上服务器
iterator.remove();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
NioTcpServer tcp = new NioTcpServer("127.0.0.1", 23000);
tcp.start();
}
}
3.服务端,对不同兴趣集操作信道的接口
package cn.itcast.nio;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
public interface Handler {
void readMsg(SelectionKey key) throws IOException;
void handleAccept(SelectionKey key) throws IOException;
void writerMsg(SelectionKey key) throws IOException;
}
package cn.itcast.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import javax.print.attribute.standard.Severity;
public class ServerHandler implements Handler {
@Override
public void handleAccept(SelectionKey key) throws IOException {
//接受请求的连接,并获取新的连接,并将新的连接注册到信道上,兴趣集为读
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel acceptChannel = serverChannel.accept();
if(acceptChannel!=null){
acceptChannel.configureBlocking(false);
//再注册前,信道必须设置为非阻塞的
acceptChannel.register(key.selector(), SelectionKey.OP_READ);
}
}
@Override
public void readMsg(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.clear();
int readNum ;
if((readNum=channel.read(buffer))>0){
buffer.flip();
System.out.println("服务端 receive :"+readNum+"bytes");
System.out.println("服务端 接受到的数据 data:"+new String(buffer.array(), 0, readNum));
buffer.flip();
channel.write(buffer);
}
channel.close();
}
@Override
public void writerMsg(SelectionKey key) throws IOException {
//获取附件信息
Object obj = key.attachment();
SocketChannel sc = (SocketChannel) key.channel();
if(obj!=null){
ByteBuffer buffer = ByteBuffer.wrap(obj.toString().getBytes());
sc.write(buffer);
key.interestOps(SelectionKey.OP_READ);
buffer.compact();
}
}
}
几点需要注意的:
1. 在注册信道前,将信道配置为非阻塞,否则会报异常IllegalBlockingModeException
2. 写数据,读数据时候,注意要操作使用clear,flip方法操作缓冲区的limit,position
3. 由于是异步的,读取数据时候需要判断,返回的int的返回值,写数据时候,要判断缓冲区是否有数据
上述实现,NioTcpServer服务线程启动后,监听指定端口,等待客户端请求的到来,然后NioTcpClient客户端进程启动并发送请求数据,服务端接收到请求数据后,响应客户端(将请求的数据作为响应数据写回到客户端通道SocketChannel,并等待客户端处理)。
实际上,客户端和服务端可以采用同样轮询的非阻塞模式来实现,为简单实现在这个例子中我们把客户端角色简化了,而实际上它可能在另一个系统通信中充当服务端角色。
另外,上面对于不同事件是采用非线程的方式来处理,只是简单地调用处理的方法。在实际中,如果存在大量连接、读写请求,可以考虑使用线程池来更大程度地并发处理,提高服务端处理的速度和吞吐量,提升系统性能。