ACE+线程池实现一个简单的服务器+多客户端通信程序

 ACE中本身具有异步通信组建,比如handle_input和handle_ouput等方法,可以用来实现一个

单线程的服务器,并且可以处理多客户端的请求功能。


  这里在ACE里面使用线程池技术,实现服务器与多个客户端的通信程序,服务器的功能是对每一个客户端

发送过来的数据,原路的返回,即回显服务器。


首先是原理:

1.ACE中建立多个线程,线程的状态是挂起,把这些建立的线程放在一个队列中代用

2.开启一个监听线程,如果监听到某一个端口比如3000端口有服务器请求功能,那么从线程池中取出一个线程,并

执行该socket连接的任务

3.线程处理完客户端请求之后,把自己放入线程池队列中,并挂起自己。

下面是具体的文件和代码。


第一个  threadstate.h:用来存储建立的一个线程的状态,比如句柄和id

#ifndef  _TASK_H_
#define  _TASK_H_


#include "stdafx.h"
#include "ace/Reactor.h"
#include "ace/WFMO_Reactor.h"
#include "ace/INET_Addr.h"
#include "ace/SOCK_Stream.h"
#include "ace/SOCK_Acceptor.h"
#include "ace/OS_main.h"
#include <iostream>
#include <string>

#include "netclient.h"
#include  "ace/OS.H"   
#include "ace/Thread.h"   
#include "ace/Synch.h"   
#include "netlistener.h"
#include <vector>

#include <hash_map>    /* 用于把<客户端名字,客户端>放入map,便于快速查找 */
#include<queue>        /* 用于维护一个ACE_SOCK_Stream临时队列,在这个队列里面处理登录的连接请求 */


#include "ace/Thread.h"
#include "ace/Synch.h"  /* 这两个头文件用于创建线程用的哈 */

#include "userpush.h"
#include "sendthread.h"


/*****************************************
* @threadstate类用户维护一个线程的状态
* 比如其句柄,id,线程执行函数等*线程创建
* 完之后进入挂起状态,然后把每一个线程的
* 该类放在一个队列中,表示所有可用的线程
* 如果来了一个socket请求,那么从队列中弹出
* 一个线程的类结构进行处理(挂起,重启等)
* 注意当前一个线程只有建立接收任务
* 暂时还不做发送任务
******************************************/



class threadstate
{
public:
	threadstate();
	~threadstate();
	void SetThreadId(ACE_thread_t id);      /* 设置id        */
	void SetThreadHdl(ACE_hthread_t hdl);   /* 设置句柄      */
	void SetPeer(ACE_SOCK_Stream peer1);    /* 设置线程的socket */
	void ThreadRec(void);                   /* 接收函数      */
	void ThreadSend(void);                  /* 线程接收函数  */
	void SetThreadState(int state);         /* 设置线程state */     

	ACE_thread_t    threadid; 	            /* 线程id        */
	ACE_hthread_t   threadhdl;              /* 线程句柄      */
	ACE_SOCK_Stream peer;                   /* 线程处理的sock */
    int state;                              /* 线程状态,挂起还是执行 ,0表示running,表示挂起 */
	bool rw;                                /* 表示线程是否真的执行了接收的任务,先不用这个标志位 */
};

#endif 

threadstate.cpp  主要初始化工作,设置自定义的线程管理类的相关状态,如下:

#include "stdafx.h"
#include "threadstate.h"
#include "threadmng.h"



#include "ace/Thread.h"
#include "ace/Synch.h"  /* 这两个头文件用于创建线程用的哈 */

#include <iostream>
using namespace std;

threadstate::threadstate(void)
{

}


threadstate::~threadstate(void)
{

}


void threadstate::SetThreadId(ACE_thread_t id)
{
	this->threadid=id;
}



void threadstate::SetThreadHdl(ACE_hthread_t hdl)
{
	this->threadhdl=hdl;
}


void threadstate::SetPeer(ACE_SOCK_Stream peer1) /* 设置线程的socket */
{
	this->peer=peer1;
}


/***************************
*@ 设置线程的状态
*@ 0表示是正在执行
*@ 1表示挂起
***************************/
void threadstate::SetThreadState(int state) /* 设置线程的socket */
{
	this->state=state;
}


