一个简单的C++ rpc代码

    本代码来自GitHub - button-chen/buttonrpc: Tiny and simple c++ rpc library,非常简单的rpc代码,总共就两个cpp文件,代码量只有3,4百行。这里简单分析一下。
 

StreamBuffer

class StreamBuffer : public vector<char>{
public:
	StreamBuffer(){ m_curpos = 0; }
	StreamBuffer(const char* in, size_t len){
		m_curpos = 0;
		insert(begin(), in, in+len);
	}
	~StreamBuffer(){ }; 

	void reset(){ m_curpos = 0; }
	const char* data(){ return &(*this)[0]; }
	const char* current(){ return&(*this)[m_curpos]; }
	void offset(int k){ m_curpos += k; }
	bool is_eof(){ return (m_curpos >= size()); }
	void input( char* in, size_t len){ insert(end(), in, in+len); }
	int findc(char c){
		iterator itr = find(begin()+m_curpos, end(), c);		
		if (itr != end())		{
			return itr - (begin()+m_curpos);
		}
		return -1;
	}

private:
	// 当前字节流位置
	unsigned int m_curpos;
};

 看着很复杂,就是一个string buffer,起始也是没有明白为什么要继承,直接包含一个不就好了。

Serializer

class Serializer{
public:
	Serializer() { m_byteorder = LittleEndian; };
	~Serializer(){ };

	Serializer(StreamBuffer dev, int byteorder=LittleEndian){
		m_byteorder = byteorder;
		m_iodevice = dev;
	}

public:
	enum ByteOrder {
		BigEndian,
		LittleEndian
	};

public:
    // 这就是一些简单的设置buffer的方法
	void reset(){
		m_iodevice.reset();
	}
	int size(){
		return m_iodevice.size();
	}
	void skip_raw_date(int k){
		m_iodevice.offset(k);
	}
	const char* data(){
		return m_iodevice.data();
	}
	void byte_orser(char* in, int len){
		if (m_byteorder == BigEndian){
			reverse(in, in+len);
		}
	}
	void write_raw_data(char* in, int len){
		m_iodevice.input(in, len);
		m_iodevice.offset(len);
	}
	const char* current(){
		return m_iodevice.current();
	}
	void clear(){
		m_iodevice.clear();
		reset();
	}
    
    // 关键的方法在这
	template<typename T>
	void output_type(T& t);

	template<typename T>
	void input_type(T t);

	// 直接给一个长度, 返回当前位置以后x个字节数据
	void get_length_mem(char* p, int len){
		memcpy(p, m_iodevice.current(), len);
		m_iodevice.offset(len);
	}

public:
    // 两个流操作符的重载是为了序列化数据
	template<typename T>
	Serializer &operator >> (T& i){
		output_type(i); 
		return *this;
	}

	template<typename T>
	Serializer &operator << (T i){
		input_type(i);
		return *this;
	}

private:
	int  m_byteorder;    // 记录是大端存储还是小端存储
	StreamBuffer m_iodevice;    // 序列化的字节流
};

// output_type模板定义,反序列化
// 根据类型大小,将buffer大小的数据拷贝到数据上
// 只能针对基本类型,以及纯struct的数据结构
template<typename T>
inline void Serializer::output_type(T& t){
	int len = sizeof(T);
	char* d = new char[len];
	if (!m_iodevice.is_eof()){
		memcpy(d, m_iodevice.current(), len);
		m_iodevice.offset(len);
		byte_orser(d, len);
		t = *reinterpret_cast<T*>(&d[0]);
	}
	delete [] d;
}

// output_type的偏特化
// 针对string类,string类不能拷贝其大小,而是需要拷贝数据
// 在对string序列化的时候,第一个uint16,2字节,存放的是string的长度
// 接下来才是数据
template<>
inline void Serializer::output_type(std::string& in){
	int marklen = sizeof(uint16_t);
	char* d = new char[marklen];
	memcpy(d, m_iodevice.current(), marklen);
	byte_orser(d, marklen);
	int len = *reinterpret_cast<uint16_t*>(&d[0]);
	m_iodevice.offset(marklen);
	delete [] d;
	if (len == 0) return;
	in.insert(in.begin(), m_iodevice.current(), m_iodevice.current() + len);
	m_iodevice.offset(len);
}

