使用CAsyncSocket类进行网络编程

8 篇文章 2 订阅

1 服务器端

由先得专门为服务器端做一个Socket通信类CNewSocket类,此类继承CAsyncSocket类,专门负责服务器端socket通信事情:

NewSocket.h:

#pragma once
#include "afxsock.h"
//此类专门用来与客户端进行socket通信
class CNewSocket :
	public CAsyncSocket
{
public:
	CNewSocket(void);
	~CNewSocket(void);
	virtual void OnReceive(int nErrorCode);
	virtual void OnSend(int nErrorCode);
	// 消息长度
	UINT m_nLength;
	//消息缓冲区
	char m_szBuffer[4096];
};

对应实现代码如下:

NewSocket.cpp:

#include "StdAfx.h"
#include "NewSocket.h"


CNewSocket::CNewSocket(void)
	: m_nLength(0)
{
	memset(m_szBuffer,0,sizeof(m_szBuffer));
}


CNewSocket::~CNewSocket(void)
{
	if(m_hSocket !=INVALID_SOCKET)
	{
		Close();
	}
}


void CNewSocket::OnReceive(int nErrorCode)
{
	// TODO: Add your specialized code here and/or call the base class
	m_nLength =Receive(m_szBuffer,sizeof(m_szBuffer),0); //接收数据
	m_szBuffer[m_nLength] ='\0';
	//接收消息后就开始给客户端发相同的消息
	AsyncSelect(FD_WRITE); //触发OnSend函数
	CAsyncSocket::OnReceive(nErrorCode);
}


void CNewSocket::OnSend(int nErrorCode)
{
	// TODO: Add your specialized code here and/or call the base class
	char m_sendBuf[4096];	//消息缓冲区

	strcpy(m_sendBuf,"server send:");
	strcat(m_sendBuf,m_szBuffer);
	Send(m_sendBuf,strlen(m_sendBuf));

	//触发OnReceive函数
	AsyncSelect(FD_READ);
	CAsyncSocket::OnSend(nErrorCode);
}

如上,NewSocket类重载了CAsyncSocket类的接收与发送事件处理例程,一旦被触发了发送或接收事件,将调用此对应函数.

接下来服务器端在做一个专门类CListenSocket类,用来监听来自客户端的连接请求,如下:

ListenSocket.h:

#pragma once
#include "afxsock.h"
#include "NewSocket.h"
class CListenSocket :
	public CAsyncSocket
{
public:
	CListenSocket(void);
	~CListenSocket(void);

	CNewSocket *m_pSocket; //指向一个连接的socket对象
	virtual void OnAccept(int nErrorCode);
};

由上可知,CListenSocket类还是继承了CAsyncSocket类,并重载了其接受事件,且包含了一个由之前定义好的CNewSocket类指针做为成员,用来指向与客户端连接好的连接.
其实现代码如下:

ListenSocket.cpp:

#include "StdAfx.h"
#include "ListenSocket.h"


CListenSocket::CListenSocket(void)
{
}


CListenSocket::~CListenSocket(void)
{
}


void CListenSocket::OnAccept(int nErrorCode)
{
	// TODO: Add your specialized code here and/or call the base class
	CNewSocket *pSocket =new CNewSocket();
	if(Accept(*pSocket))
	{
		pSocket->AsyncSelect(FD_READ); //触发通信socket的Read函数读数据
		m_pSocket =pSocket;
	}
	else
	{
		delete pSocket;
	}
	CAsyncSocket::OnAccept(nErrorCode);
}

这个OnAccept事件触发是在Listen之后,如果有客户端尝试连接时触发,且先看后续内容.

接下来就是对话框的代码了:

SocketServerDlg.h:

// SocketServerDlg.h : header file
//

#pragma once
#include "ListenSocket.h"

// CSocketServerDlg dialog
class CSocketServerDlg : public CDialogEx
{
// Construction
public:
	CSocketServerDlg(CWnd* pParent = NULL);	// standard constructor

// Dialog Data
	enum { IDD = IDD_SOCKETSERVER_DIALOG };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support

	CListenSocket m_srvrSocket; //监听套接字
// Implementation
protected:
	HICON m_hIcon;

