学习了Java Nio,分析一个基于Java NIo 的Http服务器。
主要的三个类是NioHttpServer,RequestHander,ResponceHeaderBuilder;
Http服务器为了并发,常采用线程技术来处理高并发。但是Java NIO用基于事件的处理机制来避免创建过多的线程来占用服务器资源;
NioHttpServer单独一个线程,用来开启服务器,等待请求。处理方法:该类包含一个RequestHander引用列表,当NioHttpServer接到
请求后,会调用引用列表中的请求处理器处理请求。
RequestHander为一个线程,用来处理请求。这里,程序根据cpu的个数来创建相等个数的RequestHander线程,来了请求,就取一个处理线程来处理数据;
ResponceHeaderBuilder :回应(Response)数据构造器,用来构造http会发的头数据。
NioHttpServer:
public class NioHttpServer implements Runnable {
private static Logger logger = Logger.getLogger(NioHttpServer.class);
public static void main(String[] args) throws IOException {
//得到当前路径的相对路径
String root = new File(".").getAbsolutePath();
//端口8080
int port = 8080;
//如果有输入参数,第1个参数是端口,第二个参数为路径
if (args.length > 0)
port = Integer.parseInt(args[0]);
if (args.length > 1)
root = args[1];
//打出日志
logger.info("listenning at *." + port + "; root: " + root);
//创建NioHttpServer
NioHttpServer server = new NioHttpServer(null, port);
//得到java虚拟机可用cpu的数量
int cpu = Runtime.getRuntime().availableProcessors();
//缓存,有待考究
ButterflySoftCache cache = new ButterflySoftCache();
// int i = 0;
for (int i = 0; i < cpu; ++i) {
//遍历cpu,一个cpu一个线程请求处理器
RequestHandler handler = new RequestHandler(server, root, cache);
server.addRequestHanlder(handler);
new Thread(handler, "worker" + i).start();
}
new Thread(server, "selector").start();
}
private ServerSocketChannel serverChannel;
private Selector selector;
private ByteBuffer readBuffer = ByteBuffer.allocate(8912);
private List<ChangeRequest> changeRequests = new LinkedList<ChangeRequest>();
private Map<SocketChannel, List<ByteBuffer>> pendingSent = new HashMap<SocketChannel, List<ByteBuffer>>();
private List<RequestHandler> requestHandlers = new ArrayList<RequestHandler>();
public NioHttpServer(InetAddress address, int port) throws IOException {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(address, port));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
private void accept(SelectionKey key) throws IOException {
SocketChannel socketChannel = serverChannel.accept();
// logger.info("new connection:\t" + socketChannel);
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
public void addRequestHanlder(RequestHandler handler) {
requestHandlers.add(handler);
}
private void read(SelectionKey key) throws IOException {
//读操作可以读取客户端发来的数据。此时通过传入的key获取可以执行读操作的客户端socket通道句柄
SocketChannel socketChannel = (SocketChannel) key.channel();
//清空缓冲区
readBuffer.clear();
int numRead;
try {
//执行度操作,尝试吧客户端发来的数据读到缓冲区中
numRead = socketChannel.read(readBuffer);
} catch (IOException e) {
// the remote forcibly closed the connection
//发生异常的话,key取消,客户端socket关闭
key.cancel();
socketChannel.close();
// logger.info("closed by exception" + socketChannel);
return;
}
//如果读完了
if (numRead == -1) {
// remote entity shut the socket down cleanly.
socketChannel.close();
key.cancel();
// logger.info("closed by shutdown" + socketChannel);
return;
}
int worker = socketChannel.hashCode() % requestHandlers.size();
if (logger.isDebugEnabled()) {
logger.debug(selector.keys().size() + "\t" + worker + "\t"
+ socketChannel);
}
requestHandlers.get(worker).processData(socketChannel,
readBuffer.array(), numRead);
}
public void run() {
SelectionKey key = null;
while (true) {
try {
synchronized (changeRequests) {
for (ChangeRequest request : changeRequests) {
switch (request.type) {
case ChangeRequest.CHANGEOPS:
key = request.socket.keyFor(selector);
if (key != null && key.isValid()) {
key.interestOps(request.ops);
}
break;
}
}
changeRequests.clear();
}
selector.select();
Iterator<SelectionKey> selectedKeys = selector.selectedKeys()
.iterator();
while (selectedKeys.hasNext()) {
key = selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
accept(key);
} else if (key.isReadable()) {
read(key);
} else if (key.isWritable()) {
write(key);
}
}
} catch (Exception e) {
if (key != null) {
key.cancel();
Util.closeQuietly(key.channel());
}
logger.error("closed" + key.channel(), e);
}
}
}
public void send(SocketChannel socket, byte[] data) {
synchronized (changeRequests) {
changeRequests.add(new ChangeRequest(socket,
ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));
synchronized (pendingSent) {
List<ByteBuffer> queue = pendingSent.get(socket);
if (queue == null) {
queue = new ArrayList<ByteBuffer>();
pendingSent.put(socket, queue);
}
queue.add(ByteBuffer.wrap(data));
}
}
selector.wakeup();
}
private void write(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
synchronized (pendingSent) {
List<ByteBuffer> queue = pendingSent.get(socketChannel);
while (!queue.isEmpty()) {
ByteBuffer buf = queue.get(0);
socketChannel.write(buf);
// have more to send
if (buf.remaining() > 0) {
break;
}
queue.remove(0);
}
if (queue.isEmpty()) {
key.interestOps(SelectionKey.OP_READ);
}
}
}
}
Requesthander:
public class RequestHandler implements Runnable {
private static final DateFormat formater = new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
static {
formater.setTimeZone(TimeZone.getTimeZone("GMT"));
}
private static final Logger logger = Logger.getLogger(RequestHandler.class);
private ButterflySoftCache cache;
private File currentFile;
private Date lastModified;
private List<RequestSegmentHeader> pendingRequestSegment = new ArrayList<RequestSegmentHeader>();
private Map<SocketChannel, RequestHeaderDecoder> requestMap = new WeakHashMap<SocketChannel, RequestHeaderDecoder>();
private NioHttpServer server;
private String serverRoot;
private String acceptEncoding;
/**
*
* @param server
* {@link NioHttpServer} the server
* @param wwwroot
* wwwroot
* @param cache
* cache implementation
*/
public RequestHandler(NioHttpServer server, String wwwroot,
ButterflySoftCache cache) {
this.cache = cache;
this.serverRoot = wwwroot;
this.server = server;
}
//处理客户端发来的请求数据,把数据放到list列表中,因为一直有RequestHander线程开着,所以会处理这个数据
public void processData(SocketChannel client, byte[] data, int count) {
byte[] dataCopy = new byte[count];
System.arraycopy(data, 0, dataCopy, 0, count);
/* //打出请求数据
String a=new String(dataCopy);
System.out.println(a);*/
synchronized (pendingRequestSegment) {
// add data
pendingRequestSegment
.add(new RequestSegmentHeader(client, dataCopy));
pendingRequestSegment.notify();
}
}
public void run() {
RequestSegmentHeader requestData = null;
RequestHeaderDecoder header = null;
ResponceHeaderBuilder builder = new ResponceHeaderBuilder();
byte[] head = null;
byte[] body = null;
String file = null;
String mime = null;
boolean zip = false;
// wait for data
while (true) {
//不停循环从pendingRequestSegment列表中取数据,如果有数据,就处理,没有就等待
synchronized (pendingRequestSegment) {
while (pendingRequestSegment.isEmpty()) {
try {
pendingRequestSegment.wait();
} catch (InterruptedException e) {
}
}
requestData = pendingRequestSegment.remove(0);
}
//取出请求数据(http服务器 为请求头)
header = requestMap.get(requestData.client);
if (header == null) {
header = new RequestHeaderDecoder();
requestMap.put(requestData.client, header);
}
try {
if (header.appendSegment(requestData.data)) {
file = serverRoot + header.getResouce();
currentFile = new File(file);
mime = Util.getContentType(currentFile);
// logger.info(currentFile + "\t" + mime);
acceptEncoding = header.getHeader(ACCEPT_ENCODING);
// gzip text 判断是否gzip压缩
zip = mime.contains("text")
&& acceptEncoding != null
&& (acceptEncoding.contains("gzip") || acceptEncoding
.contains("gzip"));
builder.clear(); // get ready for next request;
//构造回应数据
// always keep alive
builder.addHeader(CONNECTION, KEEP_ALIVE);
builder.addHeader(CONTENT_TYPE, mime);
// response body byte, exception throws here
body = Util.file2ByteArray(currentFile, zip);
builder.addHeader(CONTENT_LENGTH, body.length);
if (zip) {
// add zip header
builder.addHeader(CONTENT_ENCODING, GZIP);
}
// last modified header
lastModified = new Date(currentFile.lastModified());
builder.addHeader(LAST_MODIFIED,
formater.format(lastModified));
// response header byte
head = builder.getHeader();
// data is prepared, send out to the client
server.send(requestData.client, head);
if (body != null && header.getVerb() == Verb.GET)
server.send(requestData.client, body);
logger.info(header.getResouce() + " 200");
}
} catch (IOException e) {
builder.addHeader(CONTENT_LENGTH, 0);
builder.setStatus(NOT_FOUND_404);
head = builder.getHeader();
server.send(requestData.client, head);
// cache 404 if case client make a mistake again
cache.put(file, head, body);
logger.error(header.getResouce() + " 404");
} catch (Exception e) {
// any other, it's a 505 error
builder.addHeader(CONTENT_LENGTH, 0);
builder.setStatus(SERVER_ERROR_500);
head = builder.getHeader();
server.send(requestData.client, head);
logger.error("505 error", e);
}
}
}
}
//封装了一个socketChannel通道,一个字节数组
class RequestSegmentHeader {
SocketChannel client;
byte[] data;
public RequestSegmentHeader(SocketChannel client, byte[] data) {
this.client = client;
this.data = data;
}
}
ResponceHeaderBuilder:
public class ResponceHeaderBuilder {
public static final String OK_200 = "HTTP/1.1 200 OK";
public static final String NEWLINE = "\r\n";
public static final String NOT_FOUND_404 = "HTTP/1.1 404 Not Find";
public static final String SERVER_ERROR_500 = "HTTP/1.1 500 Internal Server Error";
public static final String CONTENT_TYPE = "Content-Type";
public static final String CONNECTION = "Connection";
public static final String CONTENT_LENGTH = "Content-Length";
public static final String KEEP_ALIVE = "keep-alive";
public static final String CONTENT_ENCODING = "Content-Encoding";
public static final String ACCEPT_ENCODING = "Accept-Encoding";
public static final String LAST_MODIFIED = "Last-Modified";
public static final String GZIP = "gzip";
private String status;
private Map<String, Object> header = new TreeMap<String, Object>();
/**
* status default to 200
*/
public ResponceHeaderBuilder() {
status = OK_200;
}
public ResponceHeaderBuilder addHeader(String key, Object value) {
header.put(key, value);
return this;
}
public void clear() {
status = OK_200;
header.clear();
}
public byte[] getHeader() {
return toString().getBytes();
}
public ResponceHeaderBuilder setStatus(String status) {
this.status = status;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(120);
sb.append(status).append(NEWLINE);
Set<String> keySet = header.keySet();
for (String key : keySet) {
sb.append(key).append(": ").append(header.get(key)).append(NEWLINE);
}
sb.append(NEWLINE); // empty line;
return sb.toString();
}
}