// 将数据序列化
template<typename T>
inline void Serializer::input_type(T t){
	int len = sizeof(T);
	char* d = new char[len];
	const char* p = reinterpret_cast<const char*>(&t);
	memcpy(d, p, len);
	byte_orser(d, len);
	m_iodevice.input(d, len);
	delete [] d;
}

template<>
inline void Serializer::input_type(std::string in){
	// 先存入字符串长度
	uint16_t len = in.size();
	char* p = reinterpret_cast< char*>(&len);
	byte_orser(p, sizeof(uint16_t));
	m_iodevice.input(p, sizeof(uint16_t));

	// 存入字符串
	if (len == 0) return;
	char* d = new char[len];
	memcpy(d, in.c_str(), len);
	m_iodevice.input(d, len);
	delete [] d;
}

template<>
inline void Serializer::input_type(const char* in){
	input_type<std::string>(std::string(in));
}

 
接下来才是这个简单的rpc的定义和方法,对源码做了点修改,读起来更加方便一些。
一些简单的数据定义

// 如果不是void类型,就保留
template<typename T>
struct type_xx{ typedef T type; };
// 如果是void类型,就转换成uint8类型
template<>
struct type_xx<void>{ typedef uint8_t type; };

// 枚举类
enum rpc_statue{
    RPC_ERR_SUCCESS = 0,
    RPC_ERR_FUNCTIION_NOT_BIND,
    RPC_ERR_RECV_TIMEOUT  
};
template<typename T>
class value_t{
public:
    typedef typename type_xx<T>::type type;
    typedef std::string msg_type;
    typedef uint16_t code_type;

    value_t() : code_(0) { msg_.clear(); }
    bool valid() { return (code_ == 0 ? true : false); }
    int statue() { return code_; }
    std::string statue_msg() { return msg_; }
    type val() {return val_; }

    void set_val(const type& val) { val_ = val; }
    void set_msg(msg_type msg) { msg_ = msg; }
    void set_code(code_type code) { code_ = code; }

    Serializer& operator>>(Serializer& in, value_t<T>& d){
        in >> d.code_ >> d.msg_;
        if(d.code_ == 0){
            in >> d.val_;
        }
        return in;
    }

    Serializer& operator<<(Serializer& out, value_t<T> d){
        out << d.code_ << d.msg_ << d.val_;
        return out;
    }

private:
    code_type code_;
    msg_type msg_;
    type val_;

};
// rpc服务端应答客户端的传递消息的结构体
/*
    code_:用来记录本次消息的状态,是成功,或者客户端请求的方法不存在,或者客户端超时
    msg_:消息的内容,用string保存
    val_:保存消息的类型,可以说是调用方法的返回值
*/
template<typename T>
class value_t{
public:
    typedef typename type_xx<T>::type type;
    typedef std::string msg_type;
    typedef uint16_t code_type;

    value_t() : code_(0) { msg_.clear(); }
    bool valid() { return (code_ == 0 ? true : false); }
    int statue() { return code_; }
    std::string statue_msg() { return msg_; }
    type val() {return val_; }

    void set_val(const type& val) { val_ = val; }
    void set_msg(msg_type msg) { msg_ = msg; }
    void set_code(code_type code) { code_ = code; }

    Serializer& operator>>(Serializer& in, value_t<T>& d){
        in >> d.code_ >> d.msg_;
        if(d.code_ == 0){
            in >> d.val_;
        }
        return in;
    }

    Serializer& operator<<(Serializer& out, value_t<T> d){
        out << d.code_ << d.msg_ << d.val_;
        return out;
    }

private:
    code_type code_;
    msg_type msg_;
    type val_;
};