	// Generated message map functions
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnBnClickedBtStart();
};
在这个CSocketServerDlg类的声明内包含了一个CListenSocket成员m_srvrSocket,并有一个start按键.

// SocketServerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "SocketServer.h"
#include "SocketServerDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CAboutDlg dialog used for App About

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// Dialog Data
	enum { IDD = IDD_ABOUTBOX };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CSocketServerDlg dialog




CSocketServerDlg::CSocketServerDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CSocketServerDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CSocketServerDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CSocketServerDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BT_START, &CSocketServerDlg::OnBnClickedBtStart)
END_MESSAGE_MAP()


// CSocketServerDlg message handlers

BOOL CSocketServerDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CSocketServerDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CSocketServerDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CSocketServerDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}



void CSocketServerDlg::OnBnClickedBtStart()
{
	// TODO: Add your control notification handler code here

	if(m_srvrSocket.m_hSocket ==INVALID_SOCKET)
	{
		//创建监听套接字,激发FD_ACCEPT事件,默认端口7190
		BOOL bFlag =m_srvrSocket.Create(7190,SOCK_STREAM,FD_ACCEPT);    //第三个参数表示m_srvrSocket只对FD_ACCEPT事件感兴趣
		if(!bFlag)
		{
			AfxMessageBox(_T("Socket创建失败!"));
			m_srvrSocket.Close();

			PostQuitMessage(0);

			return;
		}
		GetDlgItem(IDC_BT_START)->EnableWindow(FALSE);

		//监听成功,等待连接请求
		if(!m_srvrSocket.Listen())//如果监听失败
		{
			int nErrorCode =m_srvrSocket.GetLastError();	//检测错误信息
			if(nErrorCode !=WSAEWOULDBLOCK)				//如果不是线程阻塞
			{
				AfxMessageBox(_T("Socket错误!"));
				m_srvrSocket.Close();
				PostQuitMessage(0);

				return;
			}
		}
	}
}

当用户按下start按键时,程序首先调用m_srvrSocket的Create函数创建一个socket,然后调用它的Listen函数监听客户端的连接请求,一旦客户端尝试连接服务器,那将触发m_srvrSocket的FD_ACCEPT事件.接着在m_srvrSocket的OnAccept函数内,将创建一个CNewSocket对象,并利用此对象来操作socket收发,到此,服务器端的代码完.


2 客户端

这里的客户端的功能就是与服务器建立连接,交用户的数据发送给服务器,并显示来自服务器的接收数据.

与服务器类似,首先做一个专门用于socket通信的类ClientSocket类:

ClientSocket.h:

#pragma once
#include "afxsock.h"
class ClientSocket :
	public CAsyncSocket
{
public:
	ClientSocket(void);
	~ClientSocket(void);
	// 是否连接
	bool m_bConnected;
	// 消息长度
	UINT m_nLength;
	//消息缓冲区
	char m_szBuffer[5096];
	virtual void OnConnect(int nErrorCode);
	virtual void OnReceive(int nErrorCode);
	virtual void OnSend(int nErrorCode);
};

由上类声明可知,此类重载了CAsyncSocket的连接,接收,发送事件例程.其实现代码如下:

#include "StdAfx.h"
#include "ClientSocket.h"
#include "SocketTest.h"
#include "SocketTestDlg.h"


ClientSocket::ClientSocket(void)
	: m_bConnected(false)
	, m_nLength(0)
{
	memset(m_szBuffer,0,sizeof(m_szBuffer));
}


ClientSocket::~ClientSocket(void)
{
	if(m_hSocket !=INVALID_SOCKET)
	{
		Close();
	}
}


