• 周六. 10 月 12th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Handwriting a mini version of Tomcat based on NiO

King Wang

1 月 3, 2022

The author also established his own official account. , I usually share some programming knowledge , Welcome everyone to support ~

Scan code or wechat search north wind IT The way Focus on

Official account address : Handwritten one based on NIO The mini Tomcat

I saw an article a long time ago Write a mini version Tomcat, I think it’s still very interesting , So he also followed his hand again , Sure enough, we got what we wanted hello world, But this is based on BIO Of , I happened to read a book about concurrent programming recently , So I tried to change the big guy’s code , So there’s this based on NIO Mini Tomcat.

Source code updated to my Github:https://github.com/tzfun/MyTomcat

BIO and NIO

BIO It’s synchronous blocking IO, In the actual scenario, most of the time is spent on IO Waiting , Compare the consumption of resources , So this kind of IO The way is gradually replaced , The specific introduction will not be repeated here . In its place NIO(New IO), It’s a synchronous, non blocking IO, It passes through Selector Spin or callback to process ready data Channel,Channel Channel , amount to BIO Flow in , It doesn’t exist. It takes a lot of time to IO wait for , This greatly improves throughput .Tomcat The old version is also based on BIO Of , All subsequent updates have been replaced with NIO.

Project structure

The structure of the project is almost the same as that of the original author , It’s just that the code implementation is different , The specific structure is shown in the figure below :

Request and Response

Tomcat Mainly Http service , So the deal is Http agreement , that Http The header information of the 、 Request header 、 Blank line 、 Composition of request data , You just need to split the data to process http request , I’ve just dealt with it briefly , See notes for other details .

Request

package mytomcat;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* <p>location: mytomcat.javase_learning</p>
* Created in 14:45 2019/4/21
*/
public class MyRequest {

private String url;
private String method;
private HashMap<String,String> param = new HashMap<>();
public MyRequest(SelectionKey selectionKey) throws IOException{

// Access from contracts 
SocketChannel channel = (SocketChannel) selectionKey.channel();
String httpRequest = "";
ByteBuffer bb = ByteBuffer.allocate(16*1024); // Get memory from heap memory 
int length = 0; // Read byte Length of array 
length = channel.read(bb); // Read data from the channel to ByteBuffer In the container 
if (length < 0){

selectionKey.cancel(); // Cancel the contract 
}else {

httpRequest = new String(bb.array()).trim(); // take ByteBuffer To String
String httpHead = httpRequest.split("\n")[0]; // Get request header 
url = httpHead.split("\\s")[1].split("\\?")[0]; // Get the request path 
String path = httpHead.split("\\s")[1]; // Request full path , contain get Parameter data of 
method = httpHead.split("\\s")[0];
// Here's the split get Requested parameter data 
String[] params = path.indexOf("?") > 0 ? path.split("\\?")[1].split("\\&") : null;
if (params != null){

try{

for (String tmp : params){

param.put(tmp.split("\\=")[0],tmp.split("\\=")[1]);
}
}catch (NullPointerException e){

e.printStackTrace();
}
}
System.out.println(this);
}
bb.flip();
}
public String getUrl() {

return url;
}
public void setUrl(String url) {

this.url = url;
}
public String getMethod() {

return method;
}
public void setMethod(String method) {

this.method = method;
}
@Override
public String toString() {

return "MyRequest{" +
"url='" + url + '\'' +
", method='" + method + '\'' +
", param=" + param +
'}';
}
}

Response

package mytomcat;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* <p>location: mytomcat.javase_learning</p>
* Created in 14:49 2019/4/21
*/
public class MyResponse {

private SelectionKey selectionKey;
public MyResponse(SelectionKey selectionKey){

this.selectionKey = selectionKey;
}
public void write(String content) throws IOException{

// Splicing corresponding packets 
StringBuffer httpResponse = new StringBuffer();
httpResponse.append("HTTP/1.1 200 OK\n")
.append("Content-type:text/html\n")
.append("\r\n")
.append("<html><body>")
.append(content)
.append("</body></html>");
// Convert to ByteBuffer
ByteBuffer bb = ByteBuffer.wrap(httpResponse.toString().getBytes(StandardCharsets.UTF_8));
SocketChannel channel = (SocketChannel) selectionKey.channel(); // Access from contracts 
long len = channel.write(bb); // Write data to the channel 
if (len == -1){

selectionKey.cancel();
}
bb.flip();
channel.close();
selectionKey.cancel();
}
}

Servlet and Mapping Mapping class and its configuration class

stay Java web In development, you will encounter Servlet and Mapping Configuration of , These are essential elements ,Servlet Responsible for defining methods for handling requests and responses , It’s an abstract class .

Servlet

package mytomcat;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* <p>location: mytomcat.javase_learning</p>
* Created in 14:53 2019/4/21
*/
public abstract class MyServlet {

public abstract void doGet(MyRequest myRequest,MyResponse myResponse);
public abstract void doPost(MyRequest myRequest,MyResponse myResponse);
public void service(MyRequest myRequest,MyResponse myResponse){

if (myRequest.getMethod().equalsIgnoreCase("POST")){

doPost(myRequest,myResponse);
}else if (myRequest.getMethod().equalsIgnoreCase("GET")){

doGet(myRequest,myResponse);
}
}
}

Mapping Mapping is responsible for distributing some request paths to their respective processing classes for processing , Then you need a configuration class , Here are ServletMapping Class definition and Config class .

ServletMapping

