在第一篇中我们构造了http消息的请求和响应结构,进行了请求报文的解析,响应报文的发送,就像一个人先有了四肢,这一节我们来构造最重要的部分,就是躯干。先来看设计图,再上代码,最后总结。

首先来看设计图:

先看一个自定义的XML文件,文件中定义了这个工程中需要用到的组件,这些组件可以定义在xml文件中,也可以定义在数据库中,也可以定义在属性文件中。就像绝大多数的JAVA框架发展史一样,现在大部分组件的定义都是用Annotation实现,由容器自动扫描加载了,具体用哪种方式定义,没有好坏的分别,在哪种场景下最合适就用哪种。

<?xml version="1.0" encoding="UTF-8"?>
<common-server>

    <business-servlet project="googleServer" desc="business server...10.0.0.1">
        <servlet name="GoogleServlet" class="nioHttpServer.GoogleServlet"/>
        <servlet name="MicroSoftServlet" class="nioHttpServer.MicroSoftServlet"/>
        <!-- ....more business servlets here....-->
    </business-servlet>

    <!-- ThreadPool,Class name and invoked method name,multiple.cpu * count -->
    <threadPools>
        <pool port="8081" timeout="10000" method="process"
              trcount="100">
            nioHttpServer.CommonProcess
        </pool>

        <!-- example one begin-->
        <pool port="8082" timeout="10000" method="process"
              trcount="100">
            nioHttpServer.GoogleProcess
        </pool>

        <!-- example two begin-->
        <pool port="8083" timeout="10000" method="process"
              trcount="100">
            nioHttpServer.MicroSoftProcess
        </pool>
    </threadPools>

    <!-- business action class and method-->
    <services>
        <service method="personVerify" class="nioHttpServer.UserAction"
                 desc="custom verify..."/>

        <!-- example one begin-->
        <service method="loginAction" class="nioHttpServer.UserAction"
                 desc="custom login..."/>

        <!-- example two begin-->
        <service method="register" class="nioHttpServer.UserAction"
                 desc="custom register..."/>
    </services>

</common-server>

大概解释一下这个配置文件

第一部分是自定义Servlet,也就是这个工程的业务主调用类,一个业务模块可以定义一个servlet,属性project就可以用作是HTTP请求url中的第一个关键字。

第二部分是线程池,包括主线程类,线程池的属性信息,这里遵循Tomcat的设计原则,将servlet打入线程池执行,实现单Servlet多线程的模式。

第三部分是具体的业务Action定义,就是最具体的业务处理类,和业务处理方法。

代码部分,先来看第一个,Servlet的预加载类

package nioHttpServer;