void ClientSocket::OnConnect(int nErrorCode)
{
	// TODO: Add your specialized code here and/or call the base class
	//连接成功
	if(nErrorCode ==0)
	{
		m_bConnected =TRUE;
		

		//获取主程序句柄
		CSocketTestApp *pApp =(CSocketTestApp *)AfxGetApp();
		//获取主窗口
		CSocketTestDlg *pDlg =(CSocketTestDlg *)pApp->m_pMainWnd;

		//在主窗口输出区显示结果
		CString strTextOut;
		strTextOut.Format(_T("already connect to "));
		strTextOut +=pDlg->m_Address;
		strTextOut += _T("    端口号:");
		CString str;
		str.Format(_T("%d"),pDlg->m_Port);
		strTextOut +=str;

		pDlg->m_MsgR.InsertString(0,strTextOut);

		//激活一个网络读取事件,准备接收
		AsyncSelect(FD_READ);
		
	}
	CAsyncSocket::OnConnect(nErrorCode);
}


void ClientSocket::OnReceive(int nErrorCode)
{
	// TODO: Add your specialized code here and/or call the base class
	//获取socket数据
	m_nLength =Receive(m_szBuffer,sizeof(m_szBuffer));
	//获取主程序句柄
	CSocketTestApp *pApp =(CSocketTestApp *)AfxGetApp();
	//获取主窗口
	CSocketTestDlg *pDlg =(CSocketTestDlg *)pApp->m_pMainWnd;

	CString strTextOut(m_szBuffer);
	//在主窗口的显示区显示接收到的socket数据
	pDlg->m_MsgR.InsertString(0,strTextOut);

	memset(m_szBuffer,0,sizeof(m_szBuffer));
	CAsyncSocket::OnReceive(nErrorCode);
}


void ClientSocket::OnSend(int nErrorCode)
{
	// TODO: Add your specialized code here and/or call the base class
	//发送数据
	Send(m_szBuffer,m_nLength,0);
	m_nLength =0;

	memset(m_szBuffer,0,sizeof(m_szBuffer));

	//继续提请一个读的网络事件,接收socket消息
	AsyncSelect(FD_READ);
	CAsyncSocket::OnSend(nErrorCode);
}

接下来就是对话框代码了:

SocketTestDlg.h:

// SocketTestDlg.h : header file
//

#pragma once
#include "afxwin.h"
#include "ClientSocket.h"


// CSocketTestDlg dialog
class CSocketTestDlg : public CDialogEx
{
// Construction
public:
	CSocketTestDlg(CWnd* pParent = NULL);	// standard constructor

// Dialog Data
	enum { IDD = IDD_SOCKETTEST_DIALOG };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support


// Implementation
protected:
	HICON m_hIcon;

	// Generated message map functions
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
public:
	// 服务器IP地址
	CString m_Address;
	// 服务器端口号
	int m_Port;
	// 消息接收显示控件
	CListBox m_MsgR;
	afx_msg void OnBnClickedBtClear();
	afx_msg void OnBnClickedOk();
	// 用户输入的即将发送的内容
	CString m_MsgS;
	afx_msg void OnBnClickedBtConnect();
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	// 连接服务器次数
	int m_nTryTimes;
	ClientSocket m_ClientSocket;
	afx_msg void OnBnClickedBtSend();
	afx_msg void OnBnClickedBtClose();
};

其实现代码如下:

// SocketTestDlg.cpp : implementation file
//

#include "stdafx.h"
#include "SocketTest.h"
#include "SocketTestDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CAboutDlg dialog used for App About

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// Dialog Data
	enum { IDD = IDD_ABOUTBOX };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CSocketTestDlg dialog




CSocketTestDlg::CSocketTestDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CSocketTestDlg::IDD, pParent)
	, m_Address(_T("127.0.0.1"))
	, m_Port(0)
	, m_MsgS(_T(""))
	, m_nTryTimes(0)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CSocketTestDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_ET_IPADDRESS, m_Address);
	DDX_Text(pDX, IDC_ET_PORT, m_Port);
	DDX_Control(pDX, IDC_LIST_R, m_MsgR);
	DDX_Text(pDX, IDC_ET_SEND, m_MsgS);
}

BEGIN_MESSAGE_MAP(CSocketTestDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BT_CLEAR, &CSocketTestDlg::OnBnClickedBtClear)
	ON_BN_CLICKED(IDOK, &CSocketTestDlg::OnBnClickedOk)
	ON_BN_CLICKED(IDC_BT_CONNECT, &CSocketTestDlg::OnBnClickedBtConnect)
	ON_WM_TIMER()
	ON_BN_CLICKED(IDC_BT_SEND, &CSocketTestDlg::OnBnClickedBtSend)
	ON_BN_CLICKED(IDC_BT_CLOSE, &CSocketTestDlg::OnBnClickedBtClose)