/***************************
*@ 简单的线程接收函数
***************************/
void threadstate::ThreadRec(void)
{
	cout<<"in thread Rec, thread id is"<<this->threadid<<endl;
	while(1)
	{
	  cout<<"在threadstate 线程的接收函数里面"<<endl;
	}

}

/***************************
*@ 简单的线程发送函数
***************************/

void threadstate::ThreadSend(void)
{
	cout<<"in thread Send, thread id is"<<this->threadid<<endl;
	while(1)
	{
      cout<<"in threadstate  ThreadSend function"<<endl;
	}
		
}


threadmng.h  这个定义了一个类,threadmng用来管理线程池,下面是头文件中的内容

#ifndef _THREAD_MNG_H
#define _THREAD_MNG_H


#include "threadstate.h"
#include <queue>

/*******************************************************
* 先定义一个threadstate数组,然后新建线程的时候,
* 初始化这个数组中的相关变量,把数组成员放入threadmng
* 的threadstatpool队列中,然后设置最大线程数量
* 创建线程的时候,它的函数设置成一个固定的函数,此函数中
* 从threadmng判断任务队列是否为空,如果是非空,那么就从中
* 取出一个任务开始执行;
* 注意:
* @对于一个执行完的任务,该任务需要做的事情是
* 获得该任务的句柄,然后查找该threadmng中该任务对应
* 的线程状态类threadstate,然后设置相关参数后,把自己插入
* 可用线程池队列,然后再设置状态为挂起,并且挂起自己
* 另外:
* @从线程池队列中取出一个元素前,首先得判断这个线程的状态
* 是否是挂起,否则就等待,知道可以取这个元素为止
* 注意:
* 需要预先分配一个用户类的数组,这样避免在来客户的时候
* 重新申请客户
*********************************************************/
class threadmng
{
public:
	threadmng(int maxthread);
	~threadmng(void);
	queue<ACE_hthread_t> threadhdlpool;    /* 具体的句柄池 */
	queue<threadstate> threadstapool;      /* 线程状态池 */
	queue<ACE_SOCK_Stream> peerpool;       /* 待处理的任务池 */
	queue<NetClient>alluser;               /* 所有的用户组成的一个队列 */
    int            maxthread ;			   /* 线程池中最大的线程数量 */
	HANDLE         hthreadchg;             /* 线程状态改变时,或者需要申请一个线程时,使用这事件调用 */
	ACE_hthread_t*	pthreadhdl;            /* 线程id的数组指针 */
	ACE_thread_t *  pthreadid;
	ACE_INET_Addr   port_to_listen;        /* 端口对象 */
	ACE_SOCK_Acceptor  acceptor;           /* 连接器,注意这个连接器是否要初始化(new)这里没有说明 */

	threadstate * pthreadstate;            /* 一个hreadstate数组,需要在创建函数里面创建 */
	void SetMaxThread(int num)       ;     /* 设置最大的线程数量 */
	static void *ThreadMngrun(void * arg);              /* 管理任务线程,如果有新的任务请求,从线程池取出队列,进行工作,或者把工作完的线程挂起 */    
	static void *Listener(void *arg);                  /* 监听线程,用来判断网络是否有 */

	ACE_hthread_t listenerhdl;            /* 监听线程句柄 */
	ACE_hthread_t threadmnghdl;            /* 线程池管理线程池句柄 */

	static void *Threadrun(void *arg);     /* 线程执行函数 */

};



#endif

下面是threadmng这个类的具体实现,包括创建线程,以及监听端口线程,线程池管理线程的实现等等

#include "stdafx.h"
#include "threadmng.h"
#include "ace/Thread.h"  
#include "ace/Reactor.h"
#include "ace/WFMO_Reactor.h"
#include "ace/INET_Addr.h"
#include "ace/SOCK_Stream.h"
#include "ace/SOCK_Acceptor.h"
#include "ace/OS_main.h"
#include <iostream>
#include <string>

#include "netclient.h"
#include  "ace/OS.H"   
#include "ace/Thread.h"   
#include "ace/Synch.h"   
#include "netlistener.h"
#include <vector>

