简单的WEB服务器

优质博文:IT-BLOG-CN

目的: 了解Java Web服务器是如何运行的。Web服务器使用HTTP与其客户端,也就是Web浏览器进行通信。基于JavaWeb服务器会使用两个重要类:java.net.Socket类和java.net.ServerSocket类,并通过发送HTTP消息进行通信。

一、HTTP

超文本传输协议Hypertext Transfer Protocol,HTTP是一个简单的请求-响应协议,它通常运行在TCP之上。运行Web服务器和浏览器通过Internet发送并接收数据。请求和响应消息的头以ASCII形式给出;这个简单模型是早期Web成功的有功之臣,因为它使开发和部署非常地直截了当。HTTP使用可靠的TCP连接,默认使用TCP80端口。

HTTP中,总是由客户端通过建立连接并发送HTTP请求来初始化一个事物的。Web服务器端并不负责联系客户端或建立一个到客户端的回调链接。客户端或服务器端可提前关闭连接, 例如, 当使用Web浏览器浏览网页时, 可以单击浏览器上的stop按钮来停止下载文件, 这样就有效的关闭了一个 Web服务器的http连接。

一个HTTP请求包含以下三部分:
【1】请求方法:统一资源标识符Uniform Resource Identifier, URI协议/版本;
【2】请求头;
【3】实体;

// 请求方式 - URL - 协议/版本
POST /examples/default.jsp HTTP/1.1 
Accept: text/plain; text/html 
Accept-Language: en-gb 
Connection: Keep-Alive 
Host: localhost 
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33 Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate 

HTTP 1.1支持7种类型的请 求:GET, POST, HEAD, OPTIONS, PUT, DELETETRACEGETPOST在互联网中最常用的两种请求。

一个HTTP响应包含以下三部分:
【1】协议、状态码、描述;
【2】响应头;
【3】响应实体段;

HTTP/1.1 200 OK 
Server: Microsoft-IIS/4.0 
Date: Mon, 5 Jan 2004 13:13:33 GMT 
Content-Type: text/html 
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT 
Content-Length: 112 
<html> <head> <title>HTTP Response Example</title> </head> <body> Welcome to Brainy Software </body> 
</html>

响应头部的第一行类似于请求头部的第一行。第一行告诉你该协议使用HTTP 1.1,请求成功200,表示一切都运行良好。 响应头部和请求头部类似,也包括很多有用的信息。响应的主体内容是响应本身的HTML内容。

二、Socket类

Socket为网络通信提供了一组丰富的方法和属性。 Socket允许使用枚举中列出的ProtocolType任何通信协议执行同步和异步数据传输。套接字是网络连接的一个端点。套接字使得一个应用可以从网络中读取和写入数据。放在两个不同计算机上的两个应用可以通过连接发送和接受字节流。为了从你的应用发送一条信息到另一个应用,你需要知道另一个应用的IP地址和套接字端口。

// host远程主机的地址,port远程端口
public Socket (java.lang.String host, int port) 

一旦你成功创建了一个Socket类的实例,你可以使用它来发送和接受字节流。要发送字节流,你首先必须调用Socket类的getOutputStream方法来获取一个java.io.OutputStream对象。要发送文本到一个远程应用,你经常要从返回的OutputStream对象中构造一个java.io.PrintWriter对象。要从连接的另一端接受字节流,你可以调用Socket类的getInputStream方法用来返回一个java.io.InputStream对象。 以下的代码片段创建了一个套接字,可以和本地HTTP服务器(127.0.0.1是指本地主机)进行通讯,发送一个HTTP请求,并从服务器接受响应。它创建了一个StringBuffer对象来保存响应并在控制台上打印出来。