rpcclient代码
     在客户端和服务端都使用了zeromq,但是这其实值提供了tcp连接和发送接收消息的功能,没用到什么深奥的方法,其实也可以手错一个简单的tcp客户服务器。
    客户端的代码,主要是连接服务端,然后调用方法,获取调用方法的返回值。client方法很简单。

// client
class rpcclient{
public:
    rpcclient(): statue_(RPC_ERR_SUCCESS), m_context(1) {}
    rpcclient(std::string ip, int port) : ip_(ip), port_(port), statue_(RPC_ERR_SUCCESS), m_context(1) {} 
    ~rpcclient() {
        m_socket->close();
        delete m_socket;
        m_context.close();
    }

    // zeromq的调用流程
    bool init();
    void send(zmq::message_t& data);
    void recv(zmq::message_t& data);
    void set_tiemout(uint32_t ms);
    
    // 一些简单的set get方法
    bool set_ip(std::string ip) { ip_ = ip; }
    std::string get_ip() {return ip_; }
    bool set_port(int port) { port_ = port; }
    int get_port() {return port_; }

    // 主要是这call方法
    // 虽然看起来很多,起始是针对调用函数需要参数的数量
    // 其实感觉可以使用边参数的模板,没必要这样,后面再写
    template<typename R>
    value_t<R> call(std::string name);

    template<typename R, typename P1>
    value_t<R> call(std::string name, P1);    // 一个参数

    template<typename R, typename P1, typename P2>
    value_t<R> call(std::string name, P1, P2);

    template<typename R, typename P1, typename P2, typename P3>
    value_t<R> call(std::string name, P1, P2, P3);

    template<typename R, typename P1, typename P2, typename P3, typename P4>
    value_t<R> call(std::string name, P1, P2, P3, P4);

    template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
    value_t<R> call(std::string name, P1, P2, P3, P4, P5);

private:
    // 这个call是真正的网络调用
    template<typename R>
    value_t<R> net_call(Serializer& ds);

    std::string ip_;    // 服务端地址
    int port_;    // 服务端ip
    zmq::context_t* m_context;
    zmq::socket_t* m_socket;
    rpc_statue statue_;
};

// 初始化socket
bool rpcclient::init(){
    m_sokcet = new zmq::socket_t(m_context, ZMQ_REQ);
    ostringstream os;
    os << "tcp://" << ip_ << ":" << port_;
    m_socket->connect(os.str());
}

// 发送消息
void rpcclient::send( zmq::message_t& data ){
	m_socket->send(data);
}

// 接收消息
void rpccl;ient::recv(zmq::message_t& data){
    m_socket->recv(&data);
}

// 设置超时时间
inline void rpcclient::set_timeout(uint32_t ms){
	m_socket->setsockopt(ZMQ_RCVTIMEO, ms);
}

// 将消息发送过去,等待对方回复
template<typename R>
inline value_t<R> rpcserver::net_call(Serializer& ds){
    zmq::messge_t request(ds.size() + 1);
    memcpy(request.data(), ds.data(), ds.size());
    if(statue_ != RPC_ERR_RECV_TIMEOUT){
        send(request);
    }

    zmq::message_t reply;
    recv(reply);
    value_t<R> val;
    if(reply.size() == 0){
        statue_ = RPC_ERR_RECV_TIMEOUT;
        val.set_code();
        val.set_msg("recv timeout");
        return val;
    }
    statue_ = RPC_ERR_SUCCESS;
    ds.clear();
    ds.write_raw_data((char*)reply.data(), reply.size());
    ds.reset();

    ds >> val;
    return val;
}

// 将调用函数名字和参数序列化
template<typename R>
value_t<R> rpcserver::call(std::string name){
    Serializer ds;
    ds << name;
    return net_call<R>(ds);
}

template<typename R, typename P1>
value_t<R> rpcserver::call(std::string name, P1 p1){
    Serializer ds;
    ds << name << p1;
    return net_call<R>(ds);
}

