基于nio的HTTP服务器

本贴最后更新于 4280 天前,其中的信息可能已经时移世异

这段时间学习了java的nio。

所以,自己练手,写了一个基于nio的java服务器。

可以说是一个简单的服务器吧。总共就是10多KB而已。

代码已经放到Github上。

目前只实现了基本的访问静态资源的功能。

 

1、基本原理

先上图:

dashuserver原理

其实原理很简单。

a、开启一个selector,然后不断用selector来select。selector.select()。可以获取发生的io时间。然后生成一个AnalyseHandle。扔到线程池。

b、AnalyseHandle会读取浏览器发过来的http请求信息,然后生成一个StaticHandle,扔到线程池。

c、StaticHandle根据http请求信息,读取文件内容,然后按照http协议,把内容返回。

整一个过程是比较简单的,同时每次请求,都会生成一个session对象,里面保存了请求的一些信息,每个handle里,都会有这个对象,这样子就能保证每个handle能获取这次请求的信息。

 

2、代码分析

dashuserver类图

随便画了个类图帮助大家理解。

Server类:

package me.idashu.server;

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.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**

  • 描述:服务端

  • @author: dashu

  • @since: 13-2-27
    */
    public class Server implements Runnable{

    private int port = 80;
    private Selector selector;

    public Server() {

    }

    @Override
    public void run() {

     try {
         selector = Selector.open();
    
         ServerSocketChannel ssc = ServerSocketChannel.open();
         ssc.configureBlocking(false);
         ssc.socket().bind(new InetSocketAddress(port));
         ssc.register(selector, SelectionKey.OP_ACCEPT);
    
         System.out.println("start server");
    
         while(true){
    
             int n = selector.select();
    
             Set<SelectionKey> set = selector.selectedKeys();
             Iterator<SelectionKey> it = set.iterator();
    
             while (it.hasNext()) {
    
                 SelectionKey key = it.next();
                 it.remove();
    
                 if (key.isAcceptable()) {
                     Session session = new Session(key);
                     System.out.println("access");
                 } else if (key.isReadable()) {
                     key.interestOps(0);
                     Session session = (Session) key.attachment();
                     AnalyseHandle analyseHandle = new AnalyseHandle(session);
                     ThreadPool.getInstance().execute(analyseHandle);
                 }
    
             }
    
         }
    
     } catch (IOException e) {
         e.printStackTrace();
     }
    

    }

    public static void main(String[] args){
    new Thread(new Server()).start();
    }
    }

这是程序的入口,在main函数里面,生成了一个Server类,然后运行里面的run方法。

在run方法里面,生成了一个Selector。然后一直循环调用selector.select()。

这方法可以获取一个key,首先一般都是先这样子if (key.isAcceptable()) {...}

如果这个事件是socket连接事件,就生成一个session。

session里面会根据key,获取一个ServerSocketChannel对象,然后在把对象绑定在selector。而且只关注read方法。

然后继续循环,让发生了else if (key.isReadable()) {...},就是这个key可以读的时候。

把刚才生成的session拿出来,生成一个AnalyseHandle对象,放到线程池。取消key关注的事件,key.interestOps(0)。

Handle抽象类:

package me.idashu.server;

/**

  • 描述:处理事务的类<br>

  • 实现了 Runnable 接口,抽象类,必须继承

  • @author: dashu

  • @since: 13-3-4
    */
    public abstract class Handle implements Runnable{

    protected Session session;

    public Handle(Session session) {
    this.session = session;
    }

}

这个抽象类,就是实现了Runnable接口,然后这个类里面有一个Session对象。

在我们服务器里面,每一个处理事务的线程,都得继承这个类。

ThreadPool线程池:

