Browse Source

aio socket (#9752)

* add aio-socket

* update HttpServer

---------

Co-authored-by: litongjava <[email protected]>
Tong Li 4 months ago
parent
commit
d9c0bf9fcc

+ 19 - 0
frameworks/Java/aio-socket/.dockerignore

@@ -0,0 +1,19 @@
+.github
+.git
+.DS_Store
+docs
+kubernetes
+node_modules
+/.svelte-kit
+/package
+.env
+.env.*
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+__pycache__
+.env
+_old
+uploads
+.ipynb_checkpoints
+**/*.db
+_test

+ 3 - 0
frameworks/Java/aio-socket/.gitignore

@@ -0,0 +1,3 @@
+/target/
+logs
+.settings

+ 77 - 0
frameworks/Java/aio-socket/README.md

@@ -0,0 +1,77 @@
+# t-io Benchmarking Test
+
+This is the tio-server portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+## Controller
+
+These implementations use the tio-server's controller.
+
+### Plaintext Test
+
+* [Plaintext test source](src/main/java/com/litongjava/tio/http/server/controller/IndexController.java)
+
+### JSON Serialization Test
+
+* [JSON test source](src/main/java/com/litongjava/tio/http/server/controller/IndexController.java)
+
+
+## Versions
+3.7.3.v20231218-RELEASE (https://gitee.com/litongjava/t-io)
+
+## Test URLs
+
+All implementations use the same URLs.
+
+### Plaintext Test
+
+    http://localhost:8080/plaintext
+
+### JSON Encoding Test
+
+    http://localhost:8080/json
+
+
+
+ ## Hot to run
+ ### install mysql 8
+ - 1.please instal mysql 8.0.32,example cmd
+ ```
+ docker run --restart=always -d --name mysql_8 --hostname mysql \
+-p 3306:3306 \
+-e 'MYSQL_ROOT_PASSWORD=robot_123456#' -e 'MYSQL_ROOT_HOST=%' -e 'MYSQL_DATABASE=hello_world' \
+mysql/mysql-server:8.0.32 \
+--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --lower_case_table_names=1
+ ```
+ - 2.create database schema hello_world
+ - 3.create tablle,[example](sql/hello_world.sql)
+ - 4.import data
+ 
+ ### docker 
+ ```
+ docker build -t tio-server-benchmark -f tio-server.dockerfile .
+```
+The run is to specify the mysql database
+```
+docker run --rm -p 8080:8080 \
+-e JDBC_URL="jdbc:mysql://192.168.3.9/hello_world" \
+-e JDBC_USER="root" \
+-e JDBC_PSWD="robot_123456#" \
+tio-server-benchmark
+```
+
+### windows
+
+-windows
+```
+D:\java\jdk1.8.0_121\bin\java -jar target\tio-server-benchmark-1.0.jar --JDBC_URL=jdbc:mysql://192.168.3.9/hello_world?useSSL=false --JDBC_USER=root --JDBC_PSWD=robot_123456#
+```
+or 
+```
+set JDBC_URL=jdbc:mysql://192.168.3.9/hello_world
+set jdbc.user=root
+set JDBC_PSWD=robot_123456#
+D:\java\jdk1.8.0_121\bin\java -jar target\tio-server-benchmark-1.0.jar
+```
+
+
+

+ 19 - 0
frameworks/Java/aio-socket/aio-socket.dockerfile

@@ -0,0 +1,19 @@
+FROM litongjava/maven:3.8.8-jdk8u391 AS builder
+WORKDIR /app
+
+COPY pom.xml pom.xml
+RUN mvn dependency:go-offline  -q
+
+COPY src src
+RUN mvn package -Passembly -q
+RUN ls -l && ls -l target
+
+FROM litongjava/jre:8u391-stable-slim
+
+WORKDIR /app
+
+COPY --from=builder /app/target/aio-socket-benchmark-1.0-jar-with-dependencies.jar /app/aio-socket-benchmark-1.0.jar
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC","-cp", "/app/aio-socket-benchmark-1.0.jar","com.litongjava.aio.http.server.HttpServer"]

+ 23 - 0
frameworks/Java/aio-socket/benchmark_config.json

@@ -0,0 +1,23 @@
+{
+  "framework": "aio-socket",
+  "tests": [{
+    "default": {
+      "plaintext_url": "/plaintext",
+      "json_url": "/json",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "framework": "aio-socket",
+      "language": "Java",
+      "flavor": "None",
+      "orm": "Raw",
+      "platform": "t-io",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "aio-socket",
+      "notes": "aio-socket",
+      "versus": "t-io"
+    }
+  }]
+}

+ 15 - 0
frameworks/Java/aio-socket/config.toml

@@ -0,0 +1,15 @@
+[framework]
+name = "t-io"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "None"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "t-io"
+webserver = "None"
+versus = "t-io"

+ 90 - 0
frameworks/Java/aio-socket/pom.xml

@@ -0,0 +1,90 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.litongjava</groupId>
+  <artifactId>aio-socket-benchmark</artifactId>
+  <version>1.0</version>
+  <name>${project.artifactId}</name>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <java.version>1.8</java.version>
+    <maven.compiler.source>${java.version}</maven.compiler.source>
+    <maven.compiler.target>${java.version}</maven.compiler.target>
+    <main.class>com.litongjava.aio.http.server.HttpServer</main.class>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>com.litongjava</groupId>
+      <artifactId>aio-socket</artifactId>
+      <version>1.0.1</version>
+    </dependency>
+    <dependency>
+      <groupId>com.alibaba</groupId>
+      <artifactId>fastjson</artifactId>
+      <version>2.0.39</version>
+    </dependency>
+
+  </dependencies>
+  <repositories>
+    <repository>
+      <id>central</id>
+      <name>Central Repository</name>
+      <url>https://repo.maven.apache.org/maven2</url>
+    </repository>
+    <repository>
+      <id>sonatype-nexus-snapshots</id>
+      <name>Sonatype Nexus Snapshots</name>
+      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+    </repository>
+  </repositories>
+  <pluginRepositories>
+    <pluginRepository>
+      <id>central</id>
+      <name>Central Repository</name>
+      <url>https://repo.maven.apache.org/maven2</url>
+    </pluginRepository>
+    <pluginRepository>
+      <id>sonatype-nexus-snapshots</id>
+      <name>Sonatype Nexus Snapshots</name>
+      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+      <releases>
+        <enabled>false</enabled>
+      </releases>
+      <snapshots>
+        <enabled>true</enabled>
+      </snapshots>
+    </pluginRepository>
+  </pluginRepositories>
+  <build>
+    <plugins>
+
+      <plugin>
+        <inherited>true</inherited>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.8.0</version>
+        <configuration>
+          <debug>false</debug>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <version>3.1.0</version>
+        <configuration>
+          <descriptorRefs>
+            <descriptorRef>jar-with-dependencies</descriptorRef>
+          </descriptorRefs>
+        </configuration>
+        <executions>
+          <execution>
+            <id>make-assembly</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

+ 169 - 0
frameworks/Java/aio-socket/src/main/java/com/litongjava/aio/http/server/HttpServer.java

@@ -0,0 +1,169 @@
+package com.litongjava.aio.http.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousChannelGroup;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.nio.charset.StandardCharsets;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.ZoneId;
+import java.util.concurrent.ThreadFactory;
+
+import com.alibaba.fastjson.JSON;
+import com.litongjava.aio.http.server.model.Message;
+import com.litongjava.enhance.buffer.BufferPage;
+import com.litongjava.enhance.buffer.BufferPagePool;
+import com.litongjava.enhance.buffer.VirtualBuffer;
+import com.litongjava.enhance.channel.EnhanceAsynchronousChannelProvider;
+import com.litongjava.enhance.channel.EnhanceAsynchronousServerSocketChannel;
+
+public class HttpServer {
+
+  private static int cpuNum = Runtime.getRuntime().availableProcessors();
+  private static BufferPagePool pool = new BufferPagePool(0, 1024 * cpuNum, true);
+  private static BufferPage bufferPage = pool.allocateBufferPage();
+  private static final String HELLO_WORLD = "Hello, World!";
+
+  public static void main(String[] args) throws Exception {
+
+    // 创建异步通道提供者
+    EnhanceAsynchronousChannelProvider provider = new EnhanceAsynchronousChannelProvider(false);
+
+    // 创建通道组
+    AsynchronousChannelGroup group = provider.openAsynchronousChannelGroup(2, new ThreadFactory() {
+      @Override
+      public Thread newThread(Runnable r) {
+        return new Thread(r, "http-server-thread");
+      }
+    });
+
+    // 创建服务器通道并绑定端口
+    EnhanceAsynchronousServerSocketChannel server = (EnhanceAsynchronousServerSocketChannel) provider.openAsynchronousServerSocketChannel(group);
+    server.bind(new InetSocketAddress(8080), 0);
+
+    System.out.println("HTTP Server 正在监听端口 8080 ...");
+
+    // 异步接受连接
+    server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
+      @Override
+      public void completed(AsynchronousSocketChannel channel, Object attachment) {
+        // 继续接收其他连接
+        server.accept(null, this);
+        handleClient(channel);
+      }
+
+      @Override
+      public void failed(Throwable exc, Object attachment) {
+        exc.printStackTrace();
+      }
+    });
+
+    // 主线程阻塞
+    Thread.currentThread().join();
+  }
+
+  private static void handleClient(AsynchronousSocketChannel channel) {
+    VirtualBuffer virtualBuffer = bufferPage.allocate(8192);
+    ByteBuffer buffer = virtualBuffer.buffer();
+
+    channel.read(buffer, virtualBuffer, new CompletionHandler<Integer, VirtualBuffer>() {
+      @Override
+      public void completed(Integer result, VirtualBuffer attachment) {
+        try {
+          if (result > 0) {
+            buffer.flip();
+            byte[] bytes = new byte[buffer.remaining()];
+            buffer.get(bytes);
+            String request = new String(bytes, StandardCharsets.UTF_8);
+            
+            // 生成当前时间,格式为 RFC 1123 格式
+            String date = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneId.of("GMT")));
+            
+            ByteBuffer responseBuffer;
+            if (request.startsWith("GET /plaintext")) {
+              String body = "Hello, World!";
+              String httpResponse = "HTTP/1.1 200 OK\r\n" +
+                  "Content-Length: " + body.getBytes(StandardCharsets.UTF_8).length + "\r\n" +
+                  "Server: aio-socket\r\n" +
+                  "Content-Type: text/plain\r\n" +
+                  "Date: " + date + "\r\n" +
+                  "\r\n" +
+                  body;
+              responseBuffer = ByteBuffer.wrap(httpResponse.getBytes(StandardCharsets.UTF_8));
+
+            } else if (request.startsWith("GET /json")) {
+              String jsonString = JSON.toJSONString(new Message(HELLO_WORLD));
+              int length = jsonString.getBytes(StandardCharsets.UTF_8).length;
+              String httpResponse = "HTTP/1.1 200 OK\r\n" +
+                  "Content-Length: " + length + "\r\n" +
+                  "Server: aio-socket\r\n" +
+                  "Content-Type: application/json\r\n" +
+                  "Date: " + date + "\r\n" +
+                  "\r\n" +
+                  jsonString;
+              responseBuffer = ByteBuffer.wrap(httpResponse.getBytes(StandardCharsets.UTF_8));
+            } else {
+              String body = "Hello, World!";
+              String httpResponse = "HTTP/1.1 200 OK\r\n" +
+                  "Content-Length: " + body.getBytes(StandardCharsets.UTF_8).length + "\r\n" +
+                  "Content-Type: text/plain\r\n" +
+                  "Date: " + date + "\r\n" +
+                  "\r\n" +
+                  body;
+              responseBuffer = ByteBuffer.wrap(httpResponse.getBytes(StandardCharsets.UTF_8));
+            }
+
+            // 异步写响应
+            channel.write(responseBuffer, attachment, new CompletionHandler<Integer, VirtualBuffer>() {
+              @Override
+              public void completed(Integer result, VirtualBuffer attachment) {
+                try {
+                  channel.close();
+                } catch (IOException e) {
+                  e.printStackTrace();
+                } finally {
+                  attachment.clean();
+                }
+              }
+
+              @Override
+              public void failed(Throwable exc, VirtualBuffer attachment) {
+                exc.printStackTrace();
+                try {
+                  channel.close();
+                } catch (IOException e) {
+                  e.printStackTrace();
+                } finally {
+                  attachment.clean();
+                }
+              }
+            });
+          } else {
+            try {
+              channel.close();
+            } catch (IOException e) {
+              e.printStackTrace();
+            }
+          }
+        } finally {
+          // 注意:如果在写操作中已经归还了虚拟缓冲区,则不要重复释放
+        }
+      }
+
+      @Override
+      public void failed(Throwable exc, VirtualBuffer attachment) {
+        exc.printStackTrace();
+        try {
+          channel.close();
+        } catch (IOException e) {
+          e.printStackTrace();
+        } finally {
+          attachment.clean();
+        }
+      }
+    });
+  }
+}

+ 23 - 0
frameworks/Java/aio-socket/src/main/java/com/litongjava/aio/http/server/model/Fortune.java

@@ -0,0 +1,23 @@
+package com.litongjava.aio.http.server.model;
+
+public final class Fortune {
+
+  public Long id;
+  public String message;
+
+  public Fortune() {
+  }
+
+  public Fortune(Long id, String message) {
+    this.id = id;
+    this.message = message;
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+}

+ 13 - 0
frameworks/Java/aio-socket/src/main/java/com/litongjava/aio/http/server/model/Message.java

@@ -0,0 +1,13 @@
+package com.litongjava.aio.http.server.model;
+
+public final class Message {
+  private final String message;
+
+  public Message(String message) {
+    this.message = message;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+}

+ 32 - 0
frameworks/Java/aio-socket/src/main/java/com/litongjava/aio/http/server/model/World.java

@@ -0,0 +1,32 @@
+package com.litongjava.aio.http.server.model;
+
+public final class World {
+
+  public int id;
+  public int randomnumber;
+
+  protected World() {
+  }
+
+  public World(int id, int randomnumber) {
+    this.id = id;
+    this.randomnumber = randomnumber;
+  }
+
+  public int getId() {
+    return id;
+  }
+
+  public void setId(int id) {
+    this.id = id;
+  }
+
+  public int getRandomnumber() {
+    return randomnumber;
+  }
+
+  public void setRandomnumber(int randomnumber) {
+    this.randomnumber = randomnumber;
+  }
+
+}

+ 36 - 0
frameworks/Java/aio-socket/src/main/java/com/litongjava/aio/http/server/utils/RandomUtils.java

@@ -0,0 +1,36 @@
+package com.litongjava.aio.http.server.utils;
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.IntStream;
+
+public class RandomUtils {
+
+  private static final int MIN_WORLD_NUMBER = 1;
+  private static final int MAX_WORLD_NUMBER_PLUS_ONE = 10_001;
+  //  private static final int MAX_WORLD_NUMBER_PLUS_ONE = 30;
+
+  public static int randomWorldNumber() {
+    return ThreadLocalRandom.current().nextInt(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE);
+  }
+
+  public static IntStream randomWorldNumbers() {
+    return ThreadLocalRandom.current().ints(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE)
+        // distinct() allows us to avoid using Hibernate's first-level cache in
+        // the JPA-based implementation. Using a cache like that would bypass
+        // querying the database, which would violate the test requirements.
+        .distinct();
+  }
+
+  public static int parseQueryCount(String textValue) {
+    if (textValue == null) {
+      return 1;
+    }
+    int parsedValue;
+    try {
+      parsedValue = Integer.parseInt(textValue);
+    } catch (NumberFormatException e) {
+      return 1;
+    }
+    return Math.min(500, Math.max(1, parsedValue));
+  }
+}

+ 9 - 0
frameworks/Java/aio-socket/src/main/resources/app.properties

@@ -0,0 +1,9 @@
+http.response.header.showServer=true
+server.port=8080
+#JDBC_URL=jdbc:mysql://192.168.3.9/hello_world?useSSL=false&allowPublicKeyRetrieval=true
+#JDBC_USER=root
+#JDBC_PSWD=robot_123456#
+
+JDBC_URL=jdbc:mysql://tfb-database/hello_world
+JDBC_USER=benchmarkdbuser
+JDBC_PSWD=benchmarkdbpass

+ 9 - 0
frameworks/Java/aio-socket/src/main/resources/ehcache.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
+
+  <diskStore path="java.io.tmpdir/EhCache" />
+
+  <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false"
+                timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU" />
+</ehcache>

+ 55 - 0
frameworks/Java/aio-socket/src/main/resources/logback.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<configuration debug="false" xmlns="http://ch.qos.logback/xml/ns/logback" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd
+http://ch.qos.logback/xml/ns/logback ">
+
+  <!-- 定义日志文件的存储地址,避免在 Logback 的配置中使用相对路径 -->
+  <property name="LOG_HOME" value="logs" />
+
+  <!-- 格式化输出:%d 表示日期,[%thread] 输出线程名称,%-6level 表示日志级别占6个字符宽度,%logger{1}.%M:%L 表示类名、方法名和行号,%m 表示日志消息,%n 是换行符 -->
+  <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-6level %logger{1}.%M:%L - %m%n" />
+
+  <!-- 控制台输出 -->
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+      <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+    </encoder>
+  </appender>
+
+  <!-- 按照每小时生成日志文件 -->
+  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+      <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+    </encoder>
+    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+      <!-- 日志文件输出的文件名,包含日期和小时 -->
+      <fileNamePattern>${LOG_HOME}/log.%d{yyyyMMddHH}.%i.log</fileNamePattern>
+      <!-- 日志文件保留天数 -->
+      <maxHistory>180</maxHistory>
+      <!-- 每个日志文件的最大大小 -->
+      <maxFileSize>100MB</maxFileSize>
+    </rollingPolicy>
+  </appender>
+
+  <!-- Spring 框架日志级别设置 -->
+  <logger name="org.springframework" level="info" />
+
+  <!-- Hibernate 日志级别设置,显示 SQL 语句和绑定参数 -->
+  <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
+  <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" />
+  <logger name="org.hibernate.SQL" level="DEBUG" />
+  <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
+  <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />
+
+  <!-- MyBatis 和 SQL 相关日志配置 -->
+  <logger name="com.apache.ibatis" level="TRACE" />
+  <logger name="java.sql.Connection" level="DEBUG" />
+  <logger name="java.sql.Statement" level="DEBUG" />
+  <logger name="java.sql.PreparedStatement" level="DEBUG" />
+
+  <!-- 根日志记录器的日志级别和输出目标 -->
+  <root level="info">
+    <appender-ref ref="STDOUT" />
+    <appender-ref ref="FILE" />
+  </root>
+</configuration>

+ 20 - 0
frameworks/Java/aio-socket/src/main/resources/templates/fortunes.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Fortunes</title>
+</head>
+<body>
+<table>
+  <tr>
+    <th>id</th>
+    <th>message</th>
+  </tr>
+  #for(fortune : fortunes)
+  <tr>
+    <td>#(fortune.id)</td>
+    <td>#escape(fortune.message)</td>
+  </tr>
+  #end
+</table>
+</body>
+</html>