SOCKET通信-TCP/IP,libevent

主要是用来记录自己在项目中遇到要使用socket中的tcp/ip通信。因为该项目只用tcp/ip,所以对于udp,http这些不做记录
这些例子的学习在网上一大堆,这个是我学习的链接,这个链接里面用了多线程,一个线程用来读,一个线程用来写,总的来说都比较简单,属于有手就会,也不难理解
在server端代码

#include <iostream>
#include <winsock.h>

#pragma comment(lib, "ws2_32.lib")

using namespace std;

void initialization();

int main()
{
	// 定义长度变量
	int send_len = 0;
	int recv_len = 0;
	int len = 0;
	// 发送和接收缓冲区
	char send_buf[100];
	char recv_buf[100];
	// 服务端套接字,接收请求套接字
	SOCKET s_server;
	SOCKET s_accept;
	// 服务端地址和客户端地址
	SOCKADDR_IN server_addr;
	SOCKADDR_IN accept_addr;
	initialization();
	// 服务端信息
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	server_addr.sin_port = htons(5060);
	// 创建套接字 绑定
	s_server = socket(AF_INET, SOCK_STREAM, 0);
	if (bind(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
	{
		cout << "绑定失败" << endl;
	}
	else
	{
		cout << "绑定成功" << endl;
	}
	// 监听
	if (listen(s_server, SOMAXCONN) < 0)
	{
		cout << "设置监听状态失败!" << endl;
		WSACleanup();
	}
	else
	{
		cout << "设置监听状态成功!" << endl;
	}

	cout << "服务端正在监听连接,请稍后......" << endl;
	// 连接请求
	len = sizeof(SOCKADDR);
	s_accept = accept(s_server, (SOCKADDR*)&accept_addr, &len);
	if (s_accept == SOCKET_ERROR)
	{
		cout << "连接失败" << endl;
		WSACleanup();
		return 0;
	}
	cout << "建立连接,准备接收数据" << endl;
	// 接收数据
	while (1)
	{
		// 从 recv 获取消息内容
		recv_len = recv(s_accept, recv_buf, 100, 0);
		if (recv_len < 0)
		{
			cout << "接收失败!" << endl;
			break;
		}
		else
		{
			cout << "客户端信息:" << recv_buf << endl;
		}
		cout << "请输入回复信息:" << endl;
		cin >> send_buf;
		send_len = send(s_accept, send_buf, 100, 0);
		if (send_len < 0)
		{
			cout << "发送失败" << endl;
			break;
		}
		
	}
	// 关闭套接字
	closesocket(s_server);
	closesocket(s_accept);
	WSACleanup();
	return 0;
}

void initialization()
{
	WORD w_req = MAKEWORD(2, 2);
	WSADATA wsadata;
	int err;
	err = WSAStartup(w_req, &wsadata);
	if (err != 0)
	{
		cout << "初始化套接字失败" << endl;
	}
	else
	{
		cout << "初始化套接字成功" << endl;
	}

	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2)
	{
		cout << "套接字版本号不符!" << endl;
		WSACleanup();
	}
	else
	{
		cout << "套接字版本正确!" << endl;
	}
}

在client端代码

#include <iostream>
#include <winsock.h>

#pragma comment(lib, "ws2_32.lib")

using namespace std;

void initialization();

int main()
{
	// 定义变量长度
	int send_len = 0;
	int recv_len = 0;
	// 定义发送缓冲区和接收缓冲区
	char send_buf[100];
	char recv_buf[100];
	// 定义服务端套接字,接受请求套接字
	SOCKET s_server;
	// 服务端地址
	SOCKADDR_IN server_addr;
	initialization();
	// 填充信息
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	server_addr.sin_port = htons(5060);
	// 连接服务端
	s_server = socket(AF_INET, SOCK_STREAM, 0);
	if (connect(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
	{
		cout << "服务器连接失败" << endl;
		WSACleanup();
	}
	else
	{
		cout << "服务器连接成功" << endl;
	}

	while (1)
	{
		cout << "请输入发送数据:";
		cin >> send_buf;
		send_len = send(s_server, send_buf, 100, 0);
		if (send_len < 0)
		{
			cout << "发送失败!" << endl;
			break;
		}
		recv_len = recv(s_server, recv_buf, 100, 0);
		if (recv_len < 0)
		{
			cout << "接收失败" << endl;
			break;
		}
		else
		{
			cout << "服务端消息:" << recv_buf << endl;
		}
	}

	closesocket(s_server);
	WSACleanup();
	return 0;
}

void initialization()
{
	WORD w_req = MAKEWORD(2, 2);
	WSADATA wsadata;
	int err;
	err = WSAStartup(w_req, &wsadata);
	if (err != 0)
	{
		cout << "初始化套接字失败" << endl;
	}
	else
	{
		cout << "初始化套接字成功" << endl;
	}

	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2)
	{
		cout << "套接字版本号不符!" << endl;
		WSACleanup();
	}
	else
	{
		cout << "套接字版本正确!" << endl;
	}
}

运行上面的代码只需要新建两个VS项目,在不同的项目里面分别运行服务端和客户端就可以了。如果是两台电脑,客户端的IP那里要修改。
上面代码是纯C++的,之前学习socket的时候是学的QT,所以附上QT的代码,在QT里面是写的一个mainwindow类
server端

// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ui->port->setText("8899");
    setWindowTitle("服务器 ");

    // 1. 创建监听的服务器对象
    m_s = new QTcpServer(this);

    // 3. 检测有没有客户端连接
    // 该槽函数不是在构造函数中被执行的。当某一个信号被发送出来时,对应的槽函数才被执行,是一个回调机制。
    // connect 只是做了一个注册,告诉QT框架在什么时机,什么条件下做什么事情
    connect(m_s, &QTcpServer::newConnection, this, [=](){
        // 在第2步监听完之后 QT框架检测 QTcpServer 有没有发射出 newConnection 信号,如果有信号表示有客户端连接到达,服务器就会和客户端建立连接,
        // 建立连接完之后就会得到一个用于通信的套接字对象 m_tcp,m_tcp指向一块有效的堆内存
        // 如果有多个客户端同时通信,那么就会得到多个套接字对象,此时应该怎么处理?
        // 在内存析构这里,m_tcp是子对象,m_s是父对象,所以m_s析构的时候会先析构m_tcp
        // 3.1 有客户端连接,得到一个新的套接字对象
        m_tcp = m_s->nextPendingConnection();
        // 连接成功,把状态栏的标签替换
        m_status->setPixmap(QPixmap(":/连接.jpg").scaled(20, 20));

        // 3.2 检查是否可以接受数据
        connect(m_tcp, &QTcpSocket::readyRead, this, [=](){
            // 进行数据接收
            QByteArray data = m_tcp->readAll();
            ui->record->append("客户端say:" + data);
        });

        // 3.3 检测客户端状态连接,服务器也需要断开,双向断开,如果未连接就断开,状态标签替换
        connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
            m_tcp->close();
            m_tcp->deleteLater();   // 封装了 delete
            m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
        });
    });

    // 状态栏的处理动作
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
    ui->statusBar->addWidget(new QLabel("连接状态:"));
    ui->statusBar->addWidget(m_status);
}

