【Linux开发—基础】

一,环境搭建

1,编译器gcc ,g++ 安装

用gcc 编译c++代码,逻辑是gcc调用g++去编译c++代码
命令:

  • 1,设置root 密码: sudo passwd root,请牢记这个密码,后面还需要使用,如果是云服务器密码建议复杂一点。
sudo passwd root
  • 2,进入root账户 su,提升权限,($变成#号)
su

在这里插入图片描述

  • 3,安装gcc 和g++编译器:apt-get install gcc g++,会科技上网的走墙一下,不会的把源切到国内,比如:清华大学的源,可在网上搜索资料。
apt install gcc
apt install  g++

在这里插入图片描述

  • 4,查看当前的gcc g++版本,gcc --version ,g++ --version
gcc --version
g++ --version

在这里插入图片描述
在这里插入图片描述

  • 5,验证:g++ -o test main.cpp ,./test,意思是:g++编译 main.cpp文件, -o 生成一个test
g++ -o test main.cpp 
./test

2,ssh服务安装

这个服务 用户后续的代码编写和远程运行、调试。
命令:

  • 1,安装服务程序:sudo apt install openssh-server
sudo apt install openssh-server
  • 2,安装客户端程序:sudo apt install openssh-client,因为客户端有密钥生成器,可用去创建一些密钥,证书等
sudo apt install openssh-client
  • 3,修改配置文件:在root目录的,etc/目录下,先su到root用户,然后进入到etc文件夹cd etc,查找到sshls -l | grep ssh,再然后在ssh文件夹里找sshd_config文件,vim 进到文件vim sshd_config,修改完成后:wq保存并退出
su
cd etc
ls -l | grep ssh
cd ssh
vim sshd_config

在这里插入图片描述
在这里插入图片描述

Port 22
LoginGraceTime 2m
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
(红色内容是主要修改的地方,其他地方自行对比,如果不一致,请参考上面文档)

3,ssh服务启动:

  • 方式一,输入命令
C:\Windows\System32\bash.exe -c "sudo service ssh start"
  • 方式二,建立bat文件,将上面方式一的命令写入其中,然后放入文件夹C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp或者C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup,用户名填自己的用户名,这样就可以开机启动服务

二,Windows下开发Linux

1,创建Linux控制台项目

  • 1,MicroSoft Visual Studio创建Linux桌面程序

  • 2,工具-选项-跨平台-连接管理器添加Linux主机,主机:填Ubuntu所在主机ip,用户名:root,密码:设置的Ubuntu密码,我这里一直连接不上,后来查资料,发现可能是:ipv6的问题
    在这里插入图片描述

  • 3,127.0.0.1是本地localhost主机名,如果对方系统添加了gcc和g++,非Ubuntu系统也可以建立连接。
    在这里插入图片描述

  • 4,成功建立连接(在成功的路上遇到点小问题,详见总结 一):
    在这里插入图片描述

  • 5,工程属性配置:
    在这里插入图片描述

  • 6,目前Linux系统目前大部分是 x64的架构,很少有32位的了,不用arm64和x86了。然后生成解决方案:
    在这里插入图片描述

  • 7,生成解决方案,本地会有一份,在Windows环境下无法打开
    在这里插入图片描述

  • 8,编译文件验证,Ubuntu中找到文件:
    在这里插入图片描述
    与visual studio工具中main.cpp文件内容一样:
    在这里插入图片描述
    Ubuntu打开编译产物:
    在这里插入图片描述

  • 8,针对刚才visual studio工具的错误内容,是Linux服务器缺少gdb zip:
    在这里插入图片描述
    切换到Ubuntu服务器,sudo apt install gdb,sudo apt install zip,执行gdb -v查看安装版本

sudo apt install gdb
sudo apt install zip

在这里插入图片描述

2,Linux系统API认识(标准库函数)-基础

1,字符串函数

  • 头文件<ctype.h> 下。判断字符类型,大小写字母,中英文,是否为空格等。

2,数据转换函数

  • 头文件<stdlib.h> 下。将字符串转换成浮点数,整型数,无符号整数等等,将浮点数,整数等转换成字符串。注意,有些是C++11标准的东西。

3,控制台函数

  • 格式化输入输出,printf,vprintf,scanf,sprintf,snprintf,sscanf,fprintf,scanf

4,权限控制函数

  • 头文件:<unistd.h>,<sys/types.h>

  • Linux的权限:(文件,进程,内存 Linux下一切都是文件

    • 设置用户权限,S 提权和降权
    • 设置组权限, s 修改我当前的组
    • 仅所有者可删除权限,t
    • 读取权限, (r 4)
    • 写入权限, (w 2)
    • 执行权限, (x 1)
  • (所有者,所在组,其他组)
    在这里插入图片描述

  • 查看进程,ls -l /proc

  • uid_t geteuid(void),获得有效的用户识别码

  • uid_t getuid(void),获取真实用户的识别码

  • gid_t getegid(void),获取有效的组识别码

  • gid_t getgid(),获取真实的组识别码

  • int setuid(uid_t uid),设置真实的用户识别码

  • int seteuid(uid_t uid),设置有效的用户识别码

  • int setreuid(uid_t ruid, uid_t euid),设置真实及有效的用户识别码

  • 用户权限说明

    • 0 root 最高权限,
    • 1000~10000(不包含10000) system 数据库 服务 tty 保留用户
    • 10000(包括)以上,其他用户 网络用户, Android每个app会分配一个用户(系统应用例外)
  • 权限不足时,无法产生效果,提权需要该文件属于高级别的用户或用户组,即有效用户有更高的权限,或以更高权限的用户来执行。
    在这里插入图片描述

  • int setregid(gid_t rgid,gid_t egid),设置真实及有效的组识别码

  • int setegid(gid_t egid),设置有效的组识别码

  • int setgid(gid_t gid),设置真实的组识别码

  • pid_t setsid(void),创建会话ID,守护进程的关键调用函数,用户和组要有足够的权限。或者创建新会话(当前进程是子进程才可以创建新会话成功)。
    查看所有进程:ps -Al

ps -Al

在这里插入图片描述

5,I/O函数

  • 头文件<sys/types.h>,<unistd.h>,<sys/stat.h>,<fcntl.h>,<stdlib.h>,
  • *int open(const char pathname, int flags, mode_t mode),打开文件,相对路径,标识,权限(有O_CREAT时,mode才有作用)
    • flags: 多个标识用 | 连接
      • O_RDONLY:只读打开
      • O_WRONLY:只写打开
      • O_RDWR:读,写打开
      • O_CREAT:若文件不存在 则创建,需要使用mode选项,来指明新文件的访问权限
      • O_APPEND:追加写,如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不是覆盖原内容
    • mode: 多个标识用 | 连接
      • S_IRUSR S_IWUSR S_IXUSR 所有者的读写执行
      • S_IRGRP S_IWGRP S_IXGRP 所属组的读写执行
      • S_IROTH S_IWOTH S_IXOTH 其他用户的读写执行
  • int creat(const char*pathname, mode_t mode),创建文件
文件最主要的操作:打开、读取、写入、关闭

头文件<unistd.h>

  • ssize_t read(int fd, void *buf,size_t count):从打开的文件读取文件数据,允许把多个不同该文件数据,读入到同一个缓冲区,网络读取缓冲区,指向结构体(用于文件格式解析)ELF文件格式,还可以用于网络数据包的解析。
  • ssize_t write(int fd, const void *buf, size_t count):从打开的文件写入文件数据,fd可以是网络、串口、文件、内存、其他设备,同一个内存段的不同部分,可以保存到不同的文件中
  • int close(int fd):关闭文件,Linux(或其他操作系统)当中可以同时打开的文件数量是有限的,一般是4096个文件,不用的时候及时关闭
  • int dup(int oldfd):复制文件描述符,输出重定向
  • int dup2(int oldfd, int newfd):复制文件描述符到新的文件描述符中去,文件重定向
  • int fsync(int fd):文件数据同步(将缓存中的数据写入磁盘),慎用,比较消耗时间,到磁盘缓存区,一般迁库使用
文件读写位置修改

头文件<sys/types.h>,<unistd.h>

  • off_t lseek(int fd, off_t offset, int whence):文件读写位置修改
    • whence:
      • SEEK_SET:参数offset即为新的读写位置,从开头设置的不能为负数,从SEEK_CUR或SEEK_END时,参数offet允许负值出现
      • SEEK_CUR:已目前的读写位置往后增加offset个位移量
      • SEEK_END:将读写位置指向文件尾后再增加offset个位移量。

头文件<stdlib.h>

  • int mkstemp(char *template):创建一个临时文件,template:前缀+XXXXXX,前缀可以是任意字母,后缀必须是六个X,文件是临时的,不保证数据长期有效。
文件锁操作

文件锁是一个建议性的锁,去看文件的时候才有用,不去看时,没什么用。若是要锁有保护效果,先上锁,再写入,写完再解锁,或关闭文件自动解锁。

  • int flock(int fd, int operation):文件锁操作

    • operation:
      • LOCK_SH:建立共享锁定。多个进程可同时对同一个文件作共享锁定。
      • LOCK_EX:建立互斥锁定。一个文件同时只有一个互斥锁定。
      • LOCK_UN:解除文件锁定状态。文件被关闭时,自动解锁。可能存在时间差。
      • LOCK_NB:(NonBlock非阻塞)无法建立锁定时,此操作可不被阻断,马上返回进程。使用于多实例场景。通常与LOCK_SH或LOCK_EX做OR(|)组合。
  • 意义
    1,防止文件被篡改导致冲突。
    2,防止程序多实例(多使用于数据库),要用到LOCK_NB标志来添加锁定
    3,多个程序竞争文件控制权(多使用于消息分发、文件处理)

文件控制

头文件<fcntl.h>control=cntl

  • int fcntl(int fd, int cmd, …/* arg */):文件控制
  • int fcntl(int fd, int cmd)
  • int fcntl(int fd, int cmd, long arg)
  • int fcntl(int fd, int cmd, struct flock *lock)==>F_GETLK F_SETLK
    • cmd参数:
      • F_DUPFD:用来查找 >= 参数arg的最小且仍未使用的文件描述符,且复制参数 fd 的文件描述符。执行成功则返回新复制的文件描述符。新描述符与 fd 共享同一文件表项,但新描述符有它自己的一天文件描述符标志,其中FD_CLOEXEC文件描述标准被清除。参考dup2()函数
      • F_GETFD:取得close-on-exec标志。若此标志的FD_CLOEXEC位为0,代表在调用exec()相关函数时 文件将不会关闭。(使用场景:管道)
      • F_SETFD:设置close-on-exec标志。该标志以参数 arg 的FD_CLOEXEC位决定。
      • F_GETFL:(FL=flags)取得文件描述符状态标志,此标志为open()的参数flags。
      • F_SETFL:设置文件描述符状态标志,参数为 arg 新标志,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
      • F_GETLK:(LK=Lock)取得文件锁定状态。
      • F_SETLK:设置文件锁定状态。此时flock结构的 I_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。若无法建立锁定,返回-1,错误code为:EACCES 或 EAGAIN
      • F_SETLKW: 与F_SETLK作用相同,但无法建立锁定时,此调用会一直等到锁定动作成功为止。若再等待锁定的过程中被信号中断,则立即返回-1,错误code为:EINTR

6,进程控制函数

进程是操作系统调度的最小单位。
头文件<unistd.h>

1,建立及启动部分

argv的第一个参数,一定要是命令的自身:
在这里插入图片描述

  • int execl(const char *path, const char *arg, …)
  • int execlp(const char *file, const char *arg, …)
  • int execle(const char *path, const char *arg, …, char *const envp[])
  • int execv(const char *path , char *const argv[])
  • int execvp(const char *file, char *const argv[])
  • int execve(const char *filename, char *const argv[], char *const envp[]),内核级别调用

l:进程执行的参数,以可变参数的形式给出,这些参数以NULL为最后一个参数。
p:exec 会将当前的PATH作为一个参考环境变量
e:进程函数会需要用户来设置这个环境变量
v:进程函数会用参数数组来传递argv,数组的最后一个成员,必须是NULL,否则无法执行。

  • pid_t fork(void),建立一个新的进程,
    • 返回值:
      • 大于0的数,此时是父进程;
      • 等于0的数,此时是子进程;
      • 小于0的数,表示调用失败。
    • 进程的数量是优先的,一般1 ~ 32768/32767/65535 短整型大小(根据系统),大型服务器可以更多。
  • void abort(void),异常终止,以异常方式结束进程
  • void assert(int expression),断言终止,若测试条件不成立则终止进程
  • void exit(int status),正常结束进程。会调用atexit和on_exit函数
  • void _exit(int status),结束进程执行。
  • int atexit(void (*func)(void)),设置程序正常结束前调用的函数
  • int on_exit(void (function)(int, void), void *arg),设置程序正常结束前调用的函数。
2,改变堆栈部分

C语言当中处理异常的一种机制,异常捕获,同时清理寄存器内容(做跳转可考虑存放到缓存)。

  • int setjmp(jmp_buf environment),保存目前堆栈环境,jum_buf储存的是寄存器信息

  • void longjmp(jmp_buf environment, int value),跳转到原先setjmp保存的堆栈环境

  • int sigsetjmp(sigjmp_buf env, int savemask),保存目前堆栈环境,可用于线程切换,保存上下文(堆栈、当前寄存器、当前状态、下一条指令位置、栈内存地址),比setjmp内容多了信号量signal。

  • void siglongjmp(sigjmp_buf env, int val),改变进程优先顺序:跳转到原先sigsetjump保存的堆栈环境

3,进程识别部分

头文件<sys/resource.h>

  • pid_t getpgid(pid_t pid),获取进程组 识别码
  • pid_t getpgrp(void),获取当前进程组 识别码
  • pid_t getpid(void),获取进程 识别码
  • pid_t getppid(void),获取父进程 识别码
  • int getpriority(int which, int who),获取程序进程执行优先权
  • int setpgid(pid_t pid, pid_t pgid),设置进程组 识别码
  • int setpgrp(void),设置当前进程组 识别码
  • int setpriority(int which, int who, int prio),设置程序进程执行优先权
  • int nice(int inc),改变进程优先级
    设置和修改进程优先级时,需要权限(有效用户的权限)
4,执行shell命令:

头文件<stdlib.h>

  • int system(char *command)
5,等待子进程中断或结束

头文件<sys/types.h>,<sys/wait.h>

  • int wait(int *status),等待子进程的状态,一般跟fork配合使用。缺陷:无法指定等待子进程
    • 原理:
      先调用fork,然后调用wait(父进程);
      子进程销毁时,向父进程报告(发送SIGCHILD);
      若父进程没有接收这个报告,则子进程可能成为僵尸进程(会占用内存和进程ID)
  • pid_t waitpid(pid_t pid, int *status, int options),等待子进程中断或结束,(使用场景:逆向破解注入)
    • options:
      • WNOHANG:非阻塞
      • WUNTRACED:被调试
      • WCONTINUED:发生了信号导致进程暂停(SIGSTOP,SIGPAUSE,SIGCONT),由系统来处理
    • status:
      • WIFEXITED:是否退出,枚举status
      • WEXITSTATUS
      • WIFSIGNALED:是否有信号导致暂停,枚举status
      • WTERMSIG
      • WIFSTOPPED:停止状态,枚举status
      • WSTOPSIG:信号导致结束

7,文件和目录函数

磁盘结构(old):柱头,盘片,扇区

1,文件操作函数:
//----------------------------打开----------------------------
/*
- stream:标准输出的文件指针,stdout
- mode:
* r:读,
* w:写,
* +:创建,
* a:追加, 
* b:二进制,
* t:文本
*/ 
FILE *fopen(const char* path, const char *mode);//打开文件, 不能指定权限,默认权限:rw-rw-r-- 或 rw-r--r--
int fclose(FILE *fp);//关闭文件,必须要关闭,文件打开的个数有限,用完之后及时关闭

/*用open函数打开文件的时候得到一个fd,
可以把文件描述符fd转换为文件指针FILE* 
*/
FILE *freopen(const char *filename, const char *mode, FILE *stream);//打开文件,输入输出重定向,(场景:后台开发,服务器开发)

FILE *fdopen(int fildes, const char *mode);//将文件描述符转换为文件指针
int fileno(FILE *stream);//返回文件流所使用的文件描述符,文件指针FILE* 转换为文件描述符fd



//----------------------------读取----------------------------
/* 
- buffer:缓冲区,单次内存分配不要超过2G, 64为系统不要超过8G,因为分配的是连续空间
- size:每次读取的字节数量
- count:读取次数
- stream:文件指针,指明在哪个文件操作
*/
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);//从文件流读取数据

int fgetc(FILE *stream);//由文件中读取一个字符 c=char

/*fgets()
文件必须以t(文本)模式打开,不能以b二进制模式打开,
若以\0或\n为终止符,或发现了EOF(End Of File),会因为不知道什么时候结束,而出现严重问题;
换行符会被读入到str所指向的内存里面;
文件内部是有一个文件指针(文件共用的文件指针);
会记录上次读/写的位置,读写的位置是分开的,但是,是统一记录的;
每次读写,都可以改变文件中指针的位置
*/
char *fgets(char *str, int n, FILE *stream) //由文件中读取一个字符串 s=string


//----------------------------写入----------------------------
int fputc(int c, FILE *fp);//将指定字符写入文件流

/* fputs()
- 1, 遇到str中的\0字符时,终止写入;
- 2, str必须是标准C语言字符串(以0或者空字符串为结尾的字符串);
*/
int fputs(const char *str, FILE *stream);//将指定字符串写入文件,

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE* stream) //将数据写入文件流
int fflush(FILE *stream) //更新缓冲区,文件大的话比较耗费时间,一般,文件是在磁盘上,打开文件时是在内存中,写的过程中也是在内存中去写,是刚写完之后,并没有直接更新到磁盘上,直接去读取的话,读取的还是原来的文件;需要更新内存缓冲区和硬盘缓冲区,最后写到磁盘上。

