MiniTomcat - How Tomcat Works 1: HttpServer

如何实现一个简单的HttpServer。一个基础的Web服务器使用两个重要的类:java.net.Socket和java.net.ServerSocket。

1. HTTP请求

    一个HTTP请求包括三个组成部分:方法—统一资源标识符(URI)—协议/版本,请求的头部,主体内容

HTTP请求例子: 
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

lastName=Franks&firstName=Michael 
    
//方法—统一资源标识符(URI)—协议/版本出现在请求的第一行。  
POST /examples/default.jsp HTTP/1.1 
这里POST是请求方法,/examples/default.jsp是URI,而HTTP/1.1是协议/版本部分。

//请求的头部包含了关于客户端环境和请求的主体内容的有用信息。例如它可能包括浏览器设置的语言,主体内容的长度等等。每个头部通过一个回车换行符(CRLF)来分隔的。
    
//主体内容
lastName=Franks&firstName=Michael

 

 

2. HTTP响应
    HTTP响应包括三个部分:方法—统一资源标识符(URI)—协议/版本,响应的头部,主体内容  

HTTP响应的例子: 
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内容。头部和主体内容通过CRLF分隔开来。

 

 

3. Socket类

    套接字是网络连接的一个端点。套接字使得一个应用可以从网络中读取和写入数据。放在两个不同计算机上的两个应用

    可以通过连接发送和接受字节流。为了从你的应用发送一条信息到另一个应用,你需要知道另一个应用的IP地址和套接

    字端口。在Java里边,套接字指的是java.net.Socket类。要创建一个套接字,你可以使用Socket类众多构造方法中

    的一个。其中一个接收主机名称和端口号:
    public Socket (java.lang.String host, int port)

   

    在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号。例如,要连接yahoo.com的80端口,你需要

    构造以下的Socket对象:
    new Socket ("yahoo.com", 80);

   

    一旦你成功创建了一个Socket类的实例,你可以使用它来发送和接受字节流。要发送字节流,你首先必须调用Socket

    类的getOutputStream方法来获取一个java.io.OutputStream对象。要发送文本到一个远程应用,你经常要从返回

    的OutputStream对象中构造一个java.io.PrintWriter对象。要从连接的另一端接受字节流,你可以调用Socket类的

    getInputStream方法用来返回一个java.io.InputStream对象。

Socket socket = new Socket("127.0.0.1", "8080");
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(
socket.getOutputStream(), autoflush);
BufferedReader in = new BufferedReader(
new InputStreamReader( socket.getInputstream() ));
// send an HTTP request to the web server
out.println("GET /index.jsp HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
// read the response
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);
}
// display the response to the out console
System.out.println(sb.toString());
socket.close();

 

 

4. ServerSocket类
    Socket类代表一个客户端套接字,即任何时候你想连接到一个远程服务器应用的时候你构造的套接字,使用

    java.net.ServerSocket类。服务器套接字的实现。

    ServerSocket和Socket不同,服务器套接字的角色是等待来自客户端的连接请求。一旦服务器套接字获得一个连接请

    求,它创建一个Socket实例来与客户端进行通信。

    要创建一个服务器套接字,你需要使用ServerSocket类提供的构造方法。服务器套接字的另一个重要的属性是

    backlog,这是服务器套接字开始拒绝传入的请求之前,传入的连接请求的最大队列长度。
    public ServerSocket(int port, int backLog, InetAddress bindingAddress);

 

    对于这个构造方法,绑定地址必须是java.net.InetAddress的一个实例。一种构造InetAddress对象的简单的方法是

    调用它的静态方法getByName,传入一个包含主机名称的字符串,就像下面的代码一样。
    InetAddress.getByName("127.0.0.1");

 

    下面一行代码构造了一个监听的本地机器8080端口的ServerSocket,它的backlog为1。

    new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

 

 