MainWindow::~MainWindow()
{
    delete ui;
}

// 2. 监听
void MainWindow::on_setListen_clicked()
{
    unsigned short port = ui->port->text().toUShort();
    // 2. 监听 port要指定固定的端口最大值为65535,最小值为0,建议为5000以上
    m_s->listen(QHostAddress::Any, port);
    // 将 启动监听服务 按钮置灰
    ui->setListen->setDisabled(true);
}

// 4. 给客户端回复数据的时间
void MainWindow::on_sendMassage_clicked()
{
    // toPlainText 将数据以纯文本的方式取出来
    QString msg = ui->msg->toPlainText();
    // 4. 调用 write 方法把数据发送给客户端
    m_tcp->write(msg.toUtf8());
    ui->record->append("服务端say:" + msg);
}

下面是server的.h文件

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QLabel>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_setListen_clicked();

    void on_sendMassage_clicked();

private:
    Ui::MainWindow *ui;
    QTcpServer* m_s;
    QTcpSocket* m_tcp;
    QLabel* m_status;
};

#endif // MAINWINDOW_H

client端

// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QHostAddress>


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->ip->setText("127.0.0.1");  // 127.0.0.1表示本机
    ui->port->setText("8899");
    setWindowTitle("客户端 ");
    ui->disconnect->setDisabled(true);

    // 1. 创建监听的客户端对象
    m_tcp = new QTcpSocket;

    // 3.2 检查是否可以接受数据
    connect(m_tcp, &QTcpSocket::readyRead, this, [=](){
        // QT中使用 QTcpSocket 进行数据的操作,操作的数据不是直接对网络中的数据进行操作,是本地的数据。
        // 如:B给A发送数据,QT会维护一块内存存放B发送过来的数据,在A这里通过 readAll 读取的是QT本地那块内存的数据
        // 进行数据接收
        QByteArray data = m_tcp->readAll();
        ui->record->append("服务端say:" + data);
    });

    // 3.3 检测客户端状态连接,如果未连接就断开,状态标签替换
    // 检测到断开连接是对方端断开了连接
    connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
        m_tcp->close();
        m_tcp->deleteLater();   // 封装了 delete 在此处释放了,所以在创建监听的客户端对象那里就不需要this了
        m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
        ui->record->append("已经和服务器断开连接");
        ui->connect->setDisabled(false);
        ui->disconnect->setEnabled(false);
    });

    // 在①之后,如果信号被成功连接,则信号被发射
    // 这里的 connected 检测只能在客户端检测,服务端不能检测
    connect(m_tcp, &QTcpSocket::connected, this, [=](){
        m_status->setPixmap(QPixmap(":/连接.jpg").scaled(20, 20));
        ui->record->append("已经连接成功到服务器....");
        ui->connect->setDisabled(true);
        ui->disconnect->setEnabled(true);
    });

    // 状态栏的处理动作
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
    ui->statusBar->addWidget(new QLabel("连接状态:"));
    ui->statusBar->addWidget(m_status);
}