public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {Socket socket = new Socket("127.0.0.1", 80); //想要发送自己流你需要的得到socket类返回的一个OutputStream对象OutputStream os = socket.getOutputStream(); boolean autoflush = true; //通过现有的OutputStream构建一个PrintWriter对象来向输出流中写数据PrintWriter out = new PrintWriter( socket.getOutputStream(), autoflush); //从连接的另一端接受数据BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream())); // 发送HTTP请求到web服务器out.println("GET /index.jsp HTTP/1.1"); out.println("Host: localhost:8080"); out.println("Connection: Close"); out.println(); // 读取返回值 boolean loop = true; StringBuffer sb = new StringBuffer(8096); while (loop) {// 告知是否准备读取此流if ( in.ready() ) {int i=0; while (i!=-1) { // 读取单个字符i = in.read();sb.append((char) i);} loop = false;}Thread.currentThread().sleep(50);
} //关闭 socket
socket.close(); 

ServerSocket类

Socket类代表一个客户端套接字,即任何时候你想连接到一个远程服务器应用的时候你构造的套接字,现在,假如你想实施一个服务器应用,例如一个HTTP服务器或者FTP服务器,你需要一种不同的做法。这是因为你的服务器必须随时待命,因为它不知道一个客户端应用什么时候会尝试去连接它。为了让你的应用能随时待命,你需要使用java.net.ServerSocket类。这是服务器套接字的实现。

ServerSocketSocket不同,服务器套接字的角色是等待来自客户端的连接请求。一旦服务器套接字获得一个连接请求,它创建一个Socket实例来与客户端进行通信。 要创建一个服务器套接字,你需要使用ServerSocket类提供的四个构造方法中的一个。你需要指定IP地址和服务器套接字将要进行监听的端口号。通常,IP地址将会是127.0.0.1,也就是说,服务器套接字将会监听本地机器。服务器套接字正在监听的IP地址被称为是绑定地址。服务器套接字的另一个重要的属性是backlog,这是服务器套接字开始拒绝传入的请求之前,传入的连接请求的最大队列长度。 其中一个ServerSocket类的构造方法如下所示:

// 创建绑定到特定端口的服务器套接字。
public ServerSocket(int port) throws IOException
// 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
public ServerSocket(int port, int backlog) throws IOException
// 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
public ServerSocket(int port, int backlog, InetAddress address) throws IOException
// 创建非绑定服务器套接字。使用此构造方法时, 如果没有抛出异常,就意味着应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
public ServerSocket() throws IOException

通过ServerSocket创建实例后,可以让它在绑定地址和服务器套接字正在监听的端口上等待传入的连接请求。你可以通过调用ServerSocket类的accept方法做到这点。这个方法只会在有连接请求时才会返回,并且返回值是一个Socket类的实例。Socket对象接下去可以发送字节流并从客户端应用中接受字节流,就像上述的Socket类解释的那样。