/*fseek
- fromwhere:
	SEEK_SET:从文件开头指针,计算位置,offset不可为负数
	SEEK_CUR:从文件当前指针,计算位置,offset可为负数
	SEEK_END:从文件尾部指针,计算位置,offset可为负数
*/
int fseek(FILE *stream, long offset, int fromwhere);//移动文件流的读写位置,在跳转的时候,和文件实际的大小不一定一样,可以超过实际大小
int fsetpos(FILE *stream, const fpos_t *pos);//移动文件流的读写位置,通过修改pos.__pos的值 来进行跳转
long ftell(FILE *stream);//获取文件流的读取位置
int fgetpos(FILE* stream, fpos_t *pos);//获取文件流的读取位置,64位文件读写

int feof(FILE *stream);//检查文件流是否读取到了文件尾,文件未到尾部,则为0,否则为非0,文件读到最后一个字节,并不会触发feof,必须要再读取内容,才会触发feof
void clearerr(FILE *stream);//清除文件流的错误标识
int ferror(FILE *stream);//获取指定文件的错误标识
  • fread 函数中,读取文件数据,如果每次读取的数据量比较大,count不准确,而其返回值跟count有关,因此返回值也不准确
  • fgets 文件必须以t(文本)模式打开,不能以b二进制模式打开,若以\0或\n为终止符,或发现了EOF(End Of File),会因为不知道什么时候结束,而出现严重问题
  • fputs 中 str必须是标准C语言字符串(以0或者空字符串为结尾的字符串)
  • fseek 在跳转的时候,和文件实际的大小不一定一样,可以超过实际大小
  • feof 文件读到最后一个字节,并不会触发feof,必须要再读取内容,才会触发feof