template<typename R, typename P1, typename P2>
value_t<R> rpcserver::call(std::string name, P1 p1, P2 p2){
    Serializer ds;
    ds << name << d1 << d2;
    return net_call<R>(ds);
}

template<typename R, typename P1, typename P2, typename P3>
value_t<R> call(std::string name, P1 p1, P2 p2, P3 p3){
    Serializer ds;
    ds << name << d1 << d2 << d3;
    return net_call<R>(ds);
}

template<typename R, typename P1, typename P2, typename P3, typename P4>
value_t<R> call(std::string name, P1 p1, P2 p2, P3 p3, P4 p4){
    Serializer ds;
    ds << name << d1 << d2 << d3 << d4;
    return net_call<R>(ds);
}

template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
value_t<R> call(std::string name, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5){
    Serializer ds;
    ds << name << d1 << d3 << d4 << d5;
    return net_call<R>(ds);
}

 rpcserver实现
   
重点在服务端,服务端如何解码客户端的消息,服务端如何调用合适函数,如何将结果返回个客户端。

// server
class rpcserver{
public:
    rpcserver() : m_context(1),statue_(RPC_ERR_SUCCESS) {}
    rpcserver(std::string ip, int port) : ip_(ip), port(port_), m_context(1),statue_(RPC_ERR_SUCCESS) {}
    ~rpcserver();
    // 服务端启动流程
    void init();
    void send(zmq::message_t& data);
	void recv(zmq::message_t& data);
    void run();
    
    // 服务端绑定函数,是对客户端提供的函数方法
    template<typename F>
	void bind(std::string name, F func);

	template<typename F, typename S>
	void bind(std::string name, F func, S* s);


private:
    // 调用函数
    Serializer* call_(std::string name, const char* data, int len);

    // 代理模式,看着很复杂,其实就是应为提供的方法很简单,所以用了一种全部展开的方法 
    template<typename F>
    void callproxy(F fun, Serialzer* pr, const char* data, int len);

    template<typename R, typename P1>
    void callproxy(F fun, S* s, Serialzer* pr, const char* data, int len);

    // PROXY FUNCTION POINT
    // 这些方法最终调用的都是下面PORXY FUNCTIONAL模板函数
    template<typename R>
    void callproxy_(R(*fun)(), Serialzer* pr, const char* data, int len){
        callproxy_(std::function<R()>(func), pr, data, len);
    }

    template<typename R, typename P1>
    void callproxy_(R(*func)(P1), Serialzer* pr, const char* pr, int len){
        callproxy_(std::function<R(P1)>(func), pr, data, len);
    }