MainWindow::~MainWindow()
{
    delete ui;
}


// 4. 给客户端回复数据的时间
void MainWindow::on_sendMassage_clicked()
{
    QString msg = ui->msg->toPlainText();
    // 4. 调用 write 方法把数据发送给客户端
    m_tcp->write(msg.toUtf8());
    ui->record->append("客户端say:" + msg);
}

// 1. 连接服务器
void MainWindow::on_connect_clicked()
{
    QString ip = ui->ip->text();
    unsigned short port = ui->port->text().toUShort();
    // ① connectToHost用于客户端连接服务器 port是服务器绑定的端口
    m_tcp->connectToHost(QHostAddress(ip), port);
}

void MainWindow::on_disconnect_clicked()
{
    m_tcp->close();
    ui->connect->setDisabled(false);
    ui->disconnect->setEnabled(false);
}

同样的.h文件如下

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpSocket>
#include <QLabel>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_sendMassage_clicked();

    void on_connect_clicked();

    void on_disconnect_clicked();

private:
    Ui::MainWindow *ui;
    QTcpSocket* m_tcp;
    QLabel* m_status;
};

#endif // MAINWINDOW_H

后来的时候同事给我说使用libevent库更好用,后面学习了一下就用libevent库了,简单的实现了一下TCP/IP,反正都不难,下面这块代码是在网上找的,具体连接我找不到了,先把代码放这
server端

// libevent_server.cpp
#include "libevent_server.h"
#include <iostream>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/event.h>


libevent_server::libevent_server()
{
}


libevent_server::~libevent_server()
{
}

void libevent_server::conn_readcb(struct bufferevent *bev, void *user_data)
{
	char buf[1024] = { 0 };

	 直接将数据从bufferevent中读取出来
	//bufferevent_read(bev, buf, sizeof(buf));
	//int length = strlen(buf);
	//if (length)
	//{
	//  std::cout << "server recv msg:" << buf << std::endl;
	//}

	// 将数据从bufferevent存放到evbuffer中
	struct evbuffer *input = bufferevent_get_input(bev);

	int length = evbuffer_get_length(input);

	if (length)
	{
		evbuffer_remove(input, buf, sizeof(buf));

		std::cout << "server recv msg:" << buf << std::endl;

		bufferevent_getfd(bev);

		bufferevent_write(bev, buf, strlen(buf) + 1);

	}
	memset(buf, 0, 1024);
	return;
}