2,目录的操作函数

<sys/types.h>
<sys/stat.h>
<unistd.h>
<dirent.h>

//----------------------------创建文件夹----------------------------
int mkdir(const char *pathname, mode_t mode);//创建文件夹

//----------------------------删除文件夹----------------------------
int rmdir(const char *pathname);//删除空文件夹
int remove(const char *pathname);//删除文件或者空的文件夹,也可删除链接

//----------------------------修改目录----------------------------
//可用system函数替代:chown 用户.组 目标文件或文件夹
/* 一般而言,用户和组的代码是一样的,eg:root用户有专属的组,就是root组,system用户对应 system组,个人用户对应个人用户组,
owner和group也是一样的
*/
int chown(const char *path, uid_t owner, gid_t group);//修改文件或者目录的用户或组
int chmod(const char *path, mode_t mode);//修改文件或者目录的权限

/* 删除链接,也可以删除文件
* ln -s 源文件地址 链接文件地址
*/
int unlink(const char *pathname);//删除链接


DIR *opendir(const char *name);//打开目录
struct dirent *readdir(DIR *dir);//读取目录
int closedir(DIR *dirp);//关闭目录

struct dirent 
{
	long d_ino;//inode number 索引节点号
	off_t d_off;//offset to this dirent 在目录文件中的偏移
	unsigned short d_reclen;//length of this d_name文件名长
	unsigned char d_type;//the type of d_name 文件类型
	char d_name [NAME_MAX+1];//file name [null-terminated]文件名,最长255字符
}