	template<typename R, typename P1, typename P2>
	void callproxy_(R(*func)(P1, P2), Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2)>(func), pr, data, len);
	}

    template<typename R, typename P1, typename P2, typename P3>
	void callproxy_(R(*func)(P1, P2, P3), Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2, P3)>(func), pr, data, len);
	}

   	template<typename R, typename P1, typename P2, typename P3, typename P4>
	void callproxy_(R(*func)(P1, P2, P3, P4), Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2, P3, P4)>(func), pr, data, len);
	}

    template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
	void callproxy_(R(*func)(P1, P2, P3, P4, P5), Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2, P3, P4, P5)>(func), pr, data, len);
	}

    // PROXY CLASS MEMBER
    // 这些方法最终调用的都是下面PORXY FUNCTIONAL模板函数
    template<typename R, typename C, typename S>
	void callproxy_(R(C::* func)(), S* s, Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R()>(std::bind(func, s)), pr, data, len);
	}

	template<typename R, typename C, typename S, typename P1>
	void callproxy_(R(C::* func)(P1), S* s, Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1)>(std::bind(func, s, std::placeholders::_1)), pr, data, len);
	}

	template<typename R, typename C, typename S, typename P1, typename P2>
	void callproxy_(R(C::* func)(P1, P2), S* s, Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2)>(std::bind(func, s, std::placeholders::_1, std::placeholders::_2)), pr, data, len);
	}

	template<typename R, typename C, typename S, typename P1, typename P2, typename P3>
	void callproxy_(R(C::* func)(P1, P2, P3), S* s, Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2, P3)>(std::bind(func, s, 
			std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)), pr, data, len);
	}

	template<typename R, typename C, typename S, typename P1, typename P2, typename P3, typename P4>
	void callproxy_(R(C::* func)(P1, P2, P3, P4), S* s, Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2, P3, P4)>(std::bind(func, s,
			std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)), pr, data, len);
	}

	template<typename R, typename C, typename S, typename P1, typename P2, typename P3, typename P4, typename P5>
	void callproxy_(R(C::* func)(P1, P2, P3, P4, P5), S* s, Serializer* pr, const char* data, int len) {
		callproxy_(std::function<R(P1, P2, P3, P4, P5)>(std::bind(func, s,
			std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)), pr, data, len);
	}

    // PORXY FUNCTIONAL
    template<typename R>
	void callproxy_(std::function<R()>, Serializer* pr, const char* data, int len);

	template<typename R, typename P1>
	void callproxy_(std::function<R(P1)>, Serializer* pr, const char* data, int len);

	template<typename R, typename P1, typename P2>
	void callproxy_(std::function<R(P1, P2)>, Serializer* pr, const char* data, int len);

	template<typename R, typename P1, typename P2, typename P3>
	void callproxy_(std::function<R(P1, P2, P3)>, Serializer* pr, const char* data, int len);

	template<typename R, typename P1, typename P2, typename P3, typename P4>
	void callproxy_(std::function<R(P1, P2, P3, P4)>, Serializer* pr, const char* data, int len);

	template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
	void callproxy_(std::function<R(P1, P2, P3, P4, P5)>, Serializer* pr, const char* data, int len);


private:
    std::string ip_;
    int port_;

    std::map<std::string, std::function<void(Serializer*, const char*, int)>> m_handlers;
    zmq::context_t m_context;
	zmq::socket_t* m_socket;

	rpc_statue statue_;    
};


rpcserver::~rpcserver(){
    m_socket->close();
    delete m_socket;
    m_context.close();
}

void rpcserver::init(){
    m_socket = new zmq::socket_t(m_context, ZMQ_REP);
    ostringstream os;
	os << "tcp://*:" << port;
	m_socket->bind (os.str());
}

void rpcserver::send( zmq::message_t& data ){
	m_socket->send(data);
}

void rpcserver::recv( zmq::message_t& data ){
	m_socket->recv(&data);
}


void rpcserver::run(){
	// only server can call
    while (1){
        zmq::message_t data;
        recv(data);
        StreamBuffer iodev((char*)data.data(), data.size());
        Serializer ds(iodev);

        std::string funname;
        ds >> funname;
        Serializer* r = call_(funname, ds.current(), ds.size()- funname.size());

        zmq::message_t retmsg (r->size());
        memcpy (retmsg.data (), r->data(), r->size());
        send(retmsg);
        delete r;
    }
}

Serializer* rpcserver::call_(std::string name, const char* data, int len){
	Serializer* ds = new Serializer();
	if (m_handlers.find(name) == m_handlers.end()) {
		(*ds) << value_t<int>::code_type(RPC_ERR_FUNCTIION_NOT_BIND);
		(*ds) << value_t<int>::msg_type("function not bind: " + name);
		return ds;
	}
	auto fun = m_handlers[name];
	fun(ds, data, len);
	ds->reset();
	return ds;
}