END_MESSAGE_MAP()


// CSocketTestDlg message handlers

BOOL CSocketTestDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CSocketTestDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CSocketTestDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CSocketTestDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}



void CSocketTestDlg::OnBnClickedBtClear()
{
	// TODO: Add your control notification handler code here
	m_MsgR.ResetContent();
}


void CSocketTestDlg::OnBnClickedOk()
{
	// TODO: Add your control notification handler code here
	/*CDialogEx::OnOK();*/
	UpdateData(TRUE);
	if(m_MsgS.IsEmpty())
	{
		AfxMessageBox(_T("please type the message you want to send!"));
		return;
	}
}


void CSocketTestDlg::OnBnClickedBtConnect()
{
	// TODO: Add your control notification handler code here
	//如果当前已经与服务器建立了连接,则直接返回
	if(m_ClientSocket.m_bConnected)
	{
		AfxMessageBox(_T("当前已经与服务器建立连接"));
		return;
	}
	UpdateData(TRUE);

	if(m_Address.IsEmpty())
	{
		AfxMessageBox(_T("服务器的IP地址不能为空!"));
		return;
	}
	if(m_Port <=1024)
	{
		AfxMessageBox(_T("服务器的端口设置非法!"));
		return;
	}
	//使Connect按键失能
	GetDlgItem(IDC_BT_CONNECT)->EnableWindow(FALSE);
	//启动连接定时器,每1秒中尝试一次连接
	SetTimer(1,1000,NULL);
}


void CSocketTestDlg::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: Add your message handler code here and/or call default
	if(m_ClientSocket.m_hSocket ==INVALID_SOCKET)
	{
		BOOL bFlag =m_ClientSocket.Create(0,SOCK_STREAM,FD_CONNECT); //创建套接字
		if(!bFlag)
		{
			AfxMessageBox(_T("Socket创建失败!"));
			m_ClientSocket.Close();
			PostQuitMessage(0);//退出
			return;
		}
	}

	m_ClientSocket.Connect(m_Address,m_Port);	//连接服务器
	

	if(m_nTryTimes >=10)
	{
		KillTimer(1);
		AfxMessageBox(_T("连接失败!"));
		GetDlgItem(IDC_BT_CONNECT)->EnableWindow(TRUE);
		return;
	}
	else if(m_ClientSocket.m_bConnected)
	{
		KillTimer(1);
		GetDlgItem(IDC_BT_CONNECT)->EnableWindow(TRUE);
		return;
	}
	CString strTextOut =_T("尝试连接服务器第");

	m_nTryTimes ++;
	CString str;
	str.Format(_T("%d"),m_nTryTimes);
	strTextOut +=str;
	strTextOut +=_T("次...");
	m_MsgR.InsertString(0,strTextOut);
	CDialogEx::OnTimer(nIDEvent);
}


void CSocketTestDlg::OnBnClickedBtSend()
{
	// TODO: Add your control notification handler code here
	UpdateData(TRUE);

	if(m_ClientSocket.m_bConnected)
	{
		//将用户输入的数据从CString转化为char *字符串,即Unicode-->ANSI,并保存到m_ClientSocket.m_szBuffer中
		int len =WideCharToMultiByte(CP_ACP,0,m_MsgS,-1,NULL,0,NULL,NULL);
		WideCharToMultiByte(CP_ACP,0,m_MsgS,-1,m_ClientSocket.m_szBuffer,len,NULL,NULL );
		m_ClientSocket.m_nLength =strlen(m_ClientSocket.m_szBuffer);


		m_ClientSocket.AsyncSelect(FD_WRITE);//触发写事件
		m_MsgS =_T("");
		UpdateData(FALSE);
	}
}




void CSocketTestDlg::OnBnClickedBtClose()
{
	// TODO: Add your control notification handler code here
	m_ClientSocket.ShutDown();
	EndDialog(0);
}

OK,完!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值