2018秋季校园招聘个人笔、面试知识点总结

TCP协议:

TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种断点我们叫作套接字(socket),它的定义为端口号拼接到IP地址即构成了套接字,例如,若IP地址为192.3.4.16 而端口号为80,那么得到的套接字为192.3.4.16:80。
TCP报文首部:
图1
图2
图3
TCP三次握手过程(上图所示):

  1. 客户端发送一个包含SYN(同步序列编号)标志的请求同步报文给服务器端,同时指明其使用的端口和初始序列号seq=x;
  2. 服务器在收到客户端发送的SYN报文后,将返回一个SYN+ACK报文,表明客户端的请求已经接收到,并将收到的客户端初始序列号x自动加1,同时也将自己的初始序列号y发送给客户端;这时服务器端将处于SYN-RCVD状态,即等待接收来自客户端的确认;
  3. 客户端在收到服务器发回来的ACK报文后,也将再次返回一个ACK报文,表明服务器的请求被接受,接下来可以开始建立连接;返回的报文包括客户端的序列号加x+1,收到的服务器端的序列号y+1;
    seq是数据报文本身的序列号,ack是期望对方继续发送的报文的序列号。均为32位。

经过这三步,TCP的连接就建立完成。TCP协议为了实现可靠传输,在三次握手的过程中设置了一些异常处理机制。第三步中,如果服务器没有收到来自客户端的最终确认ACK报文,会一直处于SYN-RCVD状态,将客户端的IP加入等待列表,并重发第二步的SYN+ACK报文。重发一般进行3-5次,大约间隔30秒左右轮询一次等待列表重试所有客户端。另一方面,服务器在自己发出SYN-ACK报文后,会预分配资源为即将建立的TCP连接储存信息做准备,这个资源在等待重试期间一直被保留。更为重要的是,服务器的资源是有限的,可以维护的SYN-RCVD状态在超过极限后就不再接收新的SYN报文,也就是拒绝新的TCP连接建立。
图4
四次挥手过程(上图所示):

  1. 客户端进程发出连接释放报文,并停止发送数据。释放报文首部,FIN=1,其序列号为seq=u(等于前面已传送过来数据的最后一个字节的序号+1),此时,客户端进入FIN-WAIT-1状态。TCP规定,即使FIN报文段不携带数据,也会消耗一个序号。
  2. 服务器接收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并带上自己的序列号v,此时,服务端进入CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层应用进程,客户端向服务器的方向就释放了,这时处于半关闭状态,因为服务端可能还有数据要发送,客户端依然需要接收。这将会持续到CLOSE-WAIT时间结束。
  3. 客户端接收到服务端发送的确认报文后,就进入FIN-WAIT-2状态,等待服务端发送连接释放报文(在这之前,可能还要接收服务端发送的最后的数据)。
  4. 服务端将最后的数据发送完毕后,会向客户端发送连接释放报文,FIN=1,ACK=1,seq=w,ack=u+1,并进入LAST-ACK(最后确认)状态,等待客户端的确认。(因为服务端在半关闭状态又向客户端发送了数据,所以此时的序列号为w)
  5. 客户端在收到服务端的连接释放报文后,必须做出确认应答,ACK=1,seq=u+1,ack=w+1,此时客户端进入TIME-WAIT(时间等待)状态。注意:此时TCP连接还没有释放,必须等待2*MSL(最长报文段寿命)时间后,才进入CLOSED状态。当客户端撤销相应的TCB后,结束本次TCP连接。
  6. 服务端只要接收到客户端发出的确认应答后,立即进入CLOSED状态,同样,撤销TCB后,结束本次TCP连接。(因此,服务端结束TCP连接的时间比客户端早)

Q:为什么客户端最后还要等待2*MSL?
A:MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
  第一,保证客户端发送的最后一个ACK报文能够到达服务端,因为这个报文可能会丢失。如果丢失,服务端就会重传FIN+ACK报文,因此客户端在这个时间段内就能接收到这个重传的报文,并发出确认应答报文,然后重启2MSL计时器。
  第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间段中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