template<typename F>
void rpcserver::bind(std::string name, F func){
    m_handlers[name] = std::bind(&rpcserver::callproxy<F>, this, func, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
}


template<typename F, typename S>
inline void rpcserver::bind(std::string name, F func, S* s){
	m_handlers[name] = std::bind(&rpcserver::callproxy<F, S>, this, func, s, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
}

// 调用impl
template<typename F>
void rpcserver::callproxy( F fun, Serializer* pr, const char* data, int len ){
	callproxy_(fun, pr, data, len);
}

template<typename F, typename S>
inline void rpcserver::callproxy(F fun, S* s, Serializer* pr, const char* data, int len){
	callproxy_(fun, s, pr, data, len);
}

// 为了解决函数返回值的问题,由于返回void的函数没有返回值,所以这里只能代理
// 让void函数返回(uint8)0
// 如果是void类型,就返回0
template<typename R, typename F>
typename std::enable_if<std::is_same<R, void>::value, typename type_xx<R>::type>::type call_helper(F f) {
	f();
	return 0;
}

// 如果不是void类型,就返回原函数
template<typename R, typename F>
typename std::enable_if<!std::is_same<R, void>::value, typename type_xx<R>::type >::type call_helper(F f) {
	return f();
}

template<typename R>
void rpcserver::callproxy_(std::function<R()> func, Serializer* pr, const char* data, int len){
    typename type_xx<R>::type r = call_helper<R>(std::bind(func));

    value_t<R> val;
    val.set_code(RPC_ERR_SUCCESS);
    val.set_val(r);
    (*pr) << val;
}

template<typename R, typename P1>
void rpcserver::callproxy_(std::function<R(P1)> func, Serializer* pr, const char* data, int len){
	Serializer ds(StreamBuffer(data, len));
	P1 p1;
	ds >> p1;
	typename type_xx<R>::type r = call_helper<R>(std::bind(func, p1));

	value_t<R> val;
	val.set_code(RPC_ERR_SUCCESS);
	val.set_val(r);
	(*pr) << val;
}


template<typename R, typename P1, typename P2>
void rpcserver::callproxy_(std::function<R(P1, P2)> func, Serializer* pr, const char* data, int len ){
	Serializer ds(StreamBuffer(data, len));
	P1 p1; P2 p2;
	ds >> p1 >> p2;
	typename type_xx<R>::type r = call_helper<R>(std::bind(func, p1, p2));
	
	value_t<R> val;
	val.set_code(RPC_ERR_SUCCESS);
	val.set_val(r);
	(*pr) << val;
}


template<typename R, typename P1, typename P2, typename P3>
void rpcserver::callproxy_(std::function<R(P1, P2, P3)> func, Serializer* pr, const char* data, int len){
	Serializer ds(StreamBuffer(data, len));
	P1 p1; P2 p2; P3 p3;
	ds >> p1 >> p2 >> p3;
	typename type_xx<R>::type r = call_helper<R>(std::bind(func, p1, p2, p3));
	value_t<R> val;
	val.set_code(RPC_ERR_SUCCESS);
	val.set_val(r);
	(*pr) << val;
}

template<typename R, typename P1, typename P2, typename P3, typename P4>
void rpcserver::callproxy_(std::function<R(P1, P2, P3, P4)> func, Serializer* pr, const char* data, int len){
	Serializer ds(StreamBuffer(data, len));
	P1 p1; P2 p2; P3 p3; P4 p4;
	ds >> p1 >> p2 >> p3 >> p4;
	typename type_xx<R>::type r = call_helper<R>(std::bind(func, p1, p2, p3, p4));
	value_t<R> val;
	val.set_code(RPC_ERR_SUCCESS);
	val.set_val(r);
	(*pr) << val;
}

template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
void rpcserver::callproxy_(std::function<R(P1, P2, P3, P4, P5)> func, Serializer* pr, const char* data, int len){
	Serializer ds(StreamBuffer(data, len));
	P1 p1; P2 p2; P3 p3; P4 p4; P5 p5;
	ds >> p1 >> p2 >> p3 >> p4 >> p5;
	typename type_xx<R>::type r = call_helper<R>(std::bind(func, p1, p2, p3, p4, p5));
	value_t<R> val;
	val.set_code(RPC_ERR_SUCCESS);
	val.set_val(r);
	(*pr) << val;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值