//ServerSocketDemo
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;public class ServerSocketDemo extends Thread {private ServerSocket serverSocket;private int i = 1;public ServerSocketDemo(int port) throws IOException {serverSocket = new ServerSocket(port);//设置20s内无客户端连接,则抛出SocketTimeoutException异常serverSocket.setSoTimeout(20000);}public void run(){while(true) {System.out.println("服务端第"+i+"次启动中...对应的端口号为:"+ serverSocket.getLocalPort());i++;try {Socket server = serverSocket.accept();//彩蛋//server.setSoTimeout(5);//彩蛋//当服务端监听到客户端的连接后才会执行以下代码System.out.println("服务端打印的远程主机地址为:"+server.getRemoteSocketAddress());//监听来自客户端的消息DataInputStream dis = new DataInputStream(server.getInputStream());System.out.println("服务端接收到的来自于客户端的信息为:"+dis.readUTF());//通过socket向客户端发送信息DataOutputStream dos = new DataOutputStream(server.getOutputStream());dos.writeUTF("我是服务端,您已连接到:"+server.getLocalSocketAddress());server.close();}catch (SocketTimeoutException e){System.out.println("20s内无客户端连接,正在关闭服务端监听服务");continue;}catch (IOException e) {e.printStackTrace();break;}}}public static void main(String[] args) {try {Thread t1 = new ServerSocketDemo(8089);t1.run();}catch(IOException e){e.printStackTrace();return;}}}

三、HttpServer类

HttpServer类表示一个Web服务器,具体实现如代码如下:

public class HttpServer {public static void main(String[] args) {HttpServer server = new HttpServer();server.await();}public void await() {}
}

这个Web服务器可以处理对指定目录中的静态资源的请求,该目录包括由公有静态变量final WEB_ROOT指明的目录及其所有子目录。WEB_ROOT的初始值为:

public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";

该代码清单包含一个名为webroot的目录,用于测试该应用程序的一些静态资源都位于该目录下。在该目录下还可以找到用于测试后续章节中应用程序的几个servlet程序。若要请求静态资源,可以在浏览器的地址栏或URL框中输入如下的URL

http://machineName:port/staticResource

若从另一台机器(不是运行应用程序的那台机器)上向该应用程序发出请求,则machineName是应用程序所在计算机的名称或IP地址;若在同一台机器上发出的请求,则可以将machineName替换为localhost,此外,连接请求使用的端口为8080staticResource是请求的文件的名字,该文件必须位于WEB_ROOT指向的目录下。

例如,如果你正使用同一台机器来测试该应用程序,你想让HttpServer对象发送index.html文件,就可以使用如下的URL

http://localhost:8080/index.html

若要关闭服务器,可以通过Web浏览器的地址栏或URL框,在URLhost:port部分后面输入预先定义好的字符串,从Web浏览器发送一条关闭命令,这样服务器就会收到关闭命令了。关闭命令定义在HttpServer类的SHUTDOWN静态final变量中:

private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

因此,若要关闭服务器,需要使用如下的URL

private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

因此,若要关闭服务器,需要使用如下的URL

http://localhost:8080/SHUTDOWN

在应用程序的入口点,也就是静态main函数中,创建一个HttpServer实例,然后调用其await()方法。顾名思义,await方法会在制定的端口上等待http请求,并对其进行处理,然后发送相应的消息回客户端。在接收到命令之前,它会一直保持等待的状态。

 public void await() {ServerSocket serverSocket = null;int port = 8080;try {serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));}catch (IOException e) {e.printStackTrace();System.exit(1);}// Loop waiting for a requestwhile (!shutdown) {Socket socket = null;InputStream input = null;OutputStream output = null;try {socket = serverSocket.accept();input = socket.getInputStream();output = socket.getOutputStream();// create Request object and parseRequest request = new Request(input);request.parse();// create Response objectResponse response = new Response(output);response.setRequest(request);response.sendStaticResource();// Close the socketsocket.close();//check if the previous URI is a shutdown commandshutdown = request.getUri().equals(SHUTDOWN_COMMAND);}catch (Exception e) {e.printStackTrace();continue;}}
}

该方法名之所以称为await(),而不是wait(),是因为wait()方法是java.lang.Object类中与使用线程相关的重要方法。await()方法会先创建一个ServerSocket实例,然后进入一个while循环:

serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
......
// Loop waiting for a request
while(!shtdown) {...
}

当从8080端口接收到HTTP请求后,ServerSocket类的accept()方法返回,等待结束:

socket = serverSocket.accept();

接收到请求后,await()方法会从accept()方法返回的Socket实例中获取java.io.InputStream对象和java.io.OutputStream对象:

input = socket.getInputStream(); 
output = socket.getOutputStream();

然后,await()方法会创建一个ex01.pyrmont.Request对象,并调用其parse()方法来解析HTTP请求的原始数据:

// create Request object and parse 
Request request = new Request(input); 
request.parse();

然后,await()方法会创建一个Response对象,并分别调用其setRequest()方法和sendStaticResource()方法:

// create Response object
Response response = new Response(output); 
response.setRequest(request); 
response.sendStaticResource();

最后,await()方法关闭套接字,调用Request类的getUri()方法来测试HTTP请求的URI是否是关闭命令。若是,则将变量shutdown设置为true,程序退出while循环。

// Close the socket 
socket.close (); //check if the previous URI is a shutdown command 
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);