5. HttpServer实现

    实现简单HttpServer的3个类:

    HttpServer: 启动ServerSocket开始监听Socket,并处理Request请求,响应Response 

    Request: 处理Request请求

    Response: 返回内容

 

    HttpServer类

import java.net.*;
import java.io.*;

/** WEB_ROOT is the directory where our HTML and other files reside.
  * For this package, WEB_ROOT is the "webroot" directory under the
  * working directory.
  * The working directory is the location in the file system
  * from where the java command was invoked.
  */
public class HttpServer {
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
    
    // shutdown command
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    
    // the shutdown command received
    private boolean shutdown = false;
    
    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.await();
    }
    
    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 request
        while (!shutdown) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;
            try {
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();
            
                // create Request object and parse
                Request request = new Request(input);
                request.parse();
            
                // create Response object
                Response response = new Response(output);
                response.setRequest(request);
                response.sendStaticResource();
            
                // Close the socket
                socket.close();
                
                //check if the previous URI is a shutdown command
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            } catch (Exception e) {
                e.printStackTrace ();
                continue;
            }
        }
    }
}

 

    Request类

import java.io.InputStream;
import java.io.IOException;

public class Request {
    private InputStream input;
    private String uri;
    
    public Request(InputStream input) {
        this.input = input;
    }
    
    public void parse() {
        // Read a set of characters from the socket
        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());
    }

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

 

    Response类

import java.io.*;