/*
- d_type:
	DT_BLK:块设备
	DT_CHR:字符设备
	DT_DIR:目录
	DT_LNK:软连接
	DT_FIFO:管道
	DT_REG:普通文件
	DT_SOCK:套接字文件
	DT_UNKNOWN:未知
*/
  • 删除链接:
    在这里插入图片描述
void mulu_func()
{
    DIR* proot = opendir("~");//获取当前目录
    if (proot == NULL)
        printf("%s(%d):%s ~ is not exist!\n", __FILE__, __LINE__, __func__);
        return;

    dirent* pCurrent = NULL;
    do {
        pCurrent = readdir(proot);
        if (pCurrent != NULL) {
            if (strcmp(pCurrent->d_name, ".") == 0 || strcmp(pCurrent->d_name, "..") == 0)
                continue;
            if (pCurrent->d_type & DT_DIR) {
                printf("%s(%d):%s unlink = %s\n", __FILE__, __LINE__, __func__, pCurrent->d_name);

            }
        }
    } while (proot != NULL);
}

三,总结

一,ssh公钥问题

1,建立连接-公钥问题

  • 实操过程中,visual studio添加连接时,一直出现弹窗,
    请添加图片描述
  • 后来发现是sshd_config配置文件里面,配置的 PubkeyAuthentication yes 开启公钥验证,的问题,导致启动ssh服务器时:
sshd: no hostkeys available -- exiting

在这里插入图片描述

  • 注释掉公钥验证,后,重启服务器。

2,查看ssh服务,重启ssh服务

  • 通过
ps -e | grep ssh

查看ssh服务是否开启
发现输入命令之后没有反应

  • 安装ssh
    接着,我们安装下ssh
 sudo apt-get install openssh-server openssh-client

提示已安装
在这里插入图片描述

3,尝试启动ssh服务

使用

service ssh start

结果报错个问题,提示sshd: no hostkeys available – exiting.
在这里插入图片描述

解决办法
查看资料看到如下操作方式可以解决此问题

ssh-keygen -A
/etc/init.d/ssh start

在这里插入图片描述

二,权限问题

Linux系统里面,一切皆文件,对文件的操作,都需要相对应的权限。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

☆MOON

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值