Q:为什么建立连接是三次握手,关闭连接确是四次挥手呢?
A:建立连接的时候,服务器在LISTEN状态下,收到连接建立请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而在关闭的时候,服务端收到客户端的FIN报文时,仅仅表示客户端不能再发送数据,但还能接收数据的,并且自己也未必将数据全部都发送给对方。所以自己可以立即关闭,也可以在发送给对方一些数据后,再发送FIN报文来表示自己可以关闭连接了。因此,己方的ACK和FIN报文一般会分开发送,从而导致多了一次。

Q:如果已经建立了连接,但是客户端突然出现故障了怎么办?
A:TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

TCP连接可靠性的保证:
1) 校验和
2) 序列号(按序到达)
3) 确认应答(ACK)机制
4) 超时重传机制
5) 面向连接
6) 流量控制
  滑动窗口
7) 拥塞控制
  慢启动(指数增长)- 拥塞 - 阈值(线性增长)

UDP协议

  UDP(User Datagram Protocol),用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是其正式规范。UDP提供了无连接通信,且不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP传输的可靠性由应用层负责。
  UDP常用的端口有:DNS 53;TFTP 69;SNMP 161
  UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。

· UDP使用

  在网络质量不好的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。比如我们聊天用的QQ就是使用的UDP协议。

· UDP具有TCP所望尘莫及的速度优势

  虽然TCP协议中植入了各种安全保障功能,但是在实际执行的过程中会占用大量的系统开销,无疑使速度受到严重的影响。而UDP由于排除了信息可靠传递机制,将安全和排序等功能移交给上层应用来完成,极大降低了执行时间,使速度得到了保证。

· UDP报头

UDP在IP报文中的位置如下图所示:
图5
其报头由4个域组成,每个域占2个字节:
图6
  UDP协议使用端口号为不同的应用保留其各自的数据传输通道。UDP和TCP协议正是采用这一机制实现对同一时刻内多项应用同时发送和接收数据的支持。数据发送一方(可以是客户端或服务器端)将UDP数据包通过源端口发送出去,而数据接收一方则通过目标端口接收数据。有的网络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态端口。因为UDP报头使用两个字节地址长度来存放端口号,所以端口号的有效范围是从0到65535。一般来说,大于49151的端口号都代表动态端口。

· UDP的特性
  1. UDP是一个无连接协议,发送端和目的端在传输数据之前不建立连接,当UDP需要传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它放到网络上。在发送端,UDP传送数据的速度仅受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
  2. 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。
  3. UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。
  4. 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、发送端和目的端主机性能的限制。
  5. UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。
  6. UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。

OSI模型:
图7

  1. DNS域名解析
    · 在浏览器DNS缓存中搜索
    · 在操作系统DNS缓存中搜索
    · 读取系统hosts文件,查找其中是否有对应的ip
    · 向本地配置的首选DNS服务器发起域名解析请求
  2. 建立TCP连接
  3. 发起HTTP请求
  4. 接收响应结果
    图10
  5. 浏览器解析html
    图11
  6. 浏览器布局渲染
    · 布局:通过计算得到每个渲染对象在可视区域中的具体位置信息(大小和位置)。这是一个递归过程。
    · 绘制:将计算好的每个像素点信息绘制在屏幕上。

注:上述的响应过程我不保证完全正确,如果有疑问,还请参考其他资料,见谅。

- Linux软链接、硬链接的区别

参考:https://www.cnblogs.com/chenyongmou/p/7221399.html
  Linux文件系统中,有所谓的链接(link),我们可以将其视为档案的别名,而链接又可分为两种 : 硬链接(hard link)与软链接(symbolic link),硬链接的意思是一个档案可以有多个名称,而软链接的方式则是产生一个特殊的档案,该档案的内容是指向另一个档案的位置。硬链接是存在同一个文件系统中,而软链接却可以跨越不同的文件系统。