#include <hash_map>    /* 用于把<客户端名字,客户端>放入map,便于快速查找 */
#include<queue>        /* 用于维护一个ACE_SOCK_Stream临时队列,在这个队列里面处理登录的连接请求 */


#include "ace/Thread.h"
#include "ace/Synch.h"  /* 这两个头文件用于创建线程用的哈 */
#include "threadstate.h"

//#include "Thread.h"

//class threadmng
//
//class threadmng
//{
//public:
//	threadmng();
//	~threadmng();
//	queue<ACE_hthread_t> threadhdlpool;    /* 具体的线程句柄池 */
//	queue<threadstate> threadstapool;      /* 线程状态池 */
//	queue<ACE_SOCK_Stream> peerpool;       /* 待处理的任务池 */
//	queue<NetClient>alluser;               /* 所有的用户组成的一个队列 */
//    int MaxThread                   ;      /* 线程池中最大的线程数量 */
//	HANDLE         hthreadchg;               /* 线程状态改变时,或者需要申请一个线程时,使用这事件调用 */
//
//	void SetMaxThread(int num)       ;     /* 设置最大的线程数量 */
//	void threadmngrun(void);               /* 管理任务线程,如果有新的任务请求,从线程池取出队列,进行工作,或者把工作完的线程挂起 */    
//
//}


/*
* ---------------------------------------
* tmpmng类的初始化工作
* 包括:
* 1.根据输入值创建一个线程管理状态数组
* 2.创建线程池里面的线程
* 3.创建一个线程池管理事件
* 4.创建一个线程池管理线程,用于管理
*   线程
* 5.规划每一个线程的执行函数,应该定义在
*   线程状态
* ---------------------------------------
*/
threadmng::threadmng(int threadnum)
{
	maxthread=threadnum;                                                /* 最大线程数赋值 */
	port_to_listen.set_port_number(3000);                                /* 设置监听端口 */
	this->pthreadstate=new threadstate[threadnum];                            /* 创建线程管理数组 */
	this->pthreadhdl=new ACE_hthread_t[threadnum];                            /* 创建线程句柄数组 */
	this->pthreadid =new ACE_thread_t[threadnum];                             /* 创建线程ID数组 */
	/*
	* 通过上面创建的thread句柄,创建n个线程,并把线程
	* 的句柄以及id放在threadstate结构体数组中
	* 然后把上述结构体数组放在threadstate的队列中
	*/
	ACE_Thread::spawn_n (pthreadid,
                         threadnum,
                        (ACE_THR_FUNC)Threadrun,
                        this,
                        THR_NEW_LWP | THR_SUSPENDED,
                        ACE_DEFAULT_THREAD_PRIORITY,
                        0,
                        0,
                        pthreadhdl,
                        0,
                        0);    /* 刚刚创建的线程,处于挂起的状态 */


	for(int i=0;i<maxthread;i++)
	{
		pthreadstate[i].threadhdl=pthreadhdl[i];      /* 初始化threadstapool的handle */
		pthreadstate[i].threadid=pthreadid[i];        /* 初始化threadstapool的线程id */
		this->threadstapool.push(pthreadstate[i]);    /* 把这个结构体放到threadstapool里面 */
		this->threadhdlpool.push(pthreadhdl[i]);

	}

	if(hthreadchg==NULL)
		std::cout<<"in threadmng create event fail"<<endl;
	this->hthreadchg=CreateEvent(NULL,false,false,_T("threadpoolchg")); /* 创建一个用于需要开启线程事件 */
	


	/* 还需要做的事情 */
	/*
	 * 创建一个连接器connectro,并且把某一个端口绑定到这个连接器上
	 * 然后开始监听
	 * 注意这里是在一个单独的线程里面监听,如果有请求,就调用上面的线程池
	 * 里面的线程进行处理
	 */
	
	if(this->acceptor.open(port_to_listen,1)==-1)
	{
		std::cout<<"open port fail"<<std::endl;
	}
	/* 开启一个监听的线程,用来等待客户端接入 */
	ACE_Thread::spawn((ACE_THR_FUNC)Listener,this,THR_JOINABLE | THR_NEW_LWP,NULL,&listenerhdl);//
	ACE_Thread::spawn((ACE_THR_FUNC)ThreadMngrun,this,THR_JOINABLE | THR_NEW_LWP,NULL,&threadmnghdl);//

}


