JAVA NIO 编写 Scoket 服务改进版


前几天用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));
	}

}


RequestHandler:


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.













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值