- 深入理解Http请求、DNS劫持与解析。

参考:https://www.aliyun.com/jiaocheng/350370.html
https://blog.csdn.net/m0_37812513/article/details/78775629

- TCP拥塞控制-慢启动、拥塞避免、快重传、快启动

参考:https://blog.csdn.net/jtracydy/article/details/52366461

- Java垃圾回收机制理解

垃圾回收算法
垃圾回收器
参考:https://blog.csdn.net/daguairen/article/details/52248171
https://www.cnblogs.com/good-temper/p/3583660.html
http://www.importnew.com/26383.html

C/C++

  :是在程序运行时,申请某个大小的内存空间,栈只是一种使用堆的方法。全局变量、静态变量存放在堆上。
  (stack):又名堆栈,是一种运算受限的线性表。其限制是仅允许在表的一段进行插入和删除运算。这一端又被称为栈顶,相应的把另一端称为栈底。数据是后进先出(LIFO)。局部变量、基本类型变量、函数、用户自定义的变量存放在栈中。
  栈是操作系统在建立某个进程或者线程时为其建立的存储区域。

  • 常量指针本质是指针,用常量来修饰,表示这个指针指向的是常量,例如:
  1. const int *p;
  2. int const *p;

指针指向的对象不能通过该指针来修改,但仍可通过原来的声明修改,也即常量指针被赋值为变量的地址,但是不能通过该指针来修改原变量的值,例如:

int a = 5;
const int b = 8;
const int *c = &a;//合法
*c = 6;//非法,但是可以通过这样修改指针c指向的对象的值: a = 6;
const int *d = &b;//合法
  • 指针常量:本质是常量,用指针来修饰,说明该常量的值是一个指针指针常量在声明的时候一定要赋初值,一旦赋值,以后这个常量再也不能指向别的地址:
int a;
int *const b = &a;

虽然指针常量的值不能修改,但是它指向的对象是可变的:

char *a = “abcde123”;
char *b = “qwer”;
char *const c = &a;//指针常量c以后只能指向a
可以进行的操作有:
a[0] = ‘x’;
*c[0] = ‘x’;//和上面操作等价
  • 指向常量的指针常量:就是一个常量,且它指向的对象也是一个常量。指向的对象不能变,指针常量本身也不能变。
const int a = 23;
const int *const b = &a;
  • 判断一个链表中是否有环:

  设置两个指针,初始都指向表头,然后其中一个指针每次向前走一步,另一个指针每次向前走两步。如果走两步的指针遇到NULL,表明链表中没有环;否则,因为快的指针每次比慢的指针多走一步,两指针最终会相遇,说明链表有环,并且相遇点就是入口点。

bool judgeLoop(list *head)
{
	if(head == NULL) return false;
	list *pFast = head;
	list *pSlow = head;
	while(pFast->next != NULL && pFast->next->next != NULL)
	{
		pFast  = pFast->next->next;
		pSlow = pSlow->nxt;
		if(pFast == pSlow)
		{
			return true;//有环,并且两指针第一次相遇点即为环的入口点
		}
	}
	return false;//无环
}
  • 快速排序优化
  • STL容器

- 指针和引用的主要区别:

参考:https://blog.csdn.net/weikangc/article/details/49762929

  1. 引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。
  2. 引用初始化后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针。