四、Request类

Request类表示一个HTTP请求。可以传递InputStream对象(从通过处理与客户端通信的Socket对象中获取的),来创建Request对象。可以调用InputStream对象中的read()方法来读取HTTP请求的原始数据。

package ex01.pyrmont;import java.io.InputStream;
import java.io.IOException;public class Request {private InputStream input;private String uri;public Request(InputStream input) {this.input = input;}// 解析input输入流,这里只是获取请求行的URI// 实际的解析过程远不止这些public void parse() {//下面是用最常见的read()方法获取输入流的内容,也是为什么要传入输入流的原因StringBuffer request = new StringBuffer(2048);int i;byte[] buffer = new byte[2048];try {i = input.read(buffer);} catch (IOException e) {e.printStackTrace();i = -1;}for (int j = 0; j < i; j++) {request.append((char) buffer[j]);}System.out.print(request.toString());uri = parseUri(request.toString());}//获取URI,通过对字符串进行简单的查询和切割获得private String parseUri(String requestString) {int index1, index2;index1 = requestString.indexOf(' ');if (index1 != -1) {index2 = requestString.indexOf(' ', index1 + 1);if (index2 > index1)return requestString.substring(index1 + 1, index2);}return null;}public String getUri() {return uri;}
}

parse()方法用于解析HTTP请求中的原始数据。parse()方法会调用私有方法parseUri()来解析HTTP请求的URI,除此之外,并没有做太多的工作。parseUri()方法将URI存储在变量uri中。调用公共方法getUri()会返回HTTP请求的URIparse()方法从传入到Request对象中的套接字的InputStream对象中读取整个字节流,并将字节数组存储在缓冲区中。然后,它使用缓冲区字节数组中的数组填充StringBuffer对象request,并将StringBufferString表示传递给parseUri()方法。

parseUri()方法从请求行中获取URIparseUri()方法在请求中搜索第一个和第二个空格,从中找出URI

五、Respose类

对目标文件存在与否进行两种不同的处理 如果存在就将文件的内容写入浏览器,否则返回404页面到浏览器 从这个类可以看出,这个类只是简单的文件作为静态资源,将文件的内容写到浏览器中,没有加载servlet的代码

package ex01.pyrmont;import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File;/*HTTP Response = Status-Line*(( general-header | response-header | entity-header ) CRLF)CRLF[ message-body ]Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF*/public class CopyOfResponse {private static final int BUFFER_SIZE = 1024;Request request;OutputStream output;public CopyOfResponse(OutputStream output) {this.output = output;}public void setRequest(Request request) {this.request = request;}//设置静态资源public void sendStaticResource() throws IOException {byte[] bytes = new byte[BUFFER_SIZE];FileInputStream fis = null;try {//获取URI对应磁盘下的文件对象,因为需要用到URI,所以传入request参数File file = new File(HttpServer.WEB_ROOT, request.getUri());if (file.exists()) {//文件存在的话就将页面写到浏览器上fis = new FileInputStream(file);int ch = fis.read(bytes, 0, BUFFER_SIZE);while (ch != -1) {output.write(bytes, 0, ch); //传入输出流是用于将内容写到浏览器上ch = fis.read(bytes, 0, BUFFER_SIZE);}} else {//文件不存在,返回404页面String errorMessage = "HTTP/1.1 404 File Not Found\r\n"+ "Content-Type: text/html\r\n"+ "Content-Length: 23\r\n" + "\r\n"+ "<h1>File Not Found</h1>";output.write(errorMessage.getBytes());}} catch (Exception e) {System.out.println(e.toString());} finally {if (fis != null)fis.close();}}
}