threadmng::~threadmng(void)
{

}

void threadmng::SetMaxThread(int num)
{
	this->maxthread=num;
}




/*
--------------------------------------------
* 线程管理函数,用户处理来临一个请求的时候
* 从线程池中取出一个线程,执行线程等处理操作
--------------------------------------------
*/


void *threadmng::ThreadMngrun(void *arg)
{
	threadmng *tmpmng=(threadmng *)arg;
	ACE_hthread_t tmphdl;
	while(1)
	{  cout<<"in threadmng:ThreadMngrun"<<endl;
		WaitForSingleObject(tmpmng->hthreadchg,INFINITE);                             /* 等待有任务申请,在申请任务的地方,查看是否需要建立新的用户 */
		if(tmpmng->threadstapool.size()>0 && tmpmng->peerpool.size()>0)           /* 表示线程队列里面有可以申请使用的队列 */
		{
			tmphdl=tmpmng->threadstapool.front().threadhdl;                     /* 获得这个句柄 */
			tmpmng->threadstapool.front().peer=tmpmng->peerpool.front();          /* 把线程状态结构里面的socket赋值成socket请求队列头的socket */
			tmpmng->threadstapool.front().state=true;                           /* 表示正在运行 */
			ACE_Thread::resume(tmphdl);                                       /* 恢复这个线程运行 */
		}
		else 
		{
			cout<<"threadpool or peerpool is empty"<<endl;               //tmphdl=ACE_Thread::spawn();    /* 新建若干个线程,并且放入一个类的结构体成员里 */
			printf("threadhdlpool size is %d\n",tmpmng->threadhdlpool.size());
			printf("peerpool size is %d\n",tmpmng->peerpool.size());
		}
    }
}


/*****************************************************************
* 注意
* 如果有一个任务请求,那么在threadmngrun 让一个线程启动的时候
* 先不要弹出这个线程的threadstate类结构体,否则在线程中查找这个
* 类的threadstate比较麻烦,所以弹出这个线程的这个状态结构体
* 应该在下面这个实际的线程执行函数里,从threadstate 队列的头部
* 获得了当前这个线程的threadstate结构体之后,再弹出,这样方便操作
* ---------------------------------------------------------------
* 另外
* 还可以把原来的threadstate pool分成两个queue,一个是休眠状态
* 的线程,另外一个是正在执行的线程queue,这两个队列都放在
* threadmng类中,当从一个休眠线程队列中取得一个头部之后,如果这个
* 线程执行了,那么就把它防砸执行队列中,但这样的问题是,执行
* 线程中的threadstate并不是依次按照队尾和头部结束,这样无法清除
* 所以还是按照上面第一种方法来做吧
*---------------------------------------------------------------
* 另外
* 这个tmpfun可以写到我的threadmng类里面,充分体现面向对象的过程
* 目前已经写到threadmng类里面了哈
******************************************************************/

void *threadmng::Threadrun(void *arg) /* 创建线程时,线程执行的函数 */
{
	threadmng *tmp=(threadmng *)arg; /* 直接调用原来的指针而非使用一个拷贝,一次获得,永久使用 */
	ACE_SOCK_Stream tmpsocket;       /* 临时socket peer队列 */
	char buf[512];                   /* 接收buf */
	int recbyte;                     /* 接收的字节数 */
	while(1)
	{
		cout<<"现在正在线程池里面的线程执行函数里面"<<endl;
		threadstate tmpthreadstate;                 /* 获得当前的thread */
		tmpthreadstate=tmp->threadstapool.front();  
		tmp->threadstapool.pop();                   /* 线程在这里弹出 */
		if(tmp->peerpool.size()>0)                  /* 说明还是有任务要处理 */
		{
			tmpsocket=tmp->peerpool.front();    
			tmp->peerpool.pop();                    /* 弹出这个socket请求 */
			while(1)
			{
				cout<<"正在while(1)里面执行函数,最底层的while1"<<endl;
				if((recbyte=tmpsocket.recv(buf,512))>0)              /* 收到消息 */
				{
					buf[recbyte]=0;                                  /* 末尾赋值为0 */
					std::cout<<buf<<endl;
					tmpsocket.send(buf,recbyte);                     /* 把收到的信息返回 */
				}
				else if(recbyte==0)                                  /* 说明socket被关闭了,此时需要把线程从挂起并从队列头重取出放到队尾 */
				{
					cout<<"正要退出现在正在执行的线程了哈"<<endl;
					break;                                           /* 跳出内层循环体 */
				}
				else
				{
					break;
				}
			}
		}
		tmpthreadstate.state=false;                      /* 先把线程状态设置为休眠 */
		tmp->threadstapool.push(tmpthreadstate);         /* 把当前线程放到线程池队列里面 */
		ACE_Thread::suspend(tmpthreadstate.threadhdl);   /* 休眠当前线程 */
	}

	return NULL;
}