相同点:都是地址的概念,指针指向一块内存,它的值是所指内存的地址;引用是某块内存的别名。
不同点

  • 指针是一个实体,而引用仅是个别名;

  • 引用使用时无需解引用(*),指针需要解引用;

  • 引用只能在定义时被初始化一次,之后不可变;指针可变;

  • 引用没有 const,指针有 const;

  • 引用不能为空,指针可以为空;

  • “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;

  • 指针和引用的自增(++)运算意义不一样;
    8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

  • 析构函数:
    当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。

  • 内联函数:
    引入内联函数的目的是为了解决程序中函数调用的效率问题。程序在编译器编译的时候,会将程序中出现内联函数调用的地方全部换成函数表达式,以牺牲代码空间来节省时间(函数调用的开销)。1)在内联函数内不允许用循环语句和开关语句;2)内联函数的定义必须出现在内联函数第一次被调用之前。

- 虚函数、纯虚函数:

-虚函数:

  在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。
  虚函数的作用是实现动态绑定,也就是说程序在运行时动态的选择合适的成员函数。
  普通成员函数和析构函数可以定义为虚函数。

-纯虚函数:virtual <类型><函数名>(<参数表>)=0;

  纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
  纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。
  一般而言纯虚函数的函数体是缺省的,但是也可以给出纯虚函数的函数体(此时纯虚函数变为虚函数),这一点经常被人们忽视,调用纯虚函数的方法为baseclass::virtual function.
引入原因:
  为了方便使用多态特性,我们常常需要在基类中定义虚函数。
  在很多情况下,基类本身生成对象是不合情理的。

- 找出一个无序数组的中位数:

利用快排的思想:
  1、先进行一趟快排,使得div左边的值都比arr[div]小,div右边的值都比arr[div]大,但是这个div的位置是不确定的,可能位于中间,也可能偏左或者偏右。
  2、计算出mid所在的下标,如果是奇数则是mid=(size+1)/2,如果是偶数则是mid=size/2。
  3、此时需要比较mid和div所在的位置。如果mid在div所在位置的左边,此时就要递归去左半区间查找;如果mid在div的右边,此时就要递归去右半区间查找;如果恰好相等则说明div/mid所在的位置就是中位数。

//基于快排的思想,分而治之
int PartSort(int *arr, int start, int end)
{
    int left = start;
    int right = end;
    int key = arr[end];   //选取关键字
    while (left < right)
    {
        while (left < right && arr[left] <= key)  //左边找比key大的值
        {
            ++left;
        }
        while (left < right && arr[right] >= key)  //右边找比key小的值
        {
            --right;
        }
        if (left < right)
        {
            swap(arr[left], arr[right]);  //找到之后交换左右的值
        }
    }
    swap(arr[right], arr[end]);
    return left;
}
//求一个无序数组的中位数
int GetMidNumNoSort1(int *arr,int size)
{
    assert(arr);
    int start = 0;
    int end = size - 1;
    int mid = (size - 1) / 2;
    int div = PartSort(arr,start,end);
    while (div != mid)
    {
        if (mid < div)   //左半区间找
            div = PartSort(arr, start, div - 1);
        else    //左半区间找
            div = PartSort(arr, div + 1, end);
    }
    return arr[mid];   //找到了
}

设计模式

  设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长一段时间的试验和错误总结出来的。设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编制真正工程化。

创建型模式,提供了一种创建对象的最佳方式。
  • 工厂模式

  在工厂模式中,我们创建对象时不会对客户端暴露出创建逻辑,并且通过一个共同的接口来指向新创建的对象。定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。主要解决接口选择的问题,也即不同条件下创建不同实例。

  是Java中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。用以控制实例个数,节省系统资源。
主要解决:一个全局使用的类被频繁的创建与销毁。
注意:
 单例类只能有一个实例
 单例类只能自己创建自己的唯一实例
 单例类必须给所有其他的对象提供这一实例
实现方式:……

