前几天用java NIO 编写了一个socket 服务器的例子,文章连接:http://blog.csdn.net/xidianliuy/article/details/51612676
但是,这个例子有点缺陷:
1)用户输入的文本长度有限制,最长为 BUFFER_SIZE 长度。
2)ByteBuffer[] processInput(ByteBuffer bf, String msg)方法不是异步的。如果这个方法执行很长时间,那么服务器在这段时间内就不能处理别的请求。
下面给出一个改进版本。
这个版本不仅解决了上面的2个问题,在用户等待server处理的时候,还向客户端回写处理进度。
程序涉及下面几个类:
NIOServer - SocketServer 主程序,启动 server并监听客户端连接,读、写操作。
Request - 用来封装 client 请求。
Response - 用来封装 server 的反馈。
RequestHandler - 根据 Request 生产 Response, 具体的业务操作,是在这个类的process()方法里执行的。这个类的process()方法是异步的,在处理请求时,把具体工作委派给了一个线程池。
先看主程序:NIOServer
package nio;
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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NIOServer implements Runnable {
private static final int SELECT_TIMEOUT = 60 * 5 * 1000;
private static final int INPUT_BUFF_SIZE = 1024;
private static final int THREAD_POOL_SIZE = 30;
private boolean stopped = false;
private int port = 8081;
private ExecutorService es = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
public NIOServer() {
}
public NIOServer(int port) {
this();
this.port = port;
}
@Override
public void run() {
try (ServerSocketChannel ssc = ServerSocketChannel.open()) {
ssc.bind(new InetSocketAddress(this.port));
ssc.configureBlocking(false);
Selector sel = Selector.open();
ssc.register(sel, SelectionKey.OP_ACCEPT);
while (!stopped) {
if (sel.select(SELECT_TIMEOUT) == 0) {
continue;
}
Iterator<SelectionKey> it = sel.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
try {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
SelectionKey k = client.register(sel, SelectionKey.OP_READ);
List<ByteBuffer> buffList = new ArrayList<>();
buffList.add(ByteBuffer.allocate(INPUT_BUFF_SIZE));
k.attach(buffList);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
@SuppressWarnings("unchecked")
List<ByteBuffer> buffList = (List<ByteBuffer>) key.attachment();
if (!buffList.get(buffList.size() - 1).hasRemaining()) {
buffList.add(ByteBuffer.allocate(INPUT_BUFF_SIZE));
}
ByteBuffer[] buffs = buffList.toArray(new ByteBuffer[1]);
long len = client.read(buffs);
if (isInputCompleted(buffs) || len == -1) {
Response res = new Response();
client.register(sel, SelectionKey.OP_WRITE, res);
RequestHandler handler = new RequestHandler(new Request(buffs), res, es);
handler.process();
}
} else if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
Response res = (Response) key.attachment();
ByteBuffer[] buffs = res.getData();
if (buffs.length > 0 && buffs[buffs.length - 1].hasRemaining()) {
client.write(buffs);
}
if (res.isCompleted() && (res.getData().length == 0 || (res.getData().length > 0
&& !res.getData()[res.getData().length - 1].hasRemaining()))) {
client.close();
}
}
} catch (Exception e) {
e.printStackTrace();
System.err.println("encounter error when process key:" + key.toString());
key.cancel();
}
}
}
} catch (
IOException e) {
e.printStackTrace();
}
}
private boolean isInputCompleted(ByteBuffer[] buffs) {
StringBuilder endSb = new StringBuilder();
outFor: for (int i = buffs.length - 1; i >= 0; i--) {
ByteBuffer buff = buffs[i];
for (int j = buff.position() - 1; j >= 0; j--) {
endSb.append((char) buff.get(j));
if (endSb.length() == 5) {
break outFor;
}
}
}
if (endSb.reverse().toString().equals("\r\n.\r\n") || endSb.toString().equals(".\r\n")) {
return true;
}
return false;
}
public void stop() {
this.stopped = true;
}
public static void main(String[] args) {
new Thread(new NIOServer()).start();
}
}
Request:
package nio;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class Request {
private static final Charset DEFALULT_CHARTSET = StandardCharsets.ISO_8859_1;
private String input;
public Request(ByteBuffer[] buffs) {
if (buffs.length > 0) {
StringBuilder sb = new StringBuilder();
for (ByteBuffer buff : buffs) {
buff.flip();
while (buff.hasRemaining()) {
sb.append((char) buff.get());
}
}
input = new String(sb.toString().getBytes(StandardCharsets.ISO_8859_1), DEFALULT_CHARTSET);
}
}
public String getInput() {
return this.input;
}
}
Response:
package nio;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class Response {
private List<ByteBuffer> bufferList = new ArrayList<>();
private int processRate = 0;
private boolean completed = false;
private static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
public Response(byte[] res) {
this.append(res);
this.setProcessRate(100);
this.setCompleted(true);
}
public Response() {
}
public synchronized void append(byte[] res) {
if (!this.isCompleted()){
bufferList.add(ByteBuffer.wrap(res));
}else {
throw new RuntimeException("response was completed.");
}
}
public synchronized void setProcessRate(int rate) {
this.processRate = rate;
}
public synchronized int getProcessRate() {
return this.processRate;
}
public synchronized boolean isCompleted() {
return completed;
}
public synchronized void setCompleted(boolean completed) {
this.completed = completed;
}
public synchronized ByteBuffer[] getData() {
if (bufferList.size() > 0) {
return bufferList.toArray(new ByteBuffer[0]);
}else {
return new ByteBuffer[0];
}
}
public synchronized void appendProcessInfo() {
this.append(("processed " + this.getProcessRate() + "%\r\n").getBytes(DEFAULT_CHARSET));
}
}
package nio;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
public class RequestHandler {
private String inputStr;
private ExecutorService threadPool;
private Response response;
public RequestHandler(Request request, Response res, ExecutorService es) {
this.inputStr = request.getInput();
this.response = res;
this.threadPool = es;
}
public void process() {
RequestHandler that = this;
threadPool.submit(new Callable<byte[]>() {
@Override
public byte[] call() throws Exception {
byte[] res = that.process(inputStr);
that.response.append(res);
that.response.setProcessRate(100);
that.response.appendProcessInfo();
that.response.setCompleted(true);
return res;
}
});
}
private byte[] process(String inputStr) {
StringBuilder sb = new StringBuilder();
sb.append("===================\r\n");
sb.append("Request:\r\n" + this.inputStr + "\r\n");
sb.append("Response:\r\n");
for (int i = 0; i < 10; i++) {
this.response.setProcessRate(i * 10);
this.response.appendProcessInfo();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb.append(Thread.currentThread().getName());
sb.append(": ").append(i).append(" : ");
sb.append("Hello World!\r\n");
}
sb.append("===================\r\n");
return sb.toString().getBytes();
}
}
运行结果:
1) 运行 NIOServer 这个类
2) 打开一个终端,运行命令 telnet localhost 8081, 随便输入一些文本,结束输入,使用一个新行 "."。
下面实在我机器上运行的结果:
localhost:~ dayong-mac$ telnet localhost 8081
Trying ::1...
Connected to localhost.
Escape character is '^]'.
aaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddddddddddddddddd
.
processed 0%
processed 10%
processed 20%
processed 30%
processed 40%
processed 50%
processed 60%
processed 70%
processed 80%
processed 90%
===================
Request:
aaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddddddddddddddddd
.
Response:
pool-1-thread-1: 0 : Hello World!
pool-1-thread-1: 1 : Hello World!
pool-1-thread-1: 2 : Hello World!
pool-1-thread-1: 3 : Hello World!
pool-1-thread-1: 4 : Hello World!
pool-1-thread-1: 5 : Hello World!
pool-1-thread-1: 6 : Hello World!
pool-1-thread-1: 7 : Hello World!
pool-1-thread-1: 8 : Hello World!
pool-1-thread-1: 9 : Hello World!
===================
processed 100%
Connection closed by foreign host.