HTTP 概述

HTTP 客户程序必须先发出一个 HTTP 请求,然后才能接收到来自 HTTP 服器的响应,浏览器就是最常见的 HTTP 客户程序。HTTP 客户程序和 HTTP 服务器分别由不同的软件开发商提供,它们都可以用任意的编程语言编写。HTTP 严格规定了 HTTP 请求和 HTTP 响应的数据格式,只要 HTTP 服务器与客户程序都遵守 HTTP,就能彼此看得懂对方发送的消息

1. HTTP 请求格式

下面是一个 HTTP 请求的例子

POST /hello.jsp HTTP/1.1
Accept:image/gif, image/jpeg, */*
Referer: http://localhost/login.htm
Accept-Language: en,zh-cn;q=0.5
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 10.0)
Host: localhost
Content-Length:43
Connection: Keep-Alive
Cache-Control: no-cache

username=root&password=12346&submit=submit

HTTP 规定,HTTP 请求由三部分构成,分别是:

  • 请求方法、URI、HTTP 的版本

    • HTTP 请求的第一行包括请求方式、URI 和协议版本这三项内容,以空格分开:POST /hello.jsp HTTP/1.1
  • 请求头(Request Header)

    • 请求头包含许多有关客户端环境和请求正文的有用信息。例如,请求头可以声明浏览器的类型、所用的语言、请求正文的类型,以及请求正文的长度等

      Accept:image/gif, image/jpeg, */*
      Referer: http://localhost/login.htm
      Accept-Language: en,zh-cn;q=0.5		//浏览器所用的语言
      Content-Type: application/x-www-form-urlencoded		//正文类型
      Accept-Encoding: gzip, deflate
      User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 10.0)	//浏览器类型
      Host: localhost	 //远程主机
      Content-Length:43	//正文长度
      Connection: Keep-Alive
      Cache-Control: no-cache
      
  • 请求正文(Request Content)

    • HTTP 规定,请求头和请求正文之间必须以空行分割(即只有 CRLF 符号的行),这个空行非常重要,它表示请求头已经结束,接下来是请求正文,请求正文中可以包含客户以 POST 方式提交的表单数据

      username=root&password=12346&submit=submit
      

2. HTTP 响应格式

下面是一个 HTTP 响应的例子

HTTP/1.1 200 0K
Server: nio/1.1
Content-type: text/html; charset=GBK
Content-length:97
    
<html>
<head>
	<title>helloapp</title>
</head>
<body >
	<h1>hello</h1>
</body>
</htm1>

HTTP 响应也由三部分构成,分别是:

  • HTTP 的版本、状态代码、描述

    • HTTP 响应的第一行包括服务器使用的 HTTP 的版本、状态代码,以及对状态代码的描述,这三项内容之间以空格分割
  • 响应头 (Response Header)

    • 响应头也和请求头一样包含许多有用的信息,例如服务器类型、正文类型和正文长度等

      Server: nio/1.1		//服务器类型
      Content-type: text/html; charset=GBK	//正文类型
      Content-length:97	//正文长度
      
  • 响应正文(Response Content)

    • 响应正文就是服务器返回的具体的文档,最常见的是 HTML 网页。HTTP 响应头与响应正文之间也必须用空行分隔

      <html>
      <head>
      	<title>helloapp</title>
      </head>
      <body >
      	<h1>hello</h1>
      </body>
      </htm1>
      

创建阻塞的 HTTP 服务器

下例(SimpleHttpServer)创建了一个非常简单的 HTTP 服务器,它接收客户程序的 HTTP 请求,把它打印到控制台。然后对 HTTP 请求做简单的解析,如果客户程序请求访问 login.htm,就返回该网页,否则一律返回 hello.htm 网页。login.htm 和 hello.htm 文件位于 root 目录下

SimpleHttpServer 监听 80 端口,按照阻塞模式工作,采用线程池来处理每个客户请求

public class SimpleHttpServer {
    
    private int port = 80;
    private ServerSocketChannel serverSocketChannel = null;
    private ExecutorService executorService;
    private static final int POOL MULTIPLE = 4;
    private Charset charset = Charset.forName("GBK");
    
