TimeServer:
package yunsheng;
import java.io.IOException;
public class TimeServer {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int port = 9999;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
MultiTimeServer timeServer = new MultiTimeServer(port);
// 只需要一个线程,负责轮询多路复用器selector,就可以处理多个连接。
new Thread(timeServer, "multi-time-server-001").start();
}
}
MultiTimeServer:
package yunsheng;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class MultiTimeServer implements Runnable {
private Selector selector;
private ServerSocketChannel serverChannel;
private volatile boolean stop;
public MultiTimeServer(int port) {
try {
// 打开一个多路复用器selector
selector = Selector.open();
// 打开一个serverSocketChannel
serverChannel = ServerSocketChannel.open();
// 设置NIO模式
serverChannel.configureBlocking(false);
// 绑定端口(1024是最大连接队列长度)
serverChannel.socket().bind(new InetSocketAddress(port), 1024);
// 将serverChannel注册到selector上,监听accept事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("server start at " + port);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
this.stop = true;
}
@Override
public void run() {
while (!stop) {
try {
// 设置超时。休眠时间,selector每隔一秒被唤醒一次
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
SelectionKey key = null;
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
try {
handleInput(key);
} catch (Exception e) {
e.printStackTrace();
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
// selector关闭后,注册其上的channel和pipe等资源会自动关闭,所以不需要重复释放资源
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
// 来了新的请求
if (key.isAcceptable()) {
// 这块挺像BIO的
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// serverSocketChannel接受请求,并创建socketChannel
// 这步完成,相当于完成tcp三次握手,物理链路建立完成。
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
}
// 可读的请求
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();// 翻转,从头开始读
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("the server receive :" + body);
String currentTime = "QUERY".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis())
.toString() : "BAD ORDER";
doWrite(sc, currentTime);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else {
// 读到0字节,忽略
}
}
}
}
private void doWrite(SocketChannel sc, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer bb = ByteBuffer.allocate(bytes.length);
bb.put(bytes);
bb.flip();
sc.write(bb);
}
}
}
TimeClient:
package yunsheng;
public class TimeClient {
public static void main(String[] args) {
int port = 9999;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new Thread(new TimeClientHandler(null, port)).start();
}
}
TimeClientHandler:
package yunsheng;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class TimeClientHandler implements Runnable {
private String host;
private int port;
private SocketChannel channel;
private Selector selector;
private volatile boolean stop;
public void stop() {
this.stop = true;
}
public TimeClientHandler(String host, int port) {
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
try {
selector = Selector.open();
channel = SocketChannel.open();
channel.configureBlocking(false);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
// 先连接
try {
doConnect();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
handle(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
// 当selector关闭后,注册其上的资源会自动关闭
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void handle(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc);
} else {
System.exit(1);// 连接失败,进程退出
}
}
// 是可读状态,读取响应
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("now is " + body);
stop();
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
}
}
}
}
private void doConnect() throws IOException {
if (channel.connect(new InetSocketAddress(host, port))) {
channel.register(selector, SelectionKey.OP_READ);
doWrite(channel);
} else {
channel.register(selector, SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel channel) throws IOException {
byte[] req = "QUERY".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
channel.write(writeBuffer);
if (!writeBuffer.hasRemaining()) {
System.out.println("send to server done!");
}
}
}
总结:
从代码上可以看出,采用NIO编程,复杂性更高。
但是,
1,客户端发起连接是异步的。当没有连接成功时,可以先注册 SelectionKey.OP_CONNECT状态。不会像之前那样阻塞。
2,SocketChannel的读写操作都是异步的。无内容可读写时,不会阻塞。
3,线程模型的优化。JDK的selector在linux上采用epoll实现,没有连接句柄的限制,一个线程可以处理成千上万的连接。而性能不会降低。因此非常适合做高性能的网络服务器。