response对象是通过传递由套接字获得的OutputStream对象给HttpServer类的await方法来构造的。Response类有两个公共方法:setRequestsendStaticResourcesetRequest方法用来传递一个Request对象给Response对象,sendStaticResource方法是用来发送一个静态资源,例如一个HTML文件。它首先通过传递上一级目录的路径和子路径给File累的构造方法来实例化java.io.File类。File file = new File(HttpServer.WEB_ROOT, request.getUri())。然后它检查该文件是否存在。假如存在的话,通过传递File对象让sendStaticResource构造一个java.io.FileInputStream对象。然后,它调用FileInputStreamread方法并把字节数组写入OutputStream对象。请注意,这种情况下,静态资源是作为原始数据发送给浏览器的。假如文件并不存在,sendStaticResource方法发送一个错误信息到浏览器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/211777.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解Keras3.0 Models API: Model class

1、语法 keras.Model() 将不同层组为具有训练/推理特征的对象的模型 2、示例一 inputs keras.Input(shape(37,)) x keras.layers.Dense(32, activation"relu")(inputs) outputs keras.layers.Dense(5, activation"softmax")(x) model keras.Model…

58.Nacos源码分析2

三、服务心跳。 3.服务心跳 Nacos的实例分为临时实例和永久实例两种&#xff0c;可以通过在yaml 文件配置&#xff1a; spring:application:name: order-servicecloud:nacos:discovery:ephemeral: false # 设置实例为永久实例。true&#xff1a;临时; false&#xff1a;永久ser…

MySQL-备份+日志:介质故障与数据库恢复

目录 第1关&#xff1a;备份与恢复 第2关&#xff1a;备份日志&#xff1a;介质故障的发生与数据库的恢复 第1关&#xff1a;备份与恢复 任务描述 本关任务: 备份数据库&#xff0c;然后再恢复它。 test1_1.sh # 你写的命令将在linux的命令行运行 # 对数据库residents作海…

【C/C++笔试练习】多态的概念、虚函数的概念、虚表地址、派生类的虚函数、虚函数的访问、指针引用、动态多态、完全数计算、扑克牌大小

文章目录 C/C笔试练习选择部分&#xff08;1&#xff09;多态的概念&#xff08;2&#xff09;虚函数的概念&#xff08;3&#xff09;虚表地址&#xff08;4&#xff09;派生类的虚函数&#xff08;5&#xff09;虚函数的访问&#xff08;6&#xff09;分析程序&#xff08;7&…

C# WPF上位机开发(会员管理软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 好多同学都认为上位机只是纯软件开发&#xff0c;不涉及到硬件设备&#xff0c;比如听听音乐、看看电影、写写小的应用等等。如果是消费电子&#…

HibernateJPA快速搭建

1. 先创建一个普通Maven工程&#xff0c;导入依赖 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><depe…

Java 匿名内部类使用的外部变量,为什么一定要加 final?

问题描述 Effectively final Java 1.8 新特性&#xff0c;对于一个局部变量或方法参数&#xff0c;如果他的值在初始化后就从未更改&#xff0c;那么该变量就是 effectively final&#xff08;事实 final&#xff09;。 这种情况下&#xff0c;可以不用加 final 关键字修饰。 …

报错:Parsed mapper file: ‘file mapper.xml 导致无法启动

报错 &#xff1a; Logging initialized using class org.apache.ibatis.logging.stdout.StdOutImpl adapter. Registered plugin: com.github.yulichang.interceptor.MPJInterceptor3b2c8bda Parsed mapper file: file [/Mapper.xml] application无法启动 我这边产生原因是项…

K8S学习指南(4)-minikube的使用

文章目录 简介安装 Minikube启动 Minikube 集群基本概念创建和管理资源1. 创建 Pod2. 创建 Deployment3. 创建 Service 监视和调试1. 查看集群状态2. 查看集群信息3. 访问 Kubernetes Dashboard4. 使用 kubectl 命令 清理资源1. 删除 Pod2. 删除 Deployment3. 删除 Service4. 停…

! [remote rejected] master -> master (pre-receive hook declined)

! [remote rejected] master -> master (pre-receive hook declined) 如图&#xff1a; 出错解决方法 首先输入命令 git branch xindefenzhi然后&#xff0c;进入这个新创建的分支 git checkout branch然后重新git push就可以了