void libevent_server::conn_writecb(struct bufferevent *bev, void *user_data)
{
	// listen server send message. if server not send message will not trigger this callback
	// if send message not use evbuffer, then bufferevent_get_input can not get the message
	char buf[1024] = { 0 };

	struct evbuffer *output = bufferevent_get_output(bev);

	int length = evbuffer_get_length(output);

	if (length)
	{
		evbuffer_copyout(output, buf, sizeof(buf));

		std::cout << "server send msg:" << buf << std::endl;
	}

	evbuffer_free(output);
	memset(buf, 0, 1024);
	return;
}

void libevent_server::conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
	if (events & BEV_EVENT_EOF)
	{
		std::cout << "Connection closed." << std::endl;
	}
	else if (events & BEV_EVENT_ERROR)
	{
		std::cout << "Got an error on the connection." << std::endl;
	}
	else if (events & BEV_EVENT_CONNECTED)
	{
		std::cout << "Connection client success." << std::endl;
	}
	
	/* None of the other events can happen here, since we haven't enabled timeouts */
	bufferevent_free(bev);
	return;
}

void libevent_server::listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data)
{
	struct event_base *base = (struct event_base *)user_data;
	struct bufferevent *bev;

	bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	if (!bev)
	{
		std::cout << "Error constructing bufferevent!" << std::endl;
		event_base_loopbreak(base);
		return;
	}
	else
	{
		std::cout << "连接成功!" << std::endl;
	}
	bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, NULL);

	bufferevent_enable(bev, EV_READ | EV_WRITE);

	const char *p = "connect success!";
	bufferevent_write(bev, p, strlen(p) + 1);

	return;
}

void libevent_server::initial(int port)
{
	m_port = port;
	m_sin = { 0 };
	m_sin.sin_family = AF_INET;
	m_sin.sin_port = htons(m_port);

#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	m_base = event_base_new();
	if (!m_base)
	{
		std::cout << "not initial libevent." << std::endl;
		return;
	}
	return;
}


void libevent_server::creat_bind_accept()
{
	// 绑定
	m_listener = evconnlistener_new_bind(m_base, listener_cb, (void *)m_base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
		(struct sockaddr*)&m_sin, sizeof(m_sin));

	// 启动循环监听
	event_base_dispatch(m_base);

	// 释放
	evconnlistener_free(m_listener);
	event_base_free(m_base);
	return;
}

下面是.h文件

// libevent_server.h
#pragma once
#include <event2/util.h>


class libevent_server
{
public:
	libevent_server();
	~libevent_server();


	void initial(int port);

	void creat_bind_accept();


private:
	struct sockaddr_in m_sin;
	int m_port;
	struct event_base *m_base;
	struct evconnlistener *m_listener;

private:
	static void conn_readcb(struct bufferevent *bev, void *user_data);
	static void conn_writecb(struct bufferevent *bev, void *user_data);
	static void conn_eventcb(struct bufferevent *bev, short events, void *user_data);
	static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data);

};

在mian函数里面直接调用就可以了

#include <iostream>
#include <string>
#include "libevent_server.h"

int main()
{
	libevent_server server;
	server.initial(6000);
	server.creat_bind_accept();
}

在客户端

#include "libevent_client.h"
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/event.h>


libevent_client::libevent_client()
{
}


libevent_client::~libevent_client()
{
}

void libevent_client::initial(int port)
{
	m_port = port;
	memset(&m_sin, 0, sizeof(m_sin));
	m_sin.sin_family = AF_INET;
	m_sin.sin_port = htons(m_port);
	//m_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	inet_pton(AF_INET, "192.168.2.69", &m_sin.sin_addr.s_addr);

#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	// 创建base
	m_base = event_base_new();

	// 将套接字和base关联起来,得到bufferevent
	m_bev = bufferevent_socket_new(m_base, -1, BEV_OPT_CLOSE_ON_FREE);
	if (!m_bev)
	{
		std::cout << "error , create socket buffer." << std::endl;
		return;
	}
}

void libevent_client::read_cb(struct bufferevent *bev, void *arg)
{
	char buf[1024] = { 0 };

	//直接从bufferevent将数据读取出来
	bufferevent_read(bev, buf, sizeof(buf));
	int length = strlen(buf);
	if (length)
	{
		std::cout << "client recv msg:" << buf << std::endl;
	}


	// // 从bufferevent将数据读取出来,存放到evbuffer中
	//struct evbuffer *input = bufferevent_get_input(bev);

	//int length = evbuffer_get_length(input);

	//if (length)
	//{
	//  evbuffer_remove(input, buf, sizeof(buf));

	//  std::cout << "client recv msg:" << buf << std::endl;
	//}

	memset(buf, 0, 1024);
	return;
}