/*
----------------------------------
* 监听端口,如果有连接请求,那么
* 就把一个socket放入一个socketpool
* 然后通知线程管理程序,有相关事情
* 需要处理(使用信号量)
----------------------------------
*/
void* threadmng::Listener(void* arg)
{
	threadmng *tmpmng=(threadmng *)arg;
	ACE_SOCK_Stream peer1;
	while(1)
	{
		if(tmpmng->acceptor.accept(peer1)!=-1)
		{
			cout<<"in threadmng:Listener"<<endl;
			tmpmng->peerpool.push(peer1);  /* 说明有SOCK请求 */
			SetEvent(tmpmng->hthreadchg);  /* 告诉线程管理函数,有新的连接请求 */
		}
	}
}


	//void *ThreadMngrun(void);              /* 管理任务线程,如果有新的任务请求,从线程池取出队列,进行工作,或者把工作完的线程挂起 */    
	//void *Listener(void);                  /* 监听线程,用来判断网络是否有 */

	//ACE_hthread_t listernerhdl;            /* 监听线程句柄 */
	//ACE_hthread_t threadmnghdl;            /* 线程池管理线程池句柄 */

最后是主函数:

/*******************************************
* 功能:服务器启动主程序
* 版本:v1.0
* 时间:2014-08-08
* 作者:王丹
*******************************************/

#include "stdafx.h"
#include "ace/Reactor.h"
#include "ace/WFMO_Reactor.h"
#include "ace/INET_Addr.h"
#include "ace/SOCK_Stream.h"
#include "ace/SOCK_Acceptor.h"
#include "ace/OS_main.h"
#include <iostream>
#include <string>

#include "netclient.h"
#include  "ace/OS.H"   
#include "ace/Thread.h"   
#include "ace/Synch.h"   
#include "netlistener.h"
#include <vector>

#include <hash_map>    /* 用于把<客户端名字,客户端>放入map,便于快速查找 */
#include<queue>        /* 用于维护一个ACE_SOCK_Stream临时队列,在这个队列里面处理登录的连接请求 */


#include "ace/Thread.h"
#include "ace/Synch.h"  /* 这两个头文件用于创建线程用的哈 */

#include "userpush.h"
#include "sendthread.h"

#include "tmpmsg.h"     /* 处理handle_input消息的头文件 */

#include "threadmng.h"

#include "login.h"     /* 用于处理登录线程 */
#pragma comment(lib,"aced.lib")  
#pragma comment(lib,"ace.lib") 
using namespace std;



hash_map<string,NetClient> allclient;        /* 保存所有用户信息 */
queue<ACE_SOCK_Stream>     loginsock;        /* 全局临时连接请求 */
string                     userpushmsg;      /* 全局推送在线用户字符串 */
HANDLE                     huserpushmsg;     /* 用于推送信息的事件操作 */
queue <string>             tmpmsg;           /* 用户保存handle_input收到的所有消息 */


