In the first article, we constructed the request and response structure of the http message, analyzed the request message, and sent the response message, just like a person has limbs first. In this section, we will construct the most important part. It’s the torso. First look at the design diagram, then the code, and finally summarize.

First look at the design diagram:

First look at a custom XML file, which defines the components needed in this project. These components can be defined in the xml file, in the database, or in the property file. Just like the development history of most JAVA frameworks, most components are now defined using Annotation, which is automatically scanned and loaded by the container. There is no difference between good and bad in which way to define it. Use whichever is appropriate.

<?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>

Roughly explain this configuration file

The first part is the custom Servlet, which is the main business calling class of this project. A business module can define a servlet, and the attribute project can be used as the first keyword in the HTTP request url.

The second part is the thread pool, including the main thread class and the attribute information of the thread pool. Here, following the design principle of Tomcat, the servlet is entered into the thread pool for execution to realize the single Servlet multi-thread mode.

The third part is the specific business Action definition, which is the most specific business processing class and business processing method.

For the code part, let’s look at the first one, the preloaded class of 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);
    }
}

In this class, I will take out the XML configuration file loaded into the memory when the system starts, retrieve the Servlet information in it, and instantiate it in turn and store it in a Map for subsequent use, because the Servlet and Project names are already in the It is configured in the project, so it is easy to block illegal links and requests.

With the Servlet, the next step is to execute the threads required by this 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");
        }
    }
}

This is the main thread class. Multiple main thread classes can be configured. After the system receives a request, it will call the process method in this class. The process method will obtain the corresponding Servlet information according to the Http request, and then go to the Servlet context to obtain a legal Servlet. instance, if a matching Servlet is found, it will put itself into the thread pool that has been loaded, and execute the Service method of the obtained Servlet instance. If no matching Servlet instance is found, an error message is returned.

With the servlet, there is also the main thread of execution. Now write a service and start the listening port.

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;
    }
}

In this class, I started the Nio service. The service port and various attribute information are loaded through the configuration file. If multiple main thread classes and multiple ports are configured, multiple ports will be automatically opened when the system starts to listen for requests. Different ports execute different main thread classes, and each main thread can call Servlets and Services in the system.

The servlet is there, the main thread is there, and the service is also there. Let’s start the service now.

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();
        }

    }
}

In the service startup class, I read the Servlet information in the configuration file, pre-instantiated, read the thread pool information, opened the thread pool at startup, and finally started 2 ports in the main method. Of course, it should It was started by reading the configuration file, and finally looked at the startup effect, and started two monitors on port 8080 and port 8081 respectively.

最后来总结一下设计图

Load the configuration file when the system starts, preload the custom Servlet and thread pool respectively, and define the main thread class under each port. Then start the service, wait for the message, and after receiving the request, obtain the corresponding Servlet according to the message information, and execute the Service method of this Servlet in the main thread, and enter the thread pool.

It may be easier to understand this way, because when talking about the process, it is from front to back, first receive the request, and then process it step by step. The design and development are actually reversed, because a lot of java reflection mechanisms need to be used to complete the dynamic loading, and finally wait for the arrival of the request. At this point, the code at the framework level is basically completed. The next article will talk about how to embed the actual business into this structure.


发表回复

Thanks for your support to bet365fans!