爬虫学习-基础库的使用(urllib库)

目录 一、urllib库介绍 二、request模块使用 &#xff08;1&#xff09;urlopen ①data参数 ②timeout参数 &#xff08;2&#xff09;request &#xff08;3&#xff09;高级用法 ①验证 ②代理 ③Cookie 三、处理异常 ①URLError ②HTTPError 四、解析链接 ①urlparse ②…

LeetCode-10. 正则表达式匹配

LeetCode-10. 正则表达式匹配 问题分析算法描述程序代码CGo 问题分析 这道题的难点主要在于*号的匹配&#xff0c;这里记dp[i][j]表示s[1...i]和p[1...j]能否完成匹配&#xff0c;先根据特殊情况归纳总结&#xff1a; *号匹配 0 次&#xff0c;则dp[i][j] dp[i][j-2]*号匹配…

Mybatis源码解析4:获取Session、Mapper

Mybatis源码解析4&#xff1a;获取Session、Mapper 1.项目结构2. 源码分析2.1 获取Session DefaultSqlSessionFactory#openSession2.2 获取Mapper DefaultSqlSession#getMapper 1.项目结构 2. 源码分析 2.1 获取Session DefaultSqlSessionFactory#openSession private SqlSe…

利用人工智能算法解决内存垃圾回收问题

内存垃圾回收是计算机领域中的一个重要问题&#xff0c;可以利用人工智能算法解决此问题。常用的人工智能算法包括遗传算法、模拟退火算法、禁忌搜索算法等。 其中&#xff0c;遗传算法是一种基于自然选择和遗传进化的算法&#xff0c;可以用于优化问题。在内存垃圾回收中&…

Python实战演练之Python实现一个简单的天气查询应用

今天&#xff0c;晓白给大家分享Python实现一个简单的天气查询应用&#xff0c;帮助大家获取实时的天气信息&#xff0c;内容仅供学习交流。 首先&#xff0c;我们需要安装一个名为"requests"的Python库&#xff0c;它可以帮助我们发送HTTP请求并获取响应数据。你可…

Kernel(一):基础

本文主要讨论210的kernel基础相关知识。 内核驱动 驱动是内核中的硬件设备管理模块,工作在内核态,程序故障可能导致内核崩溃,程序漏洞会使内核不安全 根文件系统提供根目录,进程存放在根文件系统中,内核启动最后会装载根文件系统 应用程序不属于内核,…

1828_ChibiOS中的对象FIFO

全部学习汇总&#xff1a; GreyZhang/g_ChibiOS: I found a new RTOS called ChibiOS and it seems interesting! (github.com) 1. 最初的这个理解&#xff0c;当看到后面之后就知道有点偏差了。其实&#xff0c;这个传输就是一个单纯的FIFO而不是两个FIFO之间的什么操作。 2.…

去掉参数中第一个“,”

记录一下&#xff0c;前端传参中&#xff0c;传给我参数是“categoryIds: ,1731557494586241026,1731569816263311362,1731569855534579713,1731858335179223042,1731858366821052418” 但是后端&#xff0c;因为我的mybati是in查询&#xff0c;所以因为第一个是“,”。所以会导…

RabbitMQ安装在Linux系统详细教程

安装教程&#xff1a; 1.首先将下载好的文件上传到服务器&#xff0c;拉到opt文件夹中(可以用xftp&#xff09; 2.输入命令&#xff1a; cd /opt 3.安装erlang rpm -ivh erlang-23.3.4.11-1.el7.x86_64.rpm rpm -ivh&#xff08;复制配置文件的名字&#xff09; 4.在Rab…

sap增强

四代增强 2种显示增强1种隐式增强 隐式增强 光标放在增强点或其中的代码点击修改即可修改代码 显示增强 1.ENHANCEMENT-POINT 在代码修改界面选择空行 光标所在位置 可以创建多个增强实施且激活后都会执行. 2.ENHANCEMENT-SECTION 1,选中程序中空行 2.编辑->创建选项 …