    public SimpleHttpServer() throws IOException {
        executorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL MULTIPLE);
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().setReuseAddress(true);
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        System.out.println("服务器启动");
    }
    
    public void service() {
        while (true) {
            SocketChannel socketChannel = null;
            try {
                socketChannel = serverSocketChannel.accept();
                executorService.execute(new Handler(socketChannel));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String args[])throws IOException {
        new SimpleHttpServer().service();
    }
    
    public String decode(ByteBuffer buffer) {......}	//解码
    
    public ByteBuffer encode(String str) {......}	//编码
    
    //Handler是内部类,负责处理HTTP请求
    class Handler implements Runnable {
        
        private SocketChannel socketChannel;
        
        public Handler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }
        
        public void run() {
            handle(socketChannel);
        }
        
        public void handle(SocketChannel socketChannel) {
            try {
                Socket socket = socketChannel.socket();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                
                //接收HTTP请求,假定其长度不超过1024字节
                socketChannel.read(buffer);
                buffer.flip();
                String request = decode(buffer);
                //打印HTTP请求
                System.out.print(request);
                
                //生成HTTP响应结果
                StringBuffer sb = new StringBuffer("HTTP/1.1 200 0K\r\n");
                sb.append("Content-Type:text/html\r\n\r\n");
                //发送HTTP响应的第1行和响应头
                socketChannel.write(encode(sb.toString()));
                
                FileInputStream in;
                //获得HTTP请求的第1行
                String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
                if(firstLineOfRequest.indexOf("login.htm") != -1) {
                    in = new FileInputStream("login.htm");
                } else {
                    in = new FileInputStream("hello.htm");
                }
                    
                FileChannel fileChannel = in.getChannel();
                //发送响应正文
                fileChannel.transferTo(0, fileChannel.size(), socketChannel);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if(socketChannel != null) {
                        //关闭连接
                        socketChannel.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

创建非阻塞的 HTTP 服务器

下面是本节所介绍的非阻塞的 HTTP 服务器范例的模型

  • HttpServer:服务器主程序,由它启动服务器
  • AcceptHandler:负责接收客户连接
  • RequestHandler:负责接收客户的 HTTP 请求,对其解析,然后生成相应的 HTTP 响应,再把它发送给客户
  • Request:表示 HTTP 请求
  • Response:表示 HTTP 响应
  • Content:表示 HTTP 响应的正文

1. 服务器主程序 HttpServer

HttpServer 仅启用了单个主线程,采用非阻塞模式来接收客户连接,以及收发数据

public class HttpServer {
    
    private Selector selector = null;
    private ServerSocketChannel serverSocketChannel = null;
    private int port = 80;
    private Charset charset = Charset.forName("GBK");
    
    public HttpServer() throws IOException {
        //创建Selector和ServerSocketChannel
        //把ServerSocketchannel设置为非阻塞模式,绑定到80端口
        ......
    }
    
    public void service() throws IOException {
        //注册接收连接就绪事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, new AcceptHandler());
        while(true) {
            int n = selector.select();
            if(n==0) continue;
            Set readyKeys = selector.selectedKeys();
            Iterator it = readyKeys.iterator();
			while(it.hasNext()) {
                SelectionKey key = null;
                try {
                    key = (SelectionKey) it.next();
                    it.remove();
                    final Handler handler = (Handler) key.attachment();
                    handler.handle(key); //由 Handler 处理相关事件
                } catch(IOException e) {
                    e.printStackTrace();
                    try {
                        if(key != null) {
                            key.cancel();
                            key.channel().close();
                        }
                    } catch(Exception ex) {
                        e.printStackTrace();
                    }
                }
            }            
        }
    }
    
    public static void main(String args[])throws Exception {
        final HttpServer server = new HttpServer();
        server.service();
    }
}

2. 具有自动增长的缓冲区的 ChannelIO 类

自定义的 ChannelIO 类对 SocketChannel 进行了包装,增加了自动增长缓冲区容量的功能。当调用 socketChannel.read(ByteBuffer bufer) 方法时,如果 buffer 已满,即使通道中还有未接收的数据,read 方法也不会读取任何数据,而是直接返回 0,表示读到了零字节

为了能读取通道中的所有数据,必须保证缓冲区的容量足够大。在 ChannelIO 类中有一个 requestBuffer 变量,它用来存放客户的 HTTP 请求数据,当 requestBuffer 剩余容量已经不足 5%,并且还有 HTTP 请求数据未接收时,ChannellO 会自动扩充 requestBuffer 的容量,该功能由 resizeRequestBuffer() 方法完成

public class ChannelIO {
    
    protected SocketChannel socketChannel;
    protected ByteBuffer requestBuffer; //存放请求数据
    private static int requestBufferSize = 4096;
    
    public ChannelIO(SocketChannel socketChannel, boolean blocking) throws IOException {
        this.socketChannel = socketChannel;
        socketChannel.configureBlocking(blocking); //设置模式
        requestBuffer = ByteBuffer.allocate(requestBufferSize);
    }
    
    public SocketChannel 
        () {
        return socketChannel;
    }
    
    /**
     * 如果原缓冲区的剩余容量不够,就创建一个新的缓冲区,容量为原来的两倍
     * 并把原来缓冲区的数据拷贝到新缓冲区
     */
    protected void resizeRequestBuffer(int remaining) {
        if (requestBuffer.remaining() < remaining) {
            ByteBuffer bb = ByteBuffer.allocate(requestBuffer.capacity() * 2);
            requestBuffer.flip();
            bb.put(requestBuffer); //把原来缓冲区中的数据拷贝到新的缓冲区
            requestBuffer = bb;
        }
    }
    
    /**
     * 接收数据,把它们存放到requestBuffer
     * 如果requestBuffer的剩余容量不足5%
     * 就通过resizeRequestBuffer()方法扩充容量
     */
    public int read() throws IOException {
        resizeRequestBuffer(requestBufferSize/20);
        return socketChannel.read(requestBuffer);
    }
    
    /** 返回requestBuffer,它存放了请求数据 */
    public ByteBuffer getReadBuf() {
        return requestBuffer;
    }
    
    /** 发送参数指定的 ByteBuffer 的数据 */
    public int write(ByteBuffer src) throws IOException {
        return socketChannel.write(src);
    }
    
    /** 把FileChannel的数据写到SocketChannel */
    public long transferTo(FileChannel fc, long pos, long len) throws IOException {
        return fc.transferTo(pos, len, socketChannel);
    }
    
    /** 关闭SocketChannel */
    public void close() throws IOException {
        socketChannel.close();
    }
}

3. 负责处理各种事件的 Handler 接口

Handler 接口负责处理各种事件,它的定义如下:

public interface Handler {
    public void handle(SelectionKey key) throws IOException;
}

Handler 接口有 AcceptHandler 和 RequestHandler 两个实现类。AcceptHandler 负责处理接收连接就绪事件,RequestHandler 负责处理读就绪和写就绪事件。更确切地说,RequestHandler 负责接收客户的 HTTP 请求,以及发送 HTTP 响应

4. 负责处理接收连接就绪事件的 AcceptHandler类

AcceptHandler 负责处理接收连接就绪事件,获得与客户连接的 SocketChannel,然后向 Selector 注册读就绪事件,并且创建了一个 RequestHandler,把它作为 SelectionKey 的附件。当读就绪事件发生时,将由这个 RequestHandler 来处理该事件

public class AcceptHandler implements Handler {
    
    public void handle(SelectionKey key) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        //在非阻塞模式下,serverSocketChannel.accept()有可能返回null
        SocketChannel socketChannel = serverSocketChannel.accept();
        if (socketChannel == null) return;
        //ChannelIO设置为采用非阻塞模式
        ChannelIO cio = new ChannelIO(socketChannel, false);
        RequestHandler rh = new RequestHandler(cio);
        //注册读就绪事件,把RequestHandler作为附件
        socketChannel.register(key.selector(), SelectionKey.OP_READ, rh);
    }
}

5. 负责接收 HTTP 请求和发送 HTTP 响应的 RequestHandler 类

RequestHandler 先通过 ChannelIO 来接收 HTTP 请求,当接收到 HTTP 请求的所有数据后,就对 HTTP 请求数据进行解析,创建相应的 Request 对象,然后依据客户的请求内容,创建相应的 Response 对象,最后发送 Response 对象中包含的 HTTP 响应数据。为了简化程序,RequestHandler 仅仅支持 GET 和 HEAD 两种请求方式

public class RequestHandler implements Handler {
    
    private ChannelIO channelIO;
    //存放HTTP请求的缓冲区
    private ByteBuffer requestByteBuffer = null;
    //表示是否已经接收到HTTP请求的所有数据
    private boolean requestReceived = false;
    //表示HTTP请求
    private Request request = null;
    //表示HTTP响应
    private Response response = null;
    
    RequestHandler(ChannelIO channelIO) {
        this.channelIO = channelIO;
    }
    
    /** 接收HTTP请求,发送HTTP响应 */
    public void handle(SelectionKey sk) throws IOException {
        try {
            //如果还没有接收HTTP请求的所有数据,就接收HTTP请求
            if (request == null) {
                if (!receive(sk)) return;
                requestByteBuffer.flip();
                //如果成功解析了HTTP请求,就创建一个Response对象
                if (parse()) build();
                try {
                    //准备HTTP响应的内容
                    response.prepare(); 
                } catch (IOException x) {
                    response.release();
                    response = new Response(Response.Code.NOT_FOUND, new StringContent(x.getMessage()));
                    response.prepare();
                }
                
                if (send()) {
                    //如果HTTP响应没有发送完毕,则需要注册写就绪事件,以便在写就绪事件发生时继续发送数据
                    sk.interestOps(SelectionKey.OP_WRITE);
                } else {
                    //如HTTP响应发送完毕,就断开底层连接,并且释放Response占用资源
                    channelIO.close();
                    response.release();
                }
            } else {
                //如果已经接收到HTTP请求的所有数据
                //如果HTTP响应发送完毕
                if (!send()) {
                    channelIO.close();
                    response.release();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            channelIO.close();
            if (response != null) {
                response.release();
            }
        }
    }
    
    /**
     * 接收HTTP请求,如果已经接收到了HTTP请求的所有数据,就返回true,否则返回false
     */
    private boolean receive(SelectionKey sk) throws IOException {
        ByteBuffer tmp = null;
        //如果已经接收到HTTP请求的所有数据,就返回true
        if (requestReceived) return true;
        //如果已经读到通道的末尾,或者已经读到HTTP请求数据的末尾标志,就返回true
        if ((channelIO.read() < 0) || Request.isComplete(channelIO.getReadBuf())) {
            requestByteBuffer = channelIO.getReadBuf();
            return (requestReceived = true);
        }
        return false;
    }
    
    /**
     * 通过Request类的parse()方法,解析requestByteBuffer的HTTP请求数据
     * 构造相应的Request对象
     */
    private boolean parse() throws IOException {
        try {
            request = Request.parse(requestByteBuffer);
            return true;
        } catch (MalformedRequestException x) {
            //如果HTTP请求的格式不正确,就发送错误信息
            response = new Response(Response.Code.BAD_REQUEST, new StringContent(x))
        }
        return false;
    }
    
    /** 创建HTTP响应 */
    private void build() throws IOException {
        Request.Action action = request.action();
        //仅仅支持GET和HEAD请求方式
        if ((action != Request.Action.GET) && (action != Request.Action.HEAD)) {
            response = new Response(Response.Code.METHOD_NOT_ALLOWED, new StringContent("Method Not Allowed"));
        } else {
            response = new Response(Response.Code.OK, new FileContent(request.uri()), action);
        }
    }
    
    /** 发送HTTP响应,如果全部发送完毕,就返回false,否则返回true */
    private boolean send() throws IOException {
        return response.send(channelIO);
    }
}

6. 代表 HTTP 请求的 Request 类

RequestHandler 通过 ChannelIO 读取 HTTP 请求数据时,这些数据被放在 requestByteBuffer 中。当 HTTP 请求的所有数据接收完毕,就要对 requestByteBufer 的数据进行解析,然后创建相应的 Request 对象。Request 对象就表示特定的 HTTP 请求

public class Request {
    
    //枚举类,表示HTTP请求方式
    static enum Action {
        GET,PUT,POST,HEAD;
    }
    
    public static Action parse(String s) {
        if (s.equals("GET"))
            return GET;
        if (s.equals("PUT"))
            return PUT;
        if (s.equals("POST"))
            return POST;
        if (s,equals("HEAD"))
            return HEAD;
        throw new IllegalArgumentException(s);
    }
    
    private Action action;	//请求方式
    private String version;	//HTTP版本
    private URI uri;		//URI
    
    public Action action() { return action; }
    public String version() { return version; }
    public URI uri() { return uri; }
    
    private Request(Action a, String V, URI u) {
        action = a;
        version = v;
        uri =u;
    }
    
    public String toString() {
        return (action + " " + version + " " + uri);
    }
    
    private static Charset requestCharset = Charset.forName("GBK");
    
    /**
     * 判断ByteBuffer是否包含HTTP请求的所有数据
     * HTTP请求以”r\n\r\n”结尾
     */
    public static boolean isComplete(ByteBuffer bb) {
        ByteBuffer temp = bb.asReadOnlyBuffer();
        temp.flip();
        String data = requestCharset.decode(temp).toString();
        if(data.indexOf("r\n\r\n") != -1) {
            return true;
        }
        return false;
    }
    
    /**
     * 删除请求正文
     */
    private static ByteBuffer deleteContent (ByteBuffer bb) {
        ByteBuffer temp = bb.asReadOnlyBuffer();
        String data = requestCharset.decode(temp).toString();
        if(data.indexOf("\r\n\r\n") != -1) {
            data = data.substrinq(0, data.indexOf("\r\n\r\n") + 4);
            return requestCharset.encode(data);
        }
        return bb;
    }
    
    /**
     * 设定用于解析HTTP请求的字符串匹配模式,对于以下形式的HTTP请求
     * GET /dir/file HTTP/1.1
     * Host: hostname
     * 将被解析成:
     * group[l] = "GET”
     * group[2]="/dir/file"
     * group[3]="1.1"
     * group[4]="hostname"
     */
    private static Pattern requestPattern =
        Pattern.compile("\\A([A-Z]+) +([^]+) +HTTP/([0-9\\.]+)$"
                        + ",*^Host:([]+)$.*\r\n\r\n\\z",
                        Pattern.MULTILINE | Pattern.DOTALL);
    
    /** 解析HTTP请求,创建相应的Request对象 */
    public static Request parse(ByteBuffer bb) throws MalformedRequestException {
        bb = deleteContent(bb); //删除请求正文
        CharBuffer cb = requestCharset.decode(bb); //解码
        Matcher m = requestPattern.matcher(cb); //进行字符串匹配
        //如果HTTP请求与指定的字符串式不匹配,说明请求数据不正确
        if (!m.matches())
            throw new MalformedRequestException();
        Action a;
        //获得请求方式
        try {
            a = Action.parse(m.group(1));
        } catch (IllegalArgumentException x) {
            throw new MalformedRequestException();
        }
        //获得URI
        URI u;
        try {
            u=new URI("http://" + m.group(4) + m.group(2));
        } catch (URISyntaxException x) {
            throw new MalformedRequestException();
        }
        //创建一个Request对象,并将其返回
        return new Request(a, m.group(3), u);
    }
}

7. 代表 HTTP 响应的 Response 类

Response 类表示 HTTP 响应,它有三个成员变量:code、headerBufer 和 content,它们分别表示 HTTP 响应中的状态代码、响应头和正文

public class Response implements Sendable {
    
    //枚举类,表示状态代码
    static enum Code {
        
        OK(200, "OK"),
        BAD_REQUEST(400, "Bad Request"),
        NOT_FOUND(404, "Not Found"),
        METHOD_NOT_ALLOWED(405, "Method Not Allowed");
        
        private int number;
        private String reason;
        
        private Code(int i, String r) {
            number = i;
            reason =r;
        }
        
        public String toString() {
            return number + " "  + reason;
        }
    }
    
    private Code code; //状态代码
    private Content content; //响应正文
    private boolean headersOnly; //表示HTTP响应中是否仅包含响应头
    private ByteBuffer headerBuffer = null; //响应头
    
    public Response(Code rc, Content c) {
        this(rc, c, null);
    }
    
    public Response(Code rc, Content c, Request.Action head) {
        code = rc;
        content = c;
        headersOnly = (head == Request.Action.HEAD);
    }
    
    /** 创建响应头的内容,把它存放到ByteBuffer */
    private ByteBuffer headers() {
        CharBuffer cb = CharBuffer.allocate(1024);
        while(true) {
            try {
                cb.put("HTTP/1.1").put(code.toString()).put(CRLF);
                cb.put("Server: nio/1.1").put(CRLF);
                cb.put("Content-type: ") .put(content.type()).put(CRIE);
                cb.put("Content-length: ").put(Long.toString(content.length())).put(CRLF);
                cb.put(CRLF);
                break;
            } catch (BufferOverflowException x) {
                assert(cb.capacity() < (1 << 16));
                cb = CharBuffer.allocate(cb.capacity() * 2);
                continue;
            }
        }
        cb.flip();
        return responseCharset.encode(cb); //编码
    }
    
    /** 准备 HTTP 响应中的正文以及响应头的内容 */
    public void prepare() throws IOException {
        content.prepare();
        headerBuffer= headers();
    }
    
    /** 发送HTTP响应,如果全部发送完毕,就返回false,否则返回true */
    public boolean send(ChannelIO cio) throws IOException {
        if (headerBuffer == null) {
            throw new IllegalStateException();
        }
        //发送响应头
        if (headerBuffer.hasRemaining()) {
            if (cio.write(headerBuffer) <= 0)
                return true;
        }
        //发送响应正文
        if (!headersOnly) {
            if (content.send(cio))
                return true;
        }
        return false;
    }
    
    /** 释放响应正文占用的资源 */
    public void release() throws IOException {
        content.release();
    }
}

8. 代表响应正文的 Content 接口及其实现类

Response 类有一个成员变量 content,表示响应正文,它被定义为 Content 类型

public interface Content extends Sendable {
    
    //正文的类型
    String type();
    
    //返回正文的长度
    //在正文准备之前,即调用prepare()方法之前,length()方法返回“-1”
    long length();
}

Content 接口继承了 Sendable 接口,Sendable 接口表示服务器端可发送给客户的内容

public interface Sendable {
    
    // 准备发送的内容
    public void prepare() throws IOException;
    
    // 利用通道发送部分内容,如果所有内容发送完毕,就返回false
	//如果还有内容未发送,就返回true
	//如果内容还没有准备好,就抛出 IlleqalstateException
	public boolean send(ChannelIO cio) throws IOException;
    
    //当服务器发送内容完毕,就调用此方法,释放内容占用的资源
    public void release() throws IOException;
}

Content 接口有 StringContent 和 FileContent 两个实现类,StringContent 表示字符串形式的正文,FileContent 表示文件形式的正文

FileContent 类有一个成员变量 fleChannel,它表示读文件的通道。FileContent 类的 send() 方法把 fileChannel 中的数据发送到 ChannelIO 的 SocketChannel 中,如果文件中的所有数据发送完毕,send() 方法就返回 false

public class FileContent implements Content {
    
    //假定文件的根目录为"root",该目录应该位于classpath下
    private static File ROOT = new File("root");
    private File file;
    
    public FileContent(URI uri) {
        file = new File(ROOT, uri.getPath().replace('/', File,separatorChar));
    }
    
    private String type = null;
    
    /** 确定文件类型 */
    public String type() {
        if (type != null) return type;
        String nm = file.getName();
        if (nm.endsWith(".html") || nm.endsWith(".htm"))
            type = "text/html; charset=iso-8859-1"; //HTML网页
        else if ((nm.indexOf('.') < 0) || nm.endsWith(".txt"))
            type = "text/plain; charset=iso-8859-1"; //文本文件
        else
            type = "application/octet-stream"; //应用程序
        return type;
    }
    
    private FileChannel fileChannel = null;
    private long length = -1; //文件长度
    private long position = -1;//文件的当前位置
    
    public long length() {
        return length;
    }
    
    /** 创建 FileChannel 对象 */
    public void prepare() throws IOException {
        if (fileChannel == null)
            fileChannel = new RandomAccessFile(file, "r").getChannel();
        length = fileChannel.size();
        position =0;
    }
    
    /** 发送正文,如果发送完毕,就返回 false,否则返回true */
    public boolean send(ChannelIO channelIO) throws IOException {
        if (fileChannel == null)
            throw new IllegalStateException();
        if (position < 0)
            throw new IllegalStateException();
        if (position >= length)
            return false; //如果发送完毕,就返回false
        position += channelIO,transferTo(fileChannel, position, length - position);
        return (position < length);
    }
    
    public void release() throws IOException {
        if (fileChannel != null) {
            fileChannel.close(); //关闭fileChannel
            fileChannel = null;
        }
    }
}

06-06 01:49