/** 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 Response {
    private static final int BUFFER_SIZE = 1024;
    Request request;
    OutputStream output;
    
    public Response(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 {
            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 {
                // file not found
                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) {
            // thrown if cannot instantiate a File object
            System.out.println(e.toString() );
        } finally {
            if (fis!=null)
                fis.close();
        }
    }
}

 

I2C通信协议SCL与SDA引脚特性:核心要点总结 深入讲解I2C通信协议中SCL和SDA引脚的工作机制与电气特性,帮助理解i2c通信协议的稳定传输原理,掌握上拉电阻配置与信号时序控制要点。 阅读详情

相关推荐

W5500 MACRAW模式实战:在ESP32上抓取并解析原始以太网数据包

本文详细介绍了如何在ESP32开发板上使用W5500芯片的MACRAW模式捕获和解析原始以太网数据包。通过硬件连接指南、代码实现示例和协议解析技巧,帮助开发者深入理解网络通信底层机制,实现网络嗅探和自定义协议开发等高级应用。

weixin_42520573的博客 418

MiniTomcat - How Tomcat Works 2: Servlet Container

开发一个简单的Servlet容器 1. Servlet编程是通过javax.servlet和javax.servlet.http这两个包的类和接口来实现的。其中一个至关重要的就是javax.servlet.Servlet接口了。所有的servlet必须实现实现或者继承实现该接口的类。     Servlet接口有五个方法: //在servlet类已经初始化之后,init方法将会被servle...

overkill 163

ERA5 数据集下载(2025最新 可运行)

众所周知,ERA5数据集经过了更新,尤其是在API访问和下载方式上发生了变化。2025年版的ERA5数据集为气象研究人员提供了更高质量的历史气象数据。本文将介绍如何通过新的API接口下载最新的ERA5数据集。

m0_72141699的博客 3590

【计算机网络篇|第二篇】深度解析TCP三次握手

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它在通信双方之间建立一条逻辑连接,保证数据按序到达、不丢失、不重复,并提供流量控制和拥塞控制能力。HTTP、FTP、SSH 等大量应用层协议都构建在 TCP 之上。由于 TCP 是面向连接的协议,通信双方在传输数据之前必须先建立连接,而建立连接的过程就是著名的「三次握手」。

wuzhihua66的博客 499

LabVIEW专栏九、类的继承与多态实践

本文深入探讨了LabVIEW中面向对象编程的核心概念——类的继承与多态。通过构建仪器参数父类与设备基类,并以网口设备为例创建子类,详细演示了如何利用继承实现代码复用,并通过多态机制实现统一接口调用不同仪器类型,从而构建灵活、可扩展的自动化测试与仪器控制系统。

weixin_29235525的博客 249

BIOS更新后PIN不可用怎么办?从原理到实操的完整修复指南

在计算机维护中,BIOS更新是常见的固件升级操作,但其引发的登录异常却常让用户措手不及。Windows Hello PIN并非传统密码,它通过TPM芯片与设备启动环境(PCR状态)强绑定,一旦主板固件或安全启动配置改变,TPM便会拒绝释放密钥,导致“由于此设备上的更改,你的PIN不再可用”。理解这一原理后,恢复路径清晰可控:优先尝试密码登录,或通过WinRE替换utilman.exe创建临时管理员,必要时还需处理BitLocker恢复密钥。本文面向系统维护与数据安全场景,系统梳理PIN失效的恢复流程与避坑方

weixin_31364787的博客 360

【计算机网络】断点续传是怎么做到的:从 Range 请求到分片上传的完整机制

断点续传在下载侧靠的是 HTTP 现成的 Range 协议,Content-Range 保证偏移对得上,If-Range 防止文件变更导致的错误拼接;上传侧没有标准,需要自己设计 init/part/complete 三段式交互,核心是让服务端记住“哪些块已经收下”,从而把失败粒度从整个文件降到单个分片。 这套机制可以迁移到任何长耗时操作的设计上。判断方法很简单:先问失败粒度是多少。如果一次操作失败就得从头再来,那它迟早会在生产环境里出问题——传输、批量导入、数据迁移、长事务,都是同一个道理。把大操作切成

2503_94545876的博客 520

Numpy快速入门(np)!看这一篇就够了

Numpy快速入门(np)!看这一篇就够了

weixin_74009895的博客 2679

第15篇:UDP 深度解析--为什么 HTTP3 要抛弃 TCP?(附 Wireshark 实战抓包)

本文系统解析UDP与QUIC的核心原理与应用场景。UDP以极简无连接、不可靠传输为特点,适用于DNS、直播等对延迟敏感的场景;而QUIC在UDP基础上自建可靠传输,实现0-RTT握手、多路复用、连接迁移与强制加密,彻底解决TCP队头阻塞与协议僵化问题,成为HTTP/3基石。通过实验与抓包验证,深入揭示其底层机制,展现从传统网络向智能算网演进的关键路径。

cyf31的专栏 362

在游戏开发中如何使用Unity3D引擎开发麻将游戏

总体来说使用Unity3D引擎开发麻将游戏需要开发人员熟练掌握Unity3D的各种功能和API,并且需要深入了解麻将游戏的规则和背景,以便实现一个质量高、玩法丰富的游戏。在测试的过程中我们需要检查游戏是否符合规则,保证游戏逻辑的正确性,并检查游戏的UI是否和设计图一致,保证游戏的可玩性。Unity3D引擎是一个高度集成的游戏开发工具,可以为开发人员提供很多强大的工具和功能,包括3D建模、物理模拟、粒子系统、动画等等,在游戏开发中可以提高开发效率和游戏质量。在开始开发之前我们需要明确游戏的需求和功能。

a272857154的专栏 1889

2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录

由于是本人的亲身经历,所以想通过付费专栏获得一些收益。扣除平台的抽成后,我会将最终收益的一半捐献给贫困山区助学基金。专栏内容: 1. 几十场笔试真题的原题纯净版与个人解答版。 2. 几十场面试实录,包含提问、解答与个人复盘建议。 3. 个人整理的知识点总结。 4. 求职准备、简历制作方面的建议。

w0shishabi的博客 1万+

传输层协议UDP和TCP

传输层负责端到端数据传输,主要协议为UDP和TCP。UDP无连接、不可靠、面向数据报,适用于直播、游戏等容忍丢包场景;其报头8字节,含源/目的端口、长度、校验和。TCP有连接、可靠、面向字节流,通过三次握手建立连接、四次挥手释放,具备确认、重传、序号、校验等机制,适用于HTTP、文件传输等对数据完整性要求高的应用。

2503_93979533的博客 235

数字人(对嘴型 Lip-sync)数据集 LRS2 介绍(含网盘链接)

LRS2(Lip Reading Sentences 2)是牛津大学与BBC合作开发的一个大规模唇语识别数据集,专门用于训练和评估数字人唇语同步(Lip-sync)技术。该数据集包含数千条来自BBC电视节目的口语句子,每个句子长度最多100个字符。官方网站为:https://www.robots.ox.ac.uk/~vgg/data/lip_reading/lrs2.html可惜需要认证,不过我已经下载好并存到网盘里了,有需要可适用如下链接下载。(仅供学习,请勿传播)

iioSnail的博客 1685

使用官方的 Echo例程lwip--tcp--udp--跑通测试--deepseek

跑通官方的 Echo Server 例子,已跑通,可是我一点不知道内部发生了啥;恭喜你!从你的截图来看,。Ping 通了,串口打印出了 IP 地址,网络调试助手也成功收到了回显(),这说明你的 Zynq 硬件、网络物理链路、lwIP 协议栈底层机制。你觉得“不知道内部发生了啥”,这太正常了。官方的 Echo 例子把很多底层的初始化封装成了几个函数,小白一开始确实容易“知其然而不知其所以然”。下面我先用大白话帮你把“内部发生了啥”理清楚,然后手把手带你写一个。

AlibabaApple的博客 236

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Linux网络编程:UDP网络协议

UDP(User Datagram Protocol,用户数据报协议)是传输层的一种无连接协议,定义在 RFC 768 中,全文只有三页,是 TCP/IP 协议族中最简单的协议之一。它的核心设计哲学是:只做最少的事,UDP 只负责把应用层交下来的数据封装成数据报,加上端口号后交给网络层发送,不保证送达、不保证顺序、不保证不重复,也不做流量控制和拥塞控制。所以需要注意udp是用户数据报协议数据报1.数据与数据之间有边界2.发送和接收的次数要对应3.没有写阻塞。发送太快,会导致数据丢失。

qianrushixinshou的博客 385

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

基于springboot+vue+uniapp的羽毛球预约小程序(Websocket智能沟通、Echarts数据分析、会员闭环管理、灵活运营规则、全流程预约)

本系统基于B/S架构,采用前后端分离设计,集成微信小程序与管理后台,实现多角色权限管控。核心亮点包括:全流程可视化预约、灵活运营规则配置、会员闭环管理(含余额、充值、退款)、ECharts多维度数据分析及WebSocket智能客服。支持语音、图片沟通,提升服务效率。系统覆盖用户、工作人员、管理员全链路功能,实现场馆资源高效管理与用户体验优化。

paopaokaka_luck的博客 225

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

11.4【计算机网络】

m0_73553411的博客 200

HTTP、SSE、WebSocket、Socket.IO:实时通信方案深度对比与选型指南

HTTP 是基础,是整个 Web 的底座;SSE 是单向推送的轻量最优解;WebSocket 是高性能双向实时通信的底层标准;Socket.IO 是一套降低开发门槛、抹平环境差异的上层框架。技术选型永远不是“哪个技术更酷”,而是匹配业务的实时性、交互方向、网络环境、团队维护成本。简单需求优先轻方案,复杂双向实时再上 WebSocket;如果追求快速落地、兼容各种奇葩网络,Socket.IO 是很好的选择。

changshuaihua001的博客 338

计算机网络篇20:网络层——IP 协议的定位与 IPv4 报头字段,从 20 字节首部到 TTL 防环

TCP 报文要向下交给网络层才出得了主机,封装与解包是分层的物理形态。

2502_94387000的博客 831

WEISS MSI 接口技术拆解:WPG 高精密夹爪 1ms 控制周期实时插补,UDP 流式运动控制取代单指令抓取架构

MSI 采用流式控制模型:外部控制器经 UDP 实时流持续下发基础点(目标位置与力限值),WPG 夹爪内部将其插补至 1ms 控制周期。官网技术资料显示,外部连续运动控制与夹爪插补缓冲区(最高约 500ms)配合,运动逻辑完全保留在外部系统。

欣佰特cnbestec的博客 658

从零入门计算机网络系列(十七):TCP可靠传输机制进阶——从序列号与丢包处理到超时重传

本文承接上一讲TCP窗口与控制标志位,继续深入序列号、确认应答和超时重传。借助发送缓冲区下标类比,理解TCP如何为字节流编号,通过确认序号区分已确认与待确认数据。随后从发送方视角分析数据报文丢失和确认应答丢失两种情况,解释确认丢失为什么可能触发重复发送,以及接收方如何利用序列号识别重复数据,保证应用层获得有序、无重复的字节流。最后围绕“多久没收到确认才需要重传”,讨论超时时间过长与过短的影响,介绍TCP如何根据网络往返时间动态调整重传超时值,深入理解TCP可靠传输的实现逻辑。

沫漓的博客 353

计算机网络篇15:传输层——TCP 报头:4 位首部长度切出可变报头,确认应答与序号撑起可靠性

TCP 报头是 20 字节固定加选项,以 4 字节为单位,字段取值 [5, 15],报头范围 [20, 60]按首部长度读满为止,其后全是有效载荷。

2502_94387000的博客 751

TCP Socket 与 UDP Socket

Socket 是网络编程的基础,面试中 TCP 和 UDP Socket 的调用流程、内核行为、函数区别、缓冲区机制属于高频考点。很多同学容易混淆accept返回的 fd 作用、三次握手发生时机、UDP时序、内核缓冲区行为等细节。本文结合调用流程图,逐步骤拆解 TCP、UDP Socket 流程,梳理易错点和对比总结。TCP:服务端 socket-bind-listen-阻塞accept;客户端 socket-connect;握手成功后双向读写,最后 close 挥手断开。

weixin_68315058的博客 370

为什么要把父类的析构函数定义成虚函数?

问:为什么要把父类的析构函数定义成虚函数?答:否则子类的析构函数可以未被调用,具体代码如下:class A {public: A(){TRACE("A()/n");}; ~A(){TRACE("~A()/n");}; };class B : public A {public: B(){TRACE("B()/n");}; ~B(){TRACE("~B()/n");};};void CEgDl

闻缺陷则喜何志丹 1507

socket编程基础+UDP套接字

不能bind公网---->公网IP其实没有配置到你的IP上,公网IP无法被直接bind,如果我们显示的进行bind,client来访问的时候就必须使用server端bind地址信息 不建议bind特定的IP。本地环回:要求c,s必须在一台机器上,表面我们是本地通信,client发送的数据不会被推送到网络,而是在os内部,转一圈直接交给对应的机器。2>从技术角度,pid也可以,但pid系统的一个概念,pid变,网络也跟着变,则干脆分开,解耦。socket会有很多的种类,来,满足不同的应用场景。

2402_88539534的博客 78

net core下WebSocket请求总是断开的解决方案

然后通过AIge各种搜索,各种踩坑后找到最终原因:ws在连接时候,在一定时间内(不同浏览器还不一样),前端和后端必须保持通讯,否则浏览器会基于安全考虑断开连接。最近在写一个基于B/S架构的聊天功能,本地测试没发现问题,但是部署到生产环境后总是断开。开始以为是后端超时问题,加大了KeepAliveInterval的时间。通俗理解就是:前端需要给后端发起请求,后端也需要定时给前端回复信息。然后,测试后发现只要没有主动发送聊天,一段时间后该断开还是断开。到此,ws协议断开的问题得到解决,希望可以帮助到大家。

243
上一篇: Effective Java
下一篇: MiniTomcat - How Tomcat Works 2: Servlet Container
book_overkill
博客等级 码龄20年 1粉丝 1原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值