void libevent_client::write_cb(struct bufferevent *bev, void *arg)
{
	 listen client send message. if client not send message will not trigger this callback
	 if send message not use evbuffer, then bufferevent_get_input can not get the message
	//char buf[1024] = { 0 };

	//struct evbuffer *output = bufferevent_get_output(bev);

	//int length = evbuffer_get_length(output);

	//if (length)
	//{
	//  evbuffer_remove(output, buf, sizeof(buf));

	//  std::cout << "client send msg:" << buf << std::endl;
	//}

	//memset(buf, 0, 1024);

	return;
}

void libevent_client::event_cb(struct bufferevent *bev, short events, void *arg)
{
	if (events & BEV_EVENT_EOF)
	{
		std::cout << "Connection closed." << std::endl;
		bufferevent_free(bev);
	}
	else if (events & BEV_EVENT_ERROR)
	{
		std::cout << "Got an error on the connection." << std::endl;
		bufferevent_free(bev);
	}
	else if (events & BEV_EVENT_CONNECTED)
	{
		std::cout << "Connection server success." << std::endl;
	}
	return;
}

void libevent_client::input_cb(evutil_socket_t fd, short events, void *arg)
{
	// 输入事件加入到未决事件集中后,激活了input_cb,此时event_cb还未被激活
	bufferevent *bev = (bufferevent *)arg;
	char buf[1024] = { 0 };
	if (i > 0 && i < 11)
	{
		//std::cout << "input the message send to server: ";
		//std::cin >> buf;
		memcpy(buf, itoa(i, buf, _MAX_ITOSTR_BASE10_COUNT), 1);
	}
	if (strlen(buf))
	{
		bufferevent_write(bev, buf, strlen(buf) + 1);
	}
	memset(buf, 0, 1024);
	Sleep(1000);
	i++;
	return;
}


void libevent_client::connect_communicate()
{
	// 连接服务端
	int res = bufferevent_socket_connect(m_bev, (struct sockaddr *)&m_sin, sizeof(m_sin));

	if (res != 0)
	{
		std::cout << "connect failed." << std::endl;
		return;
	}

	bufferevent_setcb(m_bev, read_cb, write_cb, event_cb, NULL);

	bufferevent_enable(m_bev, EV_READ | EV_PERSIST);

	m_event = event_new(m_base, bufferevent_getfd(m_bev), EV_WRITE | EV_PERSIST, input_cb, m_bev);

	event_add(m_event, NULL);

	int i = event_base_get_num_events(m_base, EVENT_BASE_COUNT_ADDED);

	event_base_dispatch(m_base);

	event_base_free(m_base);

	event_free(m_event);

	return;
}

.h文件

#pragma once
#include <event2/util.h>

class libevent_client
{
public:
	libevent_client();
	~libevent_client();

	void initial(int port);

	void connect_communicate();

private:
	struct sockaddr_in m_sin;
	int m_port;
	evutil_socket_t m_fd;
	struct event_base *m_base;
	struct bufferevent *m_bev;
	struct event *m_event;


private:
	static void read_cb(struct bufferevent *bev, void *arg);

	static void write_cb(struct bufferevent *bev, void *arg);

	static void event_cb(struct bufferevent *bev, short events, void *arg);

	static void input_cb(evutil_socket_t fd, short events, void *arg);

	static int i;
};

在main函数中调用

#include <iostream>
#include "libevent_client.h"
int libevent_client::i = 0;

int main()
{
	libevent_client client;
	client.initial(6000);
	client.connect_communicate();
}

同样的,如果是在同一台电脑客户端和服务端在两个VS项目中打开,将客户端的initial接口中修改自己电脑的IP就可以运行。也可以将//m_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");代码放开,然后注释下面代码运行也可以。如果是在两台电脑,就用下面inet_pton(AF_INET, "192.168.2.31", &m_sin.sin_addr.s_addr);这个,把ip给改了就可以了。
记录

// 127.0.0.1表示本机

在libevent中没有写很多的注释,但是我感觉学习了libevent就能理解了。加油

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值