package me.idashu.server;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**

  • 描述:线程池<br>

  • 实现单例
    
  • @author: dashu

  • @since: 13-3-1
    */
    public class ThreadPool extends ThreadPoolExecutor{

    private static ThreadPool threadPool;

    private ThreadPool(){
    super(5,10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    }

    public synchronized static ThreadPool getInstance() {
    if (threadPool == null) {
    threadPool = new ThreadPool();
    }
    return threadPool;
    }

}

其实就是一个实现了单例的线程池。

Session类:

package me.idashu.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;

/**

  • 描述:会话
  • @author: dashu
  • @since: 13-2-27
    */

public class Session {

/**
 * 请求Key
 */
private SelectionKey key;
/**
 * 通道
 */
private SocketChannel channel;
/**
 * 应答类型
 */
private String mime;
/**
 * 请求路径
 */
private String requestPath;
/**
 * 请求方法
 */
private String method;
/**
 * 请求头信息
 */
private Map&lt;String, String&gt; heads = new HashMap&lt;String, String&gt;();

public Session(SelectionKey key) throws IOException {
    channel = ((ServerSocketChannel) key.channel()).accept();
    channel.configureBlocking(false);

    key = channel.register(key.selector(), SelectionKey.OP_READ, this);
    key.selector().wakeup();

    this.key = key;
}

/**
 * 放入头信息
 * @param key
 * @param value
 */
public void head(String key, String value){
    heads.put(key, value);
}

/**
 * 取出头信息
 * @param key
 * @return
 */
public String head(String key){
    return heads.get(key);
}

/**
 * 关闭通道
 */
public void close(){
    if (channel != null &amp;&amp; channel.isOpen())
        try {
            channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
}

public SelectionKey getKey() {
    return key;
}

public void setKey(SelectionKey key) {
    this.key = key;
}

public SocketChannel getChannel() {
    return channel;
}

public void setChannel(SocketChannel channel) {
    this.channel = channel;
}

public String getMime() {
    if (mime == null) {
        int index = requestPath.indexOf(&quot;.&quot;);
        String m = requestPath.substring(index+1);
        mime = Mime.get(m);
    }
    return mime;
}

public void setMime(String mime) {
    this.mime = mime;
}

public String getRequestPath() {
    return requestPath;
}

public void setRequestPath(String requestPath) {
    this.requestPath = requestPath;
}

public Map&lt;String, String&gt; getHeads() {
    return heads;
}

public void setHeads(Map&lt;String, String&gt; heads) {
    this.heads = heads;
}

public String getMethod() {
    return method;
}

public void setMethod(String method) {
    this.method = method;
}

}

每一次请求,都会生成一个session对象。然后session对象会传到handle里面。

handle里面就是我们处理事务的线程Runnable。

这样子每一次有关于请求的数据都被保存到session里面,或者从session里面读取。

AnalyseHandle类:

package me.idashu.server;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

/**

  • 描述:处理请求<br>

  • 判断请求类型
    
  • @author: dashu

  • @since: 13-3-4
    */
    public class AnalyseHandle extends Handle {

    public AnalyseHandle(Session session) {
    super(session);
    }

    @Override
    public void run() {
    SocketChannel sc = (SocketChannel) session.getKey().channel();
    session.setChannel(sc);
    // 请求信息
    StringBuffer headSB = new StringBuffer();
    ByteBuffer bb = ByteBuffer.allocate(1024);
    int count = 0;

     try {
         // 读取请求头信息
         while (true) {
             bb.clear();
             count = sc.read(bb);
             if (count == 0) {
                 break;
             }
             if (count &lt; 0) {
                 // 连接已经中断
                 session.close();
                 return;
             }
             bb.flip();
             byte[] data = new byte[count];
             bb.get(data, 0, count);
             headSB.append(new String(data));
         }
    

// System.out.println("head:\n"+headSB.toString().trim());
// 解析请求
StringTokenizer st = new StringTokenizer(headSB.toString(), Sysconst.CRLF);

        String line = st.nextToken();

        // 请求方法路径等
        StringTokenizer http = new StringTokenizer(line, &quot; &quot;);
        session.setMethod(http.nextToken()); // 方法

        String get = http.nextToken();
        int index = get.indexOf('?');
        if (index &gt; 0) {
            session.setRequestPath(get.substring(0, index)); // 路径
        } else {
            session.setRequestPath(get);
        }

        line = st.nextToken();

        while (line != null &amp;&amp; !line.equals(&quot;&quot;)) {
            int colon = line.indexOf(&quot;:&quot;);
            if (colon &gt; -1) {
                String key = line.substring(0, colon).toLowerCase();
                String value = line.substring(colon + 1).trim();
                session.head(key, value);
            }
            if (st.hasMoreElements()) {
                line = st.nextToken();
            } else {
                break;
            }
        }

        StaticHandle sh = new StaticHandle(session);
        ThreadPool.getInstance().execute(sh);

    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

刚才上面的时候已经提到过,这个类继承了Handle这个类,这个类在Server里面被new出来,放到线程池里。

其实这个类很简单,就是从session获取key,然后从key里获取channel。

得到channel,就可以读取浏览器传过来的http请求信息。

再进行适当的解析,保存会session里面。

在AnalyseHandle的后面,因为我们只处理静态文件,所以就直接生成了一个StaticHandle类,扔线程池里。

StaticHandle类:

package me.idashu.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

/**

  • 描述:

  • @author: dashu

  • @since: 13-3-4
    */
    public class StaticHandle extends Handle {

    /**

    • 静态路径
      */
      private String staticPath;

    public StaticHandle(Session session) {
    super(session);
    }

    @Override
    public void run() {

     // 获取channel
     SocketChannel channel = session.getChannel();
     if (!channel.isOpen())
         return;
    
     File file = new File(Sysconst.ROOTPATH, session.getRequestPath());
     FileChannel from = null;
     try {
         from = new FileInputStream(file).getChannel();
         ByteBuffer bb = ByteBuffer.allocate(1024);
         bb.clear();
    
         // 返回头信息
         StringBuffer sb = new StringBuffer();
         sb.append(&quot;HTTP/1.1 200 OK&quot;).append(Sysconst.CRLF);
         sb.append(&quot;Content-Type: &quot;).append(session.getMime()).append(Sysconst.CRLF);
         sb.append(&quot;Content-Length: &quot;).append(file.length()).append(Sysconst.CRLF);
         sb.append(Sysconst.CRLF);
         System.out.println(sb.toString());
         bb.put(sb.toString().getBytes());
         bb.flip();
         channel.write(bb);
    
         bb = from.map(FileChannel.MapMode.READ_ONLY, 0, from.size());
         channel.write(bb);
     } catch (FileNotFoundException e) {
         // 404
         ByteBuffer bb = ByteBuffer.allocate(1024);
         bb.clear();
         StringBuffer sb = new StringBuffer();
         sb.append(&quot;HTTP/1.1 404 Not Found&quot;).append(Sysconst.CRLF);
         sb.append(&quot;Content-Type: &quot;).append(&quot;text/html&quot;).append(Sysconst.CRLF);
         sb.append(Sysconst.CRLF);
         sb.append(&quot;404&quot;);
         bb.put(sb.toString().getBytes());
         bb.flip();
         try {
             channel.write(bb);
         } catch (IOException e1) {
             e1.printStackTrace();
         }
         sb.append(Sysconst.CRLF);
     } catch (IOException e) {
         e.printStackTrace();
     } finally {
         session.close();
     }
    

    }

    public String getStaticPath() {
    return staticPath;
    }

    public void setStaticPath(String staticPath) {
    this.staticPath = staticPath;
    }
    }

StaticHandle类其实就是一个处理静态文件,返回信息的类而已。

StaticHandle根据AnalyzeHandle类读取的http请求数据,找到对应的静态文件。

如果文件存在,就写回去。不存在,就返回404。

返回了信息之后,调用session.close()方法,关掉通道,整一个http请求结束。

 

其实这里还有几个其他的类的,Mime就是处理http返回类型的,Sysconst就是保存系统常量的。但是在这里就不贴代码了,有需要自己去Github上面去取吧。dashuserver

  • Web
    117 引用 • 433 回帖 • 8 关注
  • NIO
    15 引用 • 26 回帖 • 1 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
DASHU
大叔已经成为一个老油条了~~

推荐标签 标签

  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    177 引用 • 816 回帖
  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖 • 2 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 335 关注
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    93 引用 • 113 回帖 • 1 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    34 引用 • 148 回帖
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 76 关注
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 394 关注
  • IBM

    IBM(国际商业机器公司)或万国商业机器公司,简称 IBM(International Business Machines Corporation),总公司在纽约州阿蒙克市。1911 年托马斯·沃森创立于美国,是全球最大的信息技术和业务解决方案公司,拥有全球雇员 30 多万人,业务遍及 160 多个国家和地区。

    17 引用 • 53 回帖 • 141 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖 • 1 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    132 引用 • 1114 回帖 • 126 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖 • 2 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    83 引用 • 37 回帖
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 6 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 5 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    107 引用 • 295 回帖
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    238 引用 • 224 回帖
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 3 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 683 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖 • 1 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    545 引用 • 672 回帖
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    409 引用 • 1246 回帖 • 587 关注
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    52 引用 • 190 回帖 • 1 关注