import javax.servlet.http.HttpServlet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ServletContext {

    private static ServletContext context;
    public Map<String, Map<String, HttpServlet>> projectMap = new HashMap<>();

    private ServletContext() {
        init();
    }

    public static ServletContext getContext() {
        if (context == null) {
            context = new ServletContext();
        }
        return ServletContext.context;
    }

    public void init() {
        try {
            Iterator it = BusinessServer.SERVER.servlertMap.keySet().iterator();

            while (it.hasNext()) {

                String servName = it.next().toString();
                String servClass = BusinessServer.SERVER.servlertMap.get(servName);
                Map<String, HttpServlet> map = new HashMap<>();
                Class<?> servletClass = Class.forName(servClass);
                HttpServlet newInstance = (HttpServlet) servletClass.getConstructor(new Class[]{}).newInstance(new Object[]{});
                map.put(servName, newInstance);
                projectMap.put(BusinessServer.SERVER.projectName, map);

                System.out.println("Servlet loaded successful:NAME=["+servName+"],[CLASS:"+servClass+"]");
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Servlet loaded Exception");
        }
    }

    public Map<String, HttpServlet> getProject(String projectName) {
        return projectMap.get(projectName);
    }
}

在这个类中,我会将系统启动时加载到内存中的XML配置文件拿出来,检索里面的Servlet信息,并依次实例化后存放在一个Map中等待后续使用,由于Servlet和Project名都已经在项目中配置,所以对非法链接和请求的屏蔽很容易做到。

Servlet有了,下一步就是执行这个Serlvet所需的线程

package nioHttpServer;

import nioHttpServer.niosrv.NioHttpPrtWriter;
import nioHttpServer.niosrv.NioHttpRequest;
import nioHttpServer.niosrv.NioHttpResponse;

import javax.servlet.http.HttpServlet;
import java.util.Map;
import java.util.UUID;

public class CommonProcess implements Runnable {

    private HttpServlet servlet = null;
    private NioHttpRequest request;
    private NioHttpResponse response;

    public CommonProcess(NioHttpRequest request, NioHttpResponse response) {
        this.request = request;
        this.response = response;
    }

    public void process() {
        boolean flag = false;
        try {
            flag = request.parseHead(BusinessServer.SERVER.projectName);
        } catch (Exception e) {
            try {
                NioHttpPrtWriter writer = (NioHttpPrtWriter) response.getWriter(0);
                writer.writeAsString("Error During Read Head From Http Request!");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
            System.out.println("HTTP Request read error,error HEAD=["+request.getNioHead()+"]");
        }
        if (flag) {
            String project = request.getContextPath();
            String servlet = request.getServletPath();

            ServletContext container = BusinessServer.SERVER.servletContext;
            Map<String, HttpServlet> servletMap = container.getProject(project);
            if (null != servlet && !("").equals(servlet)) {
                this.servlet = servletMap.get(servlet);
            } else {
                this.servlet = servletMap.get(BusinessServer.SERVER.defaultName);
            }

            if (this.servlet != null) {
                try {
                    BusinessServer.SERVER.submitServiceProcess(this);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("Thread pool run error,Thread Name = [" + this.getClass().getSimpleName() + "],Exception=" + e.toString());
                }
            } else {
                try {
                    NioHttpPrtWriter writer = (NioHttpPrtWriter) response.getWriter(0);
                    writer.writeAsString("Error Http Request Servlet Head! ServletName does not exist!");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("Socket closed normally,ERROR HEAD=["+request.getNioHead()+"]");
            }
        } else {
            try {
                NioHttpPrtWriter writer = (NioHttpPrtWriter) response.getWriter(0);
                writer.writeAsString("Error Http Request Head!");
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("Socket closed normally,ERROR HEAD=["+request.getNioHead()+"]");
        }
    }

    public void run() {
        //Thread in thread pool,UUID to flag it as a unique thread
        UUID uuid = UUID.randomUUID();
        try {
            System.out.println("[BEGIN] THREAD [" + uuid.toString() + "],THREAD NAME=["+Thread.currentThread().getName()+"]");
            long start =System.currentTimeMillis();
            this.servlet.service(request, response);
            long cost= System.currentTimeMillis()-start;
            System.out.println("[END] THREAD [" + uuid.toString() + "],"+cost+"] mil seconds,THREAD NAME=["+Thread.currentThread().getName()+"]");
        } catch (Exception e) {
            e.printStackTrace();
            try {
                NioHttpPrtWriter writer = (NioHttpPrtWriter) response.getWriter(0);
                writer.writeAsString("Exception Happened During The Process");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println("EXCEPTION Thread[" + uuid.toString() + "] closed");
        }
    }
}

这是主线程类,主线程类可以配置多个,系统收到请求后,会调用这个类中的process方法,process方法会根据Http请求得到对应的Servlet信息,然后去Servlet上下文中获取合法的Servlet实例,如果找到匹配的Servlet,则将自己放入已经加载好的线程池中,并执行获取到的这个Servlet实例的Service方法。如果找不到匹配的Servlet实例,则返回错误信息。

Servlet有了,执行主线程也有了,现在写一个服务,启动监听端口吧

package nioHttpServer;

import nioHttpServer.niosrv.NioHttpRequest;
import nioHttpServer.niosrv.NioHttpResponse;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;

public class ComNioServer {
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private String processClazz;
    private String processMethod;
    private Thread listenThread;

    public void nioServerInit(int port, int timeOut, String processClazz, String processMethod) throws Exception {
        this.processClazz = processClazz;
        this.processMethod = processMethod;
        try {
            synchronized (Selector.class) {
                selector = Selector.open();
            }
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            ServerSocket socket = serverSocketChannel.socket();
            socket.setSoTimeout(timeOut);
            socket.setReuseAddress(true);
            socket.bind(new InetSocketAddress(port));

            System.out.println("Server Startup Success:PROCESS CLASS=["+processClazz+"];PORT=["+port+"];");

            Thread listenThread = new Thread(new HandleTheSocketChannel());
            listenThread.start();

            System.out.println("Server listener Thread:THREAD ID=["+listenThread.getId()+"]**********Server Startup Successful**********");

        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Server listener Thread Startup **********Server Startup Fail!**********");
        }
    }

    class HandleTheSocketChannel implements Runnable {
        @Override
        public void run() {
            try {
                startListen();
            } catch (Exception x) {
                x.printStackTrace();
                System.out.println( "Unable to run replication listener.");
            }
        }
    }

    private void startListen() {
        while (true) {
            try {
                Selector selector = this.selector;
                int sel = selector.select();
                if (sel == 0) {
                    continue;
                }
                Set<SelectionKey> selectKeys = selector.selectedKeys();
                Iterator keyIter = selectKeys.iterator();
                while (keyIter.hasNext()) {
                    SelectionKey key = (SelectionKey) keyIter.next();
                    keyIter.remove();
                    if (!key.isValid()) {
                        continue;
                    }
                    if (key.isAcceptable()) {
                        acceptServerSocket(key);
                    }
                    if (key.isReadable()) {
                        readServerSocketAndWriteBack(key);
                    } else {
                        key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }

    private void acceptServerSocket(SelectionKey key) {
        SocketChannel client = null;
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        try {
            client = server.accept();
            if (client == null) {
                return;
            }
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            System.out.println("Socket listener startup Exception:" + e);
            try {
                client.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    private void readServerSocketAndWriteBack(SelectionKey key) {
        NioHttpRequest request = new NioHttpRequest(key);
        NioHttpResponse response = new NioHttpResponse(key, selector);
        try {
            Class clazz = Class.forName(processClazz);
            Class[] paramTypes = {NioHttpRequest.class, NioHttpResponse.class};
            Object[] params = {request, response};
            Object obj = clazz.getConstructor(paramTypes).newInstance(params);
            Method method = clazz.getMethod(processMethod);
            method.invoke(obj, null);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Thread[" + processClazz + "]invoke Exception");
        }
    }

    public Thread getListenThread() {
        return listenThread;
    }

    public void setListenThread(Thread listenThread) {
        this.listenThread = listenThread;
    }
}

在这个类中我启动了Nio服务,服务的端口和各种属性信息,是通过配置文件加载,如果配置多个主线程类,多个端口,则系统启动时自动开启多个端口来监听请求,不同的端口执行不同的主线程类,每个主线程中都可以调用系统中的Servlet和Services。

Servlet有了,主线程有了,服务也有了,下面我们开始启动服务吧

package nioHttpServer;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;

public enum BusinessServer {
    SERVER();

    public Map<String, String> servlertMap;
    public String projectName;
    public String defaultName;

    public ServletContext servletContext;

    public Map<String, ExecutorService> businessPoolMap = new HashMap<>();

    private ExecutorService objectTasks;

    public void submitServiceProcess(CommonProcess process) {
        businessPoolMap.get(process.getClass().getSimpleName()).submit(process);
    }

    public static void main(String[] args) {
        try{
            for(int i=0;i<2;i++){
                ComNioServer server = new ComNioServer();
                int port = 8080+i;
                server.nioServerInit(port, 3600, "nioHttpServer.CommonProcess", "process");
            }

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

    }
}

在服务启动类中,我读取了配置文件中的Servlet信息,进行预先实例化,读取了线程池信息,在启动时开启线程池,最后在main方法中启动了2个端口,当然这里应该是读取配置文件启动的,最后看看启动效果,分别启动了8080端口和8081端口的两个监听。

最后来总结一下设计图

系统启动时加载配置文件,分别预加载自定义的Servlet和线程池,并定义好每个端口下的主线程类。然后启动服务,等待消息,收到请求后,根据报文信息获取到对应的Servlet,并在主线程中执行这个Servlet的Service方法,并打入线程池。

这样讲可能比较好理解,因为讲流程时是从前到后的,先收到请求,再一步一步处理。而设计开发时实际是逆向的,因为需要使用大量的反射机制完成动态的加载,最后再等待请求的到来。到这里,框架层面的代码就基本完成,下一篇讲讲怎么把实际的业务嵌入到这个结构中。


发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

Thanks for your support to bet365fans!