int _tmain(int argc, _TCHAR* argv[])
{

	ACE::init();                                        
    WSADATA   wsaData;  
	if(WSAStartup( MAKEWORD(2,0), &wsaData) != 0)  
    {  
        ACE_OS::printf("start failed");    
    }	
    threadmng server1(100);
	//huserpushmsg=(HANDLE)CreateEvent(0,false,false,_T("userpush"));                    /* 创建一个自动复位的事件,用于读取在线用户列表并推送 */
	//NetListener *listener1 = new NetListener;
	//ACE_Reactor::instance()->register_handler(listener1,ACE_Event_Handler::ACCEPT_MASK);

	///***************************************/
	///*** 创建一个处理连接的单独线程 *******/
	//ACE_thread_t processid;
	//ACE_hthread_t processhdl;
	//ACE_Thread::spawn((ACE_THR_FUNC)ProcessLogin,NULL,THR_JOINABLE | THR_NEW_LWP,&processid,&processhdl);  	
	///**********************************/

	///***************************************/
	///*** 创建一个处理推送在线用户的单独线程 *******/
	//ACE_thread_t usermsgid;
	//ACE_hthread_t usermsghdl;
	//ACE_Thread::spawn((ACE_THR_FUNC)ProcessUserPush,NULL,THR_JOINABLE | THR_NEW_LWP,&usermsgid,&usermsghdl);  
	///***************************************/
	
	//
	///***************************************/
	///*** 创建一个轮询客户端待发送数据队列的消息 *******/
	//ACE_thread_t sendmsgid;                      /* 线程id */
	//ACE_hthread_t sendmsghdl;                    /* 句柄   */
	//ACE_Thread::spawn((ACE_THR_FUNC)SendThread,NULL,THR_JOINABLE | THR_NEW_LWP,&usermsgid,&usermsghdl);  
	///***************************************/

	///***************************************/
	///*** 创建一个处理handle_input中得到的临时消息的线程 *******/
	//ACE_thread_t  tmpmsgid;
	//ACE_hthread_t tmpmsghdl;
	//ACE_Thread::spawn((ACE_THR_FUNC)ProcessTmpMsg,NULL,THR_JOINABLE | THR_NEW_LWP,&tmpmsgid,&tmpmsghdl);  
	
	/***************************************/
	/*** 创建一个处理handle_input中得到的临时消息的线程 *******/
	//ACE_thread_t  tmpmsgid;
	//ACE_hthread_t tmpmsghdl;
	//ACE_Thread::spawn((ACE_THR_FUNC)test,&tmpmsghdl,THR_JOINABLE | THR_NEW_LWP,&tmpmsgid,&tmpmsghdl); 


	//cout<<"dangqian yonghu shi "<<allclient["jakill"].username<<endl;
	//ACE_Reactor::instance()->run_event_loop();              /* 开启异步监听消息的轮询事件机制 */
	//ACE_Thread::join(threadhandle);
	
	//Sleep(1000);
	//ACE_Thread::resume(tmpmsghdl);
	while(1);
    system("pause");
    return 0;

}	




  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
ace tao.tar.gz 是一个压缩文件,其中包含了 ACE 和 TAO 的源代码和相关文件。ACE一个开放源代码的 C++ 库,用于开发高效、可伸缩的网络应用程序。它提供了丰富的网络编程功能,如网络通信多线程、同步机制等,可以支持跨平台运行。 TAO 是 ACE 框架的一部分,它是一个开放源代码的分布式对象编程平台,用于构建分布式应用程序和服务。TAO 提供了 CORBA 标准的实现,可以帮助开发人员通过网络进行远程调用和对象通信。 .tar.gz 是一种常见的压缩文件格式,通常用于将多个文件或文件夹打包成一个单一的文件,以便于传输和存储。.tar 是将多个文件或文件夹打包成一个归档文件,.gz 是对该归档文件进行压缩。因此,ace tao.tar.gz 文件是 ACE 和 TAO 的源代码和相关文件经过打包和压缩处理后的结果。 要使用 ace tao.tar.gz 文件,我们需要先解压缩它。通常可以使用解压缩软件,如 WinRAR、7-Zip 等,右键单击文件并选择解压缩选项。解压缩后会得到 ACE 和 TAO 的源代码文件和文件夹。我们可以根据需要查看和修改这些源代码,编译和构建相应的库文件和可执行文件,用于开发网络应用程序和分布式系统。 总之,ace tao.tar.gz 是 ACE 和 TAO 的源代码打包和压缩后的文件,它提供了强大的网络编程和分布式对象编程能力,可以用于开发高效、可靠的网络应用程序和分布式系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值