1. 理解NIO原理:
NIO与传统I/O的区别:
1>能够设置套接字通道是否阻塞。
阻塞:一个线程处理IO的read()或者write()时该线程将会发生阻塞,直到数据读完或者写完才释放。
非阻塞:一个线程处理准备好了的数据,有数据到IO,线程不会立马去读取,而是等数据都完全准备好了,通知处理线程可以读或者写了。这里的通知是用selector监听
2>能够与selector结合使用实现并发请求
只有NIO能够使用selector监听IO,负责通知处理线程准备好的数据,通知的状态有:是否可连接,是否可接受,是否可读,是否可写
3>IO是面向流的,而NIO是面向缓冲
例如:读取数据(写也一样)
IO: socket.read(byte 或者 byte[])
NIO: socketChannel.read(ByteBuffer)NIO与IO的优势和劣势
注意:
1、纯粹的使用NIO处理数据,有时候并不高效。比如要明确知道按行写的文件,每一行的内容进行数据处理,传统IO可以按行读取获得数据,而NIO读取到缓冲,但并不知道读取了多少行,每一行是否是所需要的数据。
2、NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
3、少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合
详情:
2.NIO服务器实例解析
一个线程管理多个连接,如果是多线程,这样就可以管理大量的连接,处理数据比较耗时采用多线程处理
程序入口是: /HttpServerReactor/src/com/StartServer.java
访问1,浏览:http://localhost:8989/files/1.txt
访问2,下载:http://localhost:8989/files/2.zip
测试并发,可以先下载,再浏览。
实现效果:
http://localhost:8989/files/1.txt” title=”” />
http://localhost:8989/files/2.zip” title=”” />
3.实现代码
1.启动程序StartServer 。
package com;
import com.core.Reactor;
public class StartServer {
private static int port = 8989;
public static void main(String[] args) {
try {
new Reactor(port).run();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
2.反应器Reactor.java
package com.core;
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 java.util.Set;
/**
* 反应器模式 用于解决多用户访问并发问题
*
* 举个例子:餐厅服务问题
*
* 传统线程池做法:来一个客人(请求)去一个服务员(线程)
* 反应器模式做法:当客人点菜的时候,服务员就可以去招呼其他客人了,等客人点好了菜,直接招呼一声“服务员”
*
* @author zjx
*/
public class Reactor {
public final Selector selector;
public final ServerSocketChannel serverSocketChannel;
public Reactor(int port) throws IOException {
// 创建serverSocketChannel
serverSocketChannel = ServerSocketChannel.open();
// InetSocketAddress inetSocketAddress=new
// InetSocketAddress(InetAddress.getLocalHost(),port);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("服务器已经启动");
// 设置通道非阻塞
serverSocketChannel.configureBlocking(false);
// 创建selector
selector = Selector.open();
// 向selector注册该channel
SelectionKey acceptKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
acceptKey.attach(new Acceptor(this));
}
public void run() {
//DebugMethod d = new DebugMethod();
try {
while (!Thread.interrupted()) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//d.debug("all keys number: " + (selector.keys().size()));
/*for (SelectionKey key : selector.keys()) {
if(key.isValid())
d.printKeyInfo(key);
}*/
//d.debug("selected keys number: " + (selectionKeys.size()));
Iterator<SelectionKey> it = selectionKeys.iterator();
// Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。
while (it.hasNext()) {
// 来一个事件 第一次触发一个accepter线程
// 以后触发SocketReadHandler
SelectionKey selectionKey = it.next();
//d.printKeyInfo(selectionKey);
it.remove();
dispatch(selectionKey);
//d.printKeyInfo(selectionKey);
//selectionKeys.clear();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 运行Acceptor或SocketReadHandler
*
* @param key
*/
void dispatch(SelectionKey key) {
CommonMethod r = (CommonMethod) (key.attachment());
if (r != null) {
r.run();
}
}
}
3.接受连接accepter.java
package com.core;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Acceptor implements CommonMethod {
private static int acceptCount = 0;
private Reactor reactor;
public Acceptor(Reactor reactor){
this.reactor=reactor;
}
@Override
public void run() {
try {
SocketChannel socketChannel=reactor.serverSocketChannel.accept();
acceptCount++;
System.out.println("成功连接客户端!连接客户端数量acceptCount="+acceptCount);
ByteBuffer inputBuffer = ByteBuffer.allocate(1024);//一个socketChannel对应一个buf
//一个请求过来,新建一个变量,用于记录读取服务器文件的位置
if(socketChannel!=null && socketChannel.isOpen()) {//调用Handler来处理channel
new SocketReadHandler(reactor.selector, socketChannel, inputBuffer, acceptCount);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、读取socketchannel,SocketReadHandler.java
package com.core;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import com.core.header.Request;
public class SocketReadHandler implements CommonMethod {
private SocketChannel socketChannel;
private SelectionKey key;
private ByteBuffer inputBuffer;
private int acceptCount;
public SocketReadHandler(Selector selector, SocketChannel socketChannel, ByteBuffer inputBuffer, int acceptCount) throws IOException {
this.socketChannel = socketChannel;
this.inputBuffer = inputBuffer;
this.acceptCount = acceptCount;
socketChannel.configureBlocking(false);
SelectionKey selectionKey = socketChannel.register(selector, 0);
this.key = selectionKey;
// 将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。
// 参看dispatch(SelectionKey key)
selectionKey.attach(this);
// 同时将SelectionKey标记为可读,以便读取。
selectionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
}
/**
* 处理读取数据
*/
public void run() {
try {
// 读并解析请求
Request req = new Request(socketChannel, inputBuffer);
req.parseRequest();
String uri = req.getUri();
String web_root = System.getProperty("user.dir");
//获取服务器文件通道
RandomAccessFile raf = new RandomAccessFile(web_root+uri, "r");
//激活新线程读取服务器文件
//Map<String, Object> dealResult = new DoFile().readServerFile(uri);
Map<String, Object> dealResult = new HashMap<String, Object>();
dealResult.put("uri", uri);
dealResult.put("raf", raf);
dealResult.put("acceptCount", acceptCount);
new SocketWriteHandlel(dealResult, key);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5、写取sockechannel
package com.core;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Map;
import com.core.header.Response;
public class SocketWriteHandlel implements CommonMethod {
private SocketChannel sc;
private SelectionKey key;
private Selector selector;
private int acceptCount;
private RandomAccessFile raf;
private static int[] positions = new int[1000];//数组大小取决于并发的连接数
public SocketWriteHandlel(Map<String, Object> dealResult, SelectionKey key) throws ClosedChannelException {
this.key = key;
this.raf = (RandomAccessFile) dealResult.get("raf");
sc = (SocketChannel) key.channel();
selector = key.selector();
this.acceptCount = (Integer)dealResult.get("acceptCount");
// 重新绑定为写对象
key.attach(this);
// 同时将SelectionKey标记为可读,以便读取。
key.interestOps(SelectionKey.OP_WRITE);
selector.wakeup();
}
public void run() {
//System.out.println("acceptCount-1="+(acceptCount-1));
try {
//生成随机数
FileChannel fc = raf.getChannel();
Integer fileSize = (int) fc.size();
if (fileSize > 1024 * 1024 * 10) {// 只在文件大于10M且第一次写头信息
if (positions[acceptCount] == 0) {
// 响应信息
String responseCode = "200".trim();
String contentType = "application/octet-stream".trim();
String contenLen = Integer.toString(fileSize);
Response response = new Response(responseCode, contentType, contenLen);
String responseStr = response.getRespHeadInfo();
// 第一部分:响应头部信息
sc.write(ByteBuffer.wrap(responseStr.getBytes()));
}
// 第二部分
long readbytes = -1;
System.out.println("position111111111=" + positions[acceptCount]);
readbytes = fc.transferTo(positions[acceptCount], 128, sc);//1、定义128字节,为了下载慢一点
positions[acceptCount] += (int) readbytes;
System.out.println("position222222222=" + positions[acceptCount]);
} else {
long readbytes = -1;
readbytes = fc.transferTo(positions[acceptCount], 2048, sc);//2、定义2048为了浏览快一点,1和2这样写为了方便测试并发。
positions[acceptCount] += (int) readbytes;
System.out.println("**************position=" + positions[acceptCount]);
}
// 重新绑定为写对象
key.attach(this);
// 同时将SelectionKey标记为可读,以便读取。
key.interestOps(SelectionKey.OP_WRITE);
selector.wakeup();
// 关闭通道
if (positions[acceptCount] == fileSize) {
raf.close();
fc.close();
sc.close();
key.cancel();
}
} catch (Exception e) {
try {
sc.close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
读写接口:
package com.core;
public interface CommonMethod {
public void run();
}
6、解析头文件,拼接response文件Request.java Response.java
package com.core.header;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import com.core.util.DealStringOrByteBuffer;
public class Request {
private SocketChannel sc;
private String uri;
private ByteBuffer readbuf;
public Request(SocketChannel sc, ByteBuffer readbuf) {
this.sc = sc;
this.readbuf = readbuf;
}
public void parseRequest() {
StringBuffer strbuf = new StringBuffer();
int len = -1;
try {
while((len = sc.read(readbuf)) > 0) {
readbuf.flip();
String str = DealStringOrByteBuffer.getString(readbuf);
strbuf.append(str);
readbuf.clear();
}
if(len == -1) {
System.out.println("断开连接!");
}
} catch (Exception e) {
e.printStackTrace();
}
String requestStr = strbuf.toString();
System.out.println("requestStr=" + requestStr);
int firstSpace = requestStr.indexOf(" ");
if(firstSpace !=-1) {
int secondSpace = requestStr.indexOf(" ", firstSpace+1);
if(secondSpace > firstSpace) {
this.uri = requestStr.substring(firstSpace+1, secondSpace);//截取uri
}
}
}
public void printRequestInfo(ByteBuffer buf) {
System.out.println("requestStr="+DealStringOrByteBuffer.getString(buf));
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
}
package com.core.header;
package com.core.header;
/**
* 在这里拼装响应头信息,包括,响应HTTP包括:状态行、消息报头、响应正文。 这里处理状态行、消息报头 响应头说明:
*
* Location: http://www.it315.org/index.jsp(控制浏览器显示哪个页面)
*
* Server:apache tomcat(服务器的类型)
*
* Content-Encoding: gzip(服务器发送的压缩编码方式)
*
* Content-Length: 80(服务器发送显示的字节码长度)
*
* Content-Language: zh-cn(服务器发送内容的语言和国家名)
*
* Content-Type: image/jpeg; charset=UTF-8(服务器发送内容的类型和编码类型)
*
* Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务器最后一次修改的时间)
*
* Refresh: 1;url=http://www.it315.org(控制浏览器1秒钟后转发URL所指向的页面)
*
* Content-Disposition: attachment; filename=aaa.jpg(服务器控制浏览器发下载方式打开文件)
*
* Transfer-Encoding: chunked(服务器分块传递数据到客户端)
*
* Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务器发送Cookie相关的信息)
*
* Expires: -1(服务器控制浏览器不要缓存网页,默认是缓存)
*
* Cache-Control: no-cache(服务器控制浏览器不要缓存网页)
*
* Pragma: no-cache(服务器控制浏览器不要缓存网页)
*
* Connection: close/Keep-Alive(HTTP请求的版本的特点)
*
* Date: Tue, 11 Jul 2000 18:23:51 GMT(响应网站的时间)
*
* @author pactera
*
*/
public class Response {
// 状态行
private String proolVersion;
private String responseCode;
private String responseResult;
// 消息报文
private String location;
private String refresh;
private String contentType;
private String contenLen;
private String contentEncode;
private String contentDisposition;
private String transferEncoding;
// ...
public Response(String responseCode, String contentType, String contenLen) {
this.proolVersion = "HTTP/1.1";
this.responseCode = responseCode;
if (responseCode.equals("200")) {
this.responseResult = "OK";
}
this.contentType = contentType;
this.contenLen = contenLen;
}
// 简单的拼装
public String getRespHeadInfo() {
StringBuffer responseStr = new StringBuffer();
location = "http://localhost:8989/WebRoot/index.jsp";
refresh = "5;url=http://www.baidu.com";
// 拼装HTTP响应头信息
responseStr.append(proolVersion + " " + responseCode + " " + responseResult).append("\r\n");
// responseStr.append("Location: " + location).append("\r\n");
// responseStr.append("Refresh: " + refresh).append("\r\n");
// responseStr.append("Content-Disposition: attachment; filename=a.txt").append("\r\n");
responseStr.append("Content-Type: " + contentType).append("\r\n");
// responseStr.append("Content-Encoding: gzip").append("\r\n");
responseStr.append("Content-Length: " + contenLen).append("\r\n");
// responseStr.append("Connection: close/Keep-Alive").append("\r\n");
responseStr.append("\r\n");
System.out.println("respone: " + responseStr.toString());
return responseStr.toString();
}
public String getProolVersion() {
return proolVersion;
}
public void setProolVersion(String proolVersion) {
this.proolVersion = proolVersion;
}
public String getResponseCode() {
return responseCode;
}
public void setResponseCode(String responseCode) {
this.responseCode = responseCode;
}
public String getResponseResult() {
return responseResult;
}
public void setResponseResult(String responseResult) {
this.responseResult = responseResult;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getContenLen() {
return contenLen;
}
public void setContenLen(String contenLen) {
this.contenLen = contenLen;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getRefresh() {
return refresh;
}
public void setRefresh(String refresh) {
this.refresh = refresh;
}
public String getContentEncode() {
return contentEncode;
}
public void setContentEncode(String contentEncode) {
this.contentEncode = contentEncode;
}
public String getContentDisposition() {
return contentDisposition;
}
public void setContentDisposition(String contentDisposition) {
this.contentDisposition = contentDisposition;
}
public String getTransferEncoding() {
return transferEncoding;
}
public void setTransferEncoding(String transferEncoding) {
this.transferEncoding = transferEncoding;
}
}
7.自定义工具类
package com.core.util;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
public class DealStringOrByteBuffer {
public static String getString(ByteBuffer buffer) {
String requestStr = "";
try {
byte[] content = new byte[buffer.limit()];
buffer.get(content);
requestStr = new String(content, "GBK");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return requestStr;
}
}
package com.core.util;
import java.nio.channels.SelectionKey;
public class DebugMethod {
public void debug(String s) {
System.out.println(s);
}
public void printKeyInfo(SelectionKey sk) {
String s = new String();
s = "Att: " + (sk.attachment() == null ? "no" : "yes");
s += ", Read: " + sk.isReadable();
s += ", Acpt: " + sk.isAcceptable();
s += ", Cnct: " + sk.isConnectable();
s += ", Wrt: " + sk.isWritable();
s += ", Valid: " + sk.isValid();
s += ", Ops: " + sk.interestOps();
debug(s);
}
}
资源下载地址:
http://download.youkuaiyun.com/download/qq_23489303/9803918