package mytomcat;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* <p>location: mytomcat.javase_learning</p>
* Created in 14:59 2019/4/21
*/
public class ServletMapping {

private String servletName;
private String url;
private String clazz;
public ServletMapping(String servletName, String url, String clazz) {

this.servletName = servletName;
this.url = url;
this.clazz = clazz;
}
public String getServletName() {

return servletName;
}
public void setServletName(String servletName) {

this.servletName = servletName;
}
public String getUrl() {

return url;
}
public void setUrl(String url) {

this.url = url;
}
public String getClazz() {

return clazz;
}
public void setClazz(String clazz) {

this.clazz = clazz;
}
}

ServletMappingConfig

package mytomcat;
import java.util.ArrayList;
import java.util.List;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* <p>location: mytomcat.javase_learning</p>
* Created in 15:01 2019/4/21
*/
public class ServletMappingConfig {

public static List<ServletMapping> servletMappingList = new ArrayList<>();
static {

servletMappingList.add(new ServletMapping("helloWorld","/world","mytomcat.HelloWorldServlet"));
}
}

Of course, the general configuration is through xml File to configure .

Here’s how to deal with /world Request processing class

package mytomcat;
import java.io.IOException;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* <p>location: mytomcat.javase_learning</p>
* Created in 14:57 2019/4/21
*/
public class HelloWorldServlet extends MyServlet {

@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {

try{

myResponse.write("get hello world");
}catch (IOException e){

e.printStackTrace();
}
}
@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {

try{

myResponse.write("post hello world");
}catch (IOException e){

e.printStackTrace();
}
}
}

Tomcat Core startup class

Tomcat The startup class of is its core , It contains initialization Mapping、 Listening port 、 Processing requests and responses, etc , The main difference between me and the original author is in this part , use NIO To replace the BIO Receiving data mode of , Using thread pool to process data .

MyTomcat

package mytomcat;
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.nio.channels.spi.SelectorProvider;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* <p>location: mytomcat.javase_learning</p>
* Created in 15:03 2019/4/21
*/
public class MyTomcat {

private int port = 8080;
private Map<String, String> urlServletMap = new HashMap<>();
private Selector selector;
private ExecutorService es = Executors.newCachedThreadPool();
public MyTomcat() {

}
public MyTomcat(int port) {

this.port = port;
}
public void start() throws IOException {

// Initialize mapping relationship 
initServletMapping();
// start-up Selector
selector = SelectorProvider.provider().openSelector();
// start-up Channel
ServerSocketChannel ssc = ServerSocketChannel.open();
// Configure non blocking selection 
ssc.configureBlocking(false);
// Listening port 
InetSocketAddress isa = new InetSocketAddress(port);
ssc.socket().bind(isa);
// take Channel Bound to the Selector On , And select the preparation mode as Accept, It may fail here , It can be opened again later 
SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("MyTomcat is started...");
ConcurrentLinkedQueue<MyRequest> requestList = new ConcurrentLinkedQueue<>();
ConcurrentLinkedQueue<MyResponse> responseList = new ConcurrentLinkedQueue<>();
while (true) {

selector.select(); // wait for Channel Prepare the data 
Set readyKeys = selector.selectedKeys();
Iterator i = readyKeys.iterator();
while (i.hasNext()) {

SelectionKey sk = (SelectionKey) i.next();
i.remove(); // Remove from collection , Prevent duplicate processing 
if (sk.isAcceptable()) {
 // If the receiving state of the key is not turned on properly , Try opening again 
doAccept(sk);
} else if (sk.isValid() && sk.isReadable()) {
 // Can be read 
requestList.add(getRequest(sk));
// Switch readiness 
sk.interestOps(SelectionKey.OP_WRITE);
} else if (sk.isValid() && sk.isWritable()) {
 // Can write 
responseList.add(getResponse(sk));
// Switch readiness 
sk.interestOps(SelectionKey.OP_READ);
}
// Wait for a pair of requests and responses to be ready 
if (!requestList.isEmpty() && !responseList.isEmpty()) {

dispatch(requestList.poll(), responseList.poll());
}
}
}
}
/**
* If the receiving mode is not turned on normally
* Try to turn on receive mode
* @param selectionKey
*/
private void doAccept(SelectionKey selectionKey) {

ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
SocketChannel clientChannel;
try {

clientChannel = server.accept();
clientChannel.configureBlocking(false);
SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {

e.printStackTrace();
}
}
/**
* Get the request from the channel and wrap it
*
* @param selectionKey
* @return
* @throws IOException
*/
private MyRequest getRequest(SelectionKey selectionKey) throws IOException {

return new MyRequest(selectionKey); // packing request
}
/**
* Get the response from the channel and wrap it
*
* @param selectionKey
* @return
*/
private MyResponse getResponse(SelectionKey selectionKey) {

return new MyResponse(selectionKey); // packing response
}
/**
* initialization Servlet Mapping object for
*/
private void initServletMapping() {

for (ServletMapping servletMapping : ServletMappingConfig.servletMappingList) {

urlServletMap.put(servletMapping.getUrl(), servletMapping.getClazz());
}
}
/**
* Request scheduling
*
* @param myRequest
* @param myResponse
*/
private void dispatch(MyRequest myRequest, MyResponse myResponse) {

if (myRequest == null) return;
if (myResponse == null) return;
String clazz = urlServletMap.get(myRequest.getUrl());
try {

if (clazz == null) {

myResponse.write("404");
return;
}
es.execute(new Runnable() {

@Override
public void run() {

try {

Class<MyServlet> myServletClass = (Class<MyServlet>) Class.forName(clazz);
MyServlet myServlet = myServletClass.newInstance();
myServlet.service(myRequest, myResponse);
} catch (IllegalAccessException e) {

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

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

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

e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {

new MyTomcat().start();
}
}

test

When we type http://localhost:8080/world?data=yes when , The success achieved the expected results

 Insert picture description here

Console output :

发表回复