主頁 > 後端開發 > Java 網路編程 —— 創建非阻塞的 HTTP 服務器

Java 網路編程 —— 創建非阻塞的 HTTP 服務器

2023-05-29 07:45:07 後端開發

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 = https://www.cnblogs.com/Yee-Q/archive/2023/05/28/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 = https://www.cnblogs.com/Yee-Q/archive/2023/05/28/requestCharset.decode(temp).toString();
        if(data.indexOf("\r\n\r\n") != -1) {
            data = https://www.cnblogs.com/Yee-Q/archive/2023/05/28/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;
        }
    }
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553637.html

標籤:其他

上一篇:527訓練總結

下一篇:返回列表

標籤雲
其他(159865) Python(38178) JavaScript(25460) Java(18141) C(15232) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7214) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5343) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4577) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1977) 功能(1967) Web開發(1951) HtmlCss(1948) C++(1924) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1862) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Java 網路編程 —— 創建非阻塞的 HTTP 服務器

    ## HTTP 概述 HTTP 客戶程式必須先發出一個 HTTP 請求,然后才能接收到來自 HTTP 服器的回應,瀏覽器就是最常見的 HTTP 客戶程式。HTTP 客戶程式和 HTTP 服務器分別由不同的軟體開發商提供,它們都可以用任意的編程語言撰寫。HTTP 嚴格規定了 HTTP 請求和 HTTP ......

    uj5u.com 2023-05-29 07:45:07 more
  • 527訓練總結

    訓練內容:2023江西省賽VP 賽后總結: 比賽程序: 做了簽到以后純純開始坐牢...... 策略失誤: I題被定位成簽到題也過了十四個人,但是后續沒有花更多的時間去看,一直在鉆“如何存盤圖上路徑”的牛角尖,沒有往“存在巧妙解法”這個角度思考。另外寫dfs的假解法的程序中發現對vector的基本洗掉 ......

    uj5u.com 2023-05-29 07:45:03 more
  • FreeSWITCH添加自定義endpoint

    作業系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 日常開發程序中會遇到需要擴展FreeSWITCH對接其它系統的情況,這里記錄下撰寫FreeSWITCH自定義endpoint的程序。 一、模塊定義函式 使用FreeSWITCH自帶的框架來定義模塊函式,函式指標及引數 ......

    uj5u.com 2023-05-29 07:44:41 more
  • 【python基礎】基本資料型別-字串型別

    # 1.初識字串 字串就是一系列字符。在python中,用引號括起來文本內容的都是字串。 其語法格式為:‘文本內容’或者“文本內容” 我們發現其中的引號可以是單引號,也可以是雙引號。這樣的靈活性可以使我們進行引號之間的嵌套。 撰寫程式如下所示: ![image](https://img2023 ......

    uj5u.com 2023-05-29 07:44:15 more
  • Rust Web 全堆疊開發之自建TCP、HTTP Server

    # Rust Web 全堆疊開發之自建TCP、HTTP Server ## 課程簡介 ### 預備知識 - Rust 編程語言入門 - https://www.bilibili.com/video/BV1hp4y1k7SV ### 課程主要內容 - WebService - 服務器端Web App - ......

    uj5u.com 2023-05-29 07:43:58 more
  • Python 標準類別庫-因特網資料處理之Base64資料編碼

    該模塊提供將二進制資料編碼為可列印ASCII字符并將這種編碼解碼回二進制資料的功能。它為[RFC 3548](https://tools.ietf.org/html/rfc3548.html)中指定的編碼提供編碼和解碼功能。定義了Base16、Base32和Base64演算法,以及事實上的標準Asci ......

    uj5u.com 2023-05-29 07:43:47 more
  • 關于STL容器的簡單總結

    # 關于STL容器的簡單總結 ## 1、結構體中多載運算子的示例 ``` //結構體小于符號的多載 struct buf { int a,b; bool operator queuea; //定義 a.push(x); //壓入 a.pop(); //彈出 a.size(); //取大小 a.fro ......

    uj5u.com 2023-05-29 07:43:38 more
  • 樹莓派使用HC-SR04超聲波測距

    ### 超聲波模塊介紹 超聲波測距原理很簡單: 1、通過記錄發送超聲波的時間、記錄超聲波回傳的時間,回傳時間與發送時間相減得到超聲波的持續時間。 2、通過公式:(**超聲波持續時間** * **聲波速度**) / **2**就可以得出距離; ![image.png](https://img2023. ......

    uj5u.com 2023-05-29 07:43:26 more
  • Python 使用ConfigParser操作ini組態檔

    ini 組態檔格式如下 要求:ini 檔案必須是GBK編碼,如果是UTF-8編碼,python讀取組態檔會報錯。 # 這里是注釋內容 # [FY12361] #婦幼保健介面服務埠 serverIP=192.168.1.11 serverPort=8400 [SM] #國產SM加密服務埠 se ......

    uj5u.com 2023-05-29 07:42:39 more
  • Python asyncio之協程學習總結

    ## 實踐環境 Python 3.6.2 ## 什么是協程 **協程**(Coroutine)一種電腦程式組件,該程式組件通過允許暫停和恢復任務,為非搶占式多任務生成子程式。**協程**也可以簡單理解為協作的程式,通過協同多任務處理實作并發的函式的變種(一種可以支持中斷的函式)。 下面,我們通過日常 ......

    uj5u.com 2023-05-29 07:42:28 more