操作系统

  • 进程、线程的区别

  在多线程环境中,进程被定义为保护单位和资源分配单位,线程是调度的基本单位。
  线程也叫轻型进程,是可执行的实体单元,它可替代以往的进程,是处理机调度的基本单位。
  多线程:指的是操作系统支持在单个进程中执行多个线程的能力。

  • 线程特征:
    1) 线程的执行状态包括运行、就绪和等待;
    2) 当不处于执行状态时,要保存线程上下文环境。可以把线程看成是进程内一个独立的程序计数器的运转;
    3) 一个执行栈
    4) 进程中的所有线程共享所属进程内的主存和其他资源。

  • 线程的状态
    1)创建:进程创建的过程中也创建了线程,一个线程可以在这个进程内部创建其他的线程。同一进程内的线程共享相同的存储空间和资源,所以在创建时还应指明栈和寄存器的位置,然后把线程放入就绪队列。
    2)阻塞:当有个线程需要等待一个事件时,它将被阻塞。
    3)解除阻塞
    4)终止:线程完成任务后,就释放它所占用的寄存器上下文和栈空间。

  • 线程的实现机制
    1.用户级线程(ULT)
      用户级线程是指由应用程序管理线程,核心感觉不到线程的存在,如下图。应用程序利用线程库进行多线程程序设计。线程库实质上是应用程序开发和运行的环境,它包括在线程创建、撤销和通信时传递的信息和数据,线程切换时保存、恢复上下文等代码。
      优点:核心不用管理线程的切换,处理机在两个线程间切换时不用进入到核心态执行,节省了用户态和核心态之间切换的开销。由于不需要对核心进行修改就可以支持用户线程,因此用户级线程的管理机制可以运行在各种操作系统中,方便、灵活。
      缺点:当线程执行系统调用时,整个进程都被阻塞,不能充分利用多处理机。
    图12
    2. 核心级线程(KLT)
      核心级线程也称为内核线程,它运行于核心中,通过核心来管理,如下图。
    图13
      核心可以调度一个进程中的多个线程同时运行,当某线程发生阻塞时,可以调度其他线程执行。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行、撤销线程。
      优点:充分发挥了多处理机的并行工作能力。
      缺点:在同一进程控制权转移时,用户级与核心级的切换开销很大。

  • 线程与进程的比较
      最初的线程伴随进程的创建而来,之后由这个线程再创建其他的线程,与进程既有密切的联系,又存在着很大的差别。
      1)调度。在传统的操作系统中,进程是资源分配和调度的基本单位。在引入线程的操作系统中线程是独立调度的基本单位进程是资源分配的基本单位。在同一进程中,线程的切换不会引起进程切换。不同进程中进行线程切换,会引起进程切换。线程的调度与传统的进程调度类似,即在就绪线程中选择合适的线程占用处理机。
      2)拥有资源。进程是资源分配的基本单位,同一进程内的所有线程共享该进程的资源和状态,但不归线程所有。线程具有寄存器和栈,以及一点在运行中必不可少的资源;而进程具有PCB、用户地址空间、执行程序和数据,是多个线程的集合。
      3)并发性。多个进程间和多个线程间都分别可以并发执行。
      4)系统开销。进程创建、撤销和切换都需要很大的开销,但线程切换时开销很小。由于同一进程内的多个线程共享进程的地址空间,因此,这些进程之间的同步和通信非常容易实现,甚至无需操作系统的干预。
      5)通信。进程间通信(IPC)需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以直接读/写进程数据段*如全局变量)来进行通信。

死锁产生的四个必要条件

  互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
  不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
  请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
  循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
  以上给出了导致死锁的四个必要条件,只要系统发生死锁则以上四个条件至少有一个成立。事实上循环等待的成立蕴含了前三个条件的成立,似乎没有必要列出然而考虑这些条件对死锁的预防是有利的,因为可以通过破坏四个条件中的任何一个来预防死锁的发生。

  • 进程调度
  • 时间片分配

最后来个总结:上述所有知识点,是我在今年秋招的笔试、面试过程中所遇到的,部分解答是参考或引用网上其他资料的;对于我自己解答的部分,因为才疏学浅,也许不够严谨和完整,如果对您的使用带来困扰,我深感抱歉。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值