cocos2d-x 3.7 C++ 使用socket连接客户端与服务器(JAVA做服务端)

做网络游戏的时候,我们肯定不能使用http进行数据的交换,下面纪录下cocos2d-x使用socket的方法


客户端用的是ODSocket,服务端研究半天,最后还是发现java做socket服务端比C++要方便太多。


下面是cocos2d客户端文件目录结构



下面是java服务端文件目录结构



接下来直接上代码,代码中有详细的注释

首先是客户端

HelloWorldScene.h

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"
#include "ODSocket/ODSocket.h"

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();
    virtual bool init();
    CREATE_FUNC(HelloWorld);
    void connectServer();
    void receiveData();
    
private:
    ODSocket socket;
};

#endif // __HELLOWORLD_SCENE_H__

HelloWorldScene.cpp

#include "HelloWorldScene.h"


USING_NS_CC;

Scene* HelloWorld::createScene()
{
    auto scene = Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
//    cocos2d::Size visibleSize = Director::getInstance()->getVisibleSize();
//    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    this->connectServer();
    
    return true;
}

// Socker连接
void HelloWorld::connectServer()
{
    // 初始化
    socket.Init();
    socket.Create(AF_INET, SOCK_STREAM, 0);
    
    // 设置服务器的IP地址,端口号
    // 并连接服务器 Connect
    const char* ip = "127.0.0.1";
    int port = 12345;
    bool result = socket.Connect(ip, port);
    
    // 发送数据 Send
    std::string data = "我连接上了";
    socket.Send(Value(data).asString().c_str(), (int)strlen(Value(data).asString().c_str()));
    
    if (result) {
        CCLOG("连接服务器成功!");
        // 开启新线程,在子线程中,接收数据
        std::thread recvThread = std::thread(&HelloWorld::receiveData, this);
        recvThread.detach(); // 从主线程分离
    }
    else {
        CCLOG("无法连接服务器!");
        return;
    }
}

// 接收数据
void HelloWorld::receiveData()
{
    // 因为是强联网
    // 所以可以一直检测服务端是否有数据传来
    while (true) {
        // 接收数据 Recv
        char data[512] = "";
        int result = socket.Recv(data, 512, 0);
        // 与服务器的连接断开了
        if (result <= 0) {
            CCLOG("服务器已经断开连接!");
            break;
        }
        
        CCLOG("%s", data);
        
        std::string str = "我收到了你的消息" + Value(data).asString();
        socket.Send(Value(str).asString().c_str(), (int)strlen(Value(str).asString().c_str()));
    }
    // 关闭连接
    socket.Close();
}


接下来是服务端

Main.java

package com.ron.socket;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Main {

	public static void main(String[] args) {
		// 创建ServerSocket,监听端口号:12345
        ServerSocket ss;
		try {
			ss = new ServerSocket(12345);
			
			// 创建用于管理客户端的收发数据的子线程类
	        ClientThread clientThread = new ClientThread();
	        clientThread.start();
	         
	        System.out.println("服务器开启啦");
	 
	        // 监听端口号:12345
	        // 等待客户连接 accept()
	        while (true) {
	            // 开始接收客户端的连接
	            Socket socket = ss.accept();
	            System.out.println("有新客户连接~");
	            clientThread.addClient(socket);
	        }
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

ClientThread.java

package com.ron.socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;

//继承Thread线程类
public class ClientThread extends Thread {
	 // 客户端连接的socket列表
	 private ArrayList<Socket> clients = new ArrayList<Socket>();
	
	 // 添加客户
	 public void addClient(Socket socket) {
	     clients.add(socket);
	 }
	 // 删除客户
	 public void removeClient(Socket socket) {
	     clients.remove(socket);
	 }
	 // 向客户发送数据
	 public void sendMessage(Socket socket, String data) throws IOException {
	     // 给玩家发送数据
	     OutputStream os = socket.getOutputStream();
	     os.write(data.getBytes("UTF-8"));
	 }
	
	 @Override
	 public void run() {
	     while (true) {
	         try {
	             for (Socket socket : clients) {
	                 // 获取客户端发来的数据
	                 InputStream is = socket.getInputStream();
	                 int len = is.available() + 1;
	                 byte[] buff = new byte[len];
	                 int flag = is.read(buff);
	
	                 // read()返回-1,说明客户端的socket已断开
	                 if (flag == -1) {
	                     System.out.println("客户已经断开连接~");
	                     this.removeClient(socket);
	                     break;
	                 }
	
	                 // 输出接收到的数据
	                 String read = new String(buff);
	                 System.out.println("收到数据:" + read);
	
	                 // 给玩家发送数据
	                 String data = "恭喜你,连接成功啦~~";
	                 sendMessage(socket, data);
	             }
	             sleep(10);
	         } catch (IOException e) {
	             e.printStackTrace();
	         } catch (InterruptedException e) {
	             e.printStackTrace();
	         }
	     }
	 }
}


首先运行服务端,然后运行客户端。服务端需要Run As Java Application。运行成功后,会看到服务端和客户端在不断的通信



接下来是ODSocket的代码,http://www.codeforge.cn/read/214931/ODSocket.h__html这里有下载,不过下面我也贴上来好了。


ODSocket.cpp

#include "ODSocket.h"


#ifdef WIN32
    #include "StdAfx.h"
	#pragma comment(lib, "wsock32")
#endif


ODSocket::ODSocket(SOCKET sock)
{
	m_sock = sock;
}

ODSocket::~ODSocket()
{
}

int ODSocket::Init()
{
#ifdef WIN32
	/*
	http://msdn.microsoft.com/zh-cn/vstudio/ms741563(en-us,VS.85).aspx

	typedef struct WSAData { 
		WORD wVersion;								//winsock version
		WORD wHighVersion;							//The highest version of the Windows Sockets specification that the Ws2_32.dll can support
		char szDescription[WSADESCRIPTION_LEN+1]; 
		char szSystemStatus[WSASYSSTATUS_LEN+1]; 
		unsigned short iMaxSockets; 
		unsigned short iMaxUdpDg; 
		char FAR * lpVendorInfo; 
	}WSADATA, *LPWSADATA; 
	*/
	WSADATA wsaData;
	//#define MAKEWORD(a,b) ((WORD) (((BYTE) (a)) | ((WORD) ((BYTE) (b))) << 8)) 
	WORD version = MAKEWORD(2, 0);
	int ret = WSAStartup(version, &wsaData);//win sock start up
	if ( ret ) {
//		cerr << "Initilize winsock error !" << endl;
		return -1;
	}
#endif
	
	return 0;
}
//this is just for windows
int ODSocket::Clean()
{
#ifdef WIN32
		return (WSACleanup());
#endif
		return 0;
}

ODSocket& ODSocket::operator = (SOCKET s)
{
	m_sock = s;
	return (*this);
}

ODSocket::operator SOCKET ()
{
	return m_sock;
}
//create a socket object win/lin is the same
// af:
bool ODSocket::Create(int af, int type, int protocol)
{
	m_sock = socket(af, type, protocol);
	if ( m_sock == INVALID_SOCKET ) {
		return false;
	}
	return true;
}

bool ODSocket::Connect(const char* ip, unsigned short port)
{
	struct sockaddr_in svraddr;
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = inet_addr(ip);
	svraddr.sin_port = htons(port);
	int ret = connect(m_sock, (struct sockaddr*)&svraddr, sizeof(svraddr));
	if ( ret == SOCKET_ERROR ) {
		return false;
	}
	return true;
}

bool ODSocket::Bind(unsigned short port)
{
	struct sockaddr_in svraddr;
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = INADDR_ANY;
	svraddr.sin_port = htons(port);

	int opt =  1;
	if ( setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0 ) 
		return false;

	int ret = bind(m_sock, (struct sockaddr*)&svraddr, sizeof(svraddr));
	if ( ret == SOCKET_ERROR ) {
		return false;
	}
	return true;
}
//for server
bool ODSocket::Listen(int backlog)
{
	int ret = listen(m_sock, backlog);
	if ( ret == SOCKET_ERROR ) {
		return false;
	}
	return true;
}

bool ODSocket::Accept(ODSocket& s, char* fromip)
{
	struct sockaddr_in cliaddr;
	socklen_t addrlen = sizeof(cliaddr);
	SOCKET sock = accept(m_sock, (struct sockaddr*)&cliaddr, &addrlen);
	if ( sock == SOCKET_ERROR ) {
		return false;
	}

	s = sock;
	if ( fromip != NULL )
		sprintf(fromip, "%s", inet_ntoa(cliaddr.sin_addr));

	return true;
}

int ODSocket::Send(const char* buf, int len, int flags)
{
	int bytes;
	int count = 0;

	while ( count < len ) {

		bytes = send(m_sock, buf + count, len - count, flags);
		if ( bytes == -1 || bytes == 0 )
			return -1;
		count += bytes;
	} 

	return count;
}

int ODSocket::Recv(char* buf, int len, int flags)
{
	return (recv(m_sock, buf, len, flags));
}

int ODSocket::Close()
{
#ifdef WIN32
	return (closesocket(m_sock));
#else
	return (close(m_sock));
#endif
}

int ODSocket::GetError()
{
#ifdef WIN32
	return (WSAGetLastError());
#else
	return (0);
#endif
}

bool ODSocket::DnsParse(const char* domain, char* ip)
{
	struct hostent* p;
	if ( (p = gethostbyname(domain)) == NULL )
		return false;
		
	sprintf(ip, 
		"%u.%u.%u.%u",
		(unsigned char)p->h_addr_list[0][0], 
		(unsigned char)p->h_addr_list[0][1], 
		(unsigned char)p->h_addr_list[0][2], 
		(unsigned char)p->h_addr_list[0][3]);
	
	return true;
}

ODSocket.h

/*
 * define file about portable socket class. 
 * description:this sock is suit both windows and linux
 * design:odison
 * e-mail:[email protected]>
 * 
 */

#ifndef _ODSOCKET_H_
#define _ODSOCKET_H_

#ifdef WIN32
	#include <winsock.h>
	typedef int				socklen_t;
#else
    #include "cocos2d.h"
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <netdb.h>
	#include <fcntl.h>
	#include <unistd.h>
	#include <sys/stat.h>
	#include <sys/types.h>
	#include <arpa/inet.h>
	typedef int				SOCKET;

	//#pragma region define win32 const variable in linux
	#define INVALID_SOCKET	-1
	#define SOCKET_ERROR	-1
	//#pragma endregion
#endif


class ODSocket {

public:
	ODSocket(SOCKET sock = INVALID_SOCKET);
	~ODSocket();

	// Create socket object for snd/recv data
	bool Create(int af, int type, int protocol = 0);

	// Connect socket
	bool Connect(const char* ip, unsigned short port);
	//#region server
	// Bind socket
	bool Bind(unsigned short port);

	// Listen socket
	bool Listen(int backlog = 5); 

	// Accept socket
	bool Accept(ODSocket& s, char* fromip = NULL);
	//#endregion
	
	// Send socket
	int Send(const char* buf, int len, int flags = 0);

	// Recv socket
	int Recv(char* buf, int len, int flags = 0);
	
	// Close socket
	int Close();

	// Get errno
	int GetError();
	
	//#pragma region just for win32
	// Init winsock DLL 
	static int Init();	
	// Clean winsock DLL
	static int Clean();
	//#pragma endregion

	// Domain parse
	static bool DnsParse(const char* domain, char* ip);

	ODSocket& operator = (SOCKET s);

	operator SOCKET ();

protected:
	SOCKET m_sock;

};

#endif

stdafx.cpp

// stdafx.cpp : source file that includes just the standard includes
// ChatClient.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

stdafx.h

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//

#pragma once

#include "targetver.h"

#include <stdio.h>
//#include <tchar.h>



// TODO: reference additional headers your program requires here

targetver.h

#pragma once

// The following macros define the minimum required platform.  The minimum required platform
// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run 
// your application.  The macros work by enabling all features available on platform versions up to and 
// including the version specified.

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef _WIN32_WINNT            // Specifies that the minimum required platform is Windows Vista.
#define _WIN32_WINNT 0x0600     // Change this to the appropriate value to target other versions of Windows.
#endif

tchar.h

/***
*tchar.h - definitions for generic international text functions
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       Definitions for generic international functions, mostly defines
*       which map string/formatted-io/ctype functions to char, wchar_t, or
*       MBCS versions.  To be used for compatibility between single-byte,
*       multi-byte and Unicode text models.
*
*       [Public]
*
****/

#pragma once

#include <crtdefs.h>

#ifndef _INC_TCHAR
#define _INC_TCHAR

#ifdef _STRSAFE_H_INCLUDED_
#error Need to include strsafe.h after tchar.h
#endif

#pragma warning(disable:4514)       /* disable unwanted C++ /W4 warning */
/* #pragma warning(default:4514) */ /* use this to reenable, if necessary */

/* Notes */

/* There is no:
 *      _tcscat_l
 *      _tcscpy_l
 * because mbscat and mbscpy just behave like strcat and strcpy,
 * so no special locale-specific behavior is needed.
 */

/* Functions like:
 *      _strncat_l
 *      _strncat_s_l
 * are only available if ANSI is defined (i.e. no _UNICODE nor _MBCS),
 * because these functions are only accessible through the _tcs macros.
 */

#ifdef  __cplusplus
extern "C" {
#endif

#ifndef _CRT_FAR_MAPPINGS_NO_DEPRECATE
/*
Long ago, these f prefix text functions referred to handling of text in segmented architectures. Ever since the move
to Win32 they have been obsolete names, but we kept them around as aliases. Now that we have a deprecation 
mechanism we can warn about them. You should switch to the identical function without the f prefix.
*/
#pragma deprecated("_ftcscat")
#pragma deprecated("_ftcschr")
#pragma deprecated("_ftcscpy")
#pragma deprecated("_ftcscspn")
#pragma deprecated("_ftcslen")
#pragma deprecated("_ftcsncat")
#pragma deprecated("_ftcsncpy")
#pragma deprecated("_ftcspbrk")
#pragma deprecated("_ftcsrchr")
#pragma deprecated("_ftcsspn")
#pragma deprecated("_ftcsstr")
#pragma deprecated("_ftcstok")
#pragma deprecated("_ftcsdup")
#pragma deprecated("_ftcsnset")
#pragma deprecated("_ftcsrev")
#pragma deprecated("_ftcsset")
#pragma deprecated("_ftcscmp")
#pragma deprecated("_ftcsicmp")
#pragma deprecated("_ftcsnccmp")
#pragma deprecated("_ftcsncmp")
#pragma deprecated("_ftcsncicmp")
#pragma deprecated("_ftcsnicmp")
#pragma deprecated("_ftcscoll")
#pragma deprecated("_ftcsicoll")
#pragma deprecated("_ftcsnccoll")
#pragma deprecated("_ftcsncoll")
#pragma deprecated("_ftcsncicoll")
#pragma deprecated("_ftcsnicoll")
#pragma deprecated("_ftcsclen")
#pragma deprecated("_ftcsnccat")
#pragma deprecated("_ftcsnccpy")
#pragma deprecated("_ftcsncset")
#pragma deprecated("_ftcsdec")
#pragma deprecated("_ftcsinc")
#pragma deprecated("_ftcsnbcnt")
#pragma deprecated("_ftcsnccnt")
#pragma deprecated("_ftcsnextc")
#pragma deprecated("_ftcsninc")
#pragma deprecated("_ftcsspnp")
#pragma deprecated("_ftcslwr")
#pragma deprecated("_ftcsupr")
#pragma deprecated("_ftclen")
#pragma deprecated("_ftccpy")
#pragma deprecated("_ftccmp")
#endif /* ndef _CRT_FAR_MAPPINGS_NO_DEPRECATE */

#define _ftcscat    _tcscat
#define _ftcschr    _tcschr
#define _ftcscpy    _tcscpy
#define _ftcscspn   _tcscspn
#define _ftcslen    _tcslen
#define _ftcsncat   _tcsncat
#define _ftcsncpy   _tcsncpy
#define _ftcspbrk   _tcspbrk
#define _ftcsrchr   _tcsrchr
#define _ftcsspn    _tcsspn
#define _ftcsstr    _tcsstr
#define _ftcstok    _tcstok

#define _ftcsdup    _tcsdup
#define _ftcsnset   _tcsnset
#define _ftcsrev    _tcsrev
#define _ftcsset    _tcsset

#define _ftcscmp      _tcscmp
#define _ftcsicmp     _tcsicmp
#define _ftcsnccmp    _tcsnccmp
#define _ftcsncmp     _tcsncmp
#define _ftcsncicmp   _tcsncicmp
#define _ftcsnicmp    _tcsnicmp

#define _ftcscoll     _tcscoll
#define _ftcsicoll    _tcsicoll
#define _ftcsnccoll   _tcsnccoll
#define _ftcsncoll    _tcsncoll
#define _ftcsncicoll  _tcsncicoll
#define _ftcsnicoll   _tcsnicoll

/* Redundant "logical-character" mappings */

#define _ftcsclen   _tcsclen
#define _ftcsnccat  _tcsnccat
#define _ftcsnccpy  _tcsnccpy
#define _ftcsncset  _tcsncset

#define _ftcsdec    _tcsdec
#define _ftcsinc    _tcsinc
#define _ftcsnbcnt  _tcsnbcnt
#define _ftcsnccnt  _tcsnccnt
#define _ftcsnextc  _tcsnextc
#define _ftcsninc   _tcsninc
#define _ftcsspnp   _tcsspnp

#define _ftcslwr    _tcslwr
#define _ftcsupr    _tcsupr

#define _ftclen     _tclen
#define _ftccpy     _tccpy
#define _ftccmp     _tccmp

#ifndef _CONST_RETURN
#ifdef  __cplusplus
#define _CONST_RETURN  const
#define _CRT_CONST_CORRECT_OVERLOADS
#else
#define _CONST_RETURN
#endif
#endif

/* For backwards compatibility */
#define _WConst_return _CONST_RETURN

#ifdef  _UNICODE

#ifdef  __cplusplus
}   /* ... extern "C" */
#endif

/* ++++++++++++++++++++ UNICODE ++++++++++++++++++++ */

#include <wchar.h>

#ifdef  __cplusplus
extern "C" {
#endif

#ifndef _WCTYPE_T_DEFINED
typedef unsigned short wint_t;
typedef unsigned short wctype_t;
#define _WCTYPE_T_DEFINED
#endif

#ifndef __TCHAR_DEFINED
typedef wchar_t     _TCHAR;
typedef wchar_t     _TSCHAR;
typedef wchar_t     _TUCHAR;
typedef wchar_t     _TXCHAR;
typedef wint_t      _TINT;
#define __TCHAR_DEFINED
#endif

#ifndef _TCHAR_DEFINED
#if     !__STDC__
typedef wchar_t     TCHAR;
#endif
#define _TCHAR_DEFINED
#endif

#define _TEOF       WEOF

#define __T(x)      L ## x


/* Program */

#define _tmain      wmain
#define _tWinMain   wWinMain
#define _tenviron   _wenviron
#define __targv     __wargv

/* Formatted i/o */

#define _tprintf        wprintf
#define _tprintf_l      _wprintf_l
#define _tprintf_s      wprintf_s
#define _tprintf_s_l    _wprintf_s_l
#define _tprintf_p      _wprintf_p
#define _tprintf_p_l    _wprintf_p_l
#define _tcprintf       _cwprintf
#define _tcprintf_l     _cwprintf_l
#define _tcprintf_s     _cwprintf_s
#define _tcprintf_s_l   _cwprintf_s_l
#define _tcprintf_p     _cwprintf_p
#define _tcprintf_p_l   _cwprintf_p_l
#define _vtcprintf      _vcwprintf
#define _vtcprintf_l    _vcwprintf_l
#define _vtcprintf_s    _vcwprintf_s
#define _vtcprintf_s_l  _vcwprintf_s_l
#define _vtcprintf_p    _vcwprintf_p
#define _vtcprintf_p_l  _vcwprintf_p_l
#define _ftprintf       fwprintf
#define _ftprintf_l     _fwprintf_l
#define _ftprintf_s     fwprintf_s
#define _ftprintf_s_l   _fwprintf_s_l
#define _ftprintf_p     _fwprintf_p
#define _ftprintf_p_l   _fwprintf_p_l
#define _stprintf       _swprintf
#define _stprintf_l     __swprintf_l
#define _stprintf_s     swprintf_s
#define _stprintf_s_l   _swprintf_s_l
#define _stprintf_p     _swprintf_p
#define _stprintf_p_l   _swprintf_p_l
#define _sctprintf      _scwprintf
#define _sctprintf_l    _scwprintf_l
#define _sctprintf_p    _scwprintf_p
#define _sctprintf_p_l  _scwprintf_p_l
#define _sntprintf      _snwprintf
#define _sntprintf_l    _snwprintf_l
#define _sntprintf_s    _snwprintf_s
#define _sntprintf_s_l  _snwprintf_s_l
#define _vtprintf       vwprintf
#define _vtprintf_l     _vwprintf_l
#define _vtprintf_s     vwprintf_s
#define _vtprintf_s_l   _vwprintf_s_l
#define _vtprintf_p     _vwprintf_p
#define _vtprintf_p_l   _vwprintf_p_l
#define _vftprintf      vfwprintf
#define _vftprintf_l    _vfwprintf_l
#define _vftprintf_s    vfwprintf_s
#define _vftprintf_s_l  _vfwprintf_s_l
#define _vftprintf_p    _vfwprintf_p
#define _vftprintf_p_l  _vfwprintf_p_l
#define _vstprintf      vswprintf
#define _vstprintf_l    _vswprintf_l
#define _vstprintf_s    vswprintf_s
#define _vstprintf_s_l  _vswprintf_s_l
#define _vstprintf_p    _vswprintf_p
#define _vstprintf_p_l  _vswprintf_p_l
#define _vsctprintf     _vscwprintf
#define _vsctprintf_l   _vscwprintf_l
#define _vsctprintf_p   _vscwprintf_p
#define _vsctprintf_p_l _vscwprintf_p_l
#define _vsntprintf     _vsnwprintf
#define _vsntprintf_l   _vsnwprintf_l
#define _vsntprintf_s   _vsnwprintf_s
#define _vsntprintf_s_l _vsnwprintf_s_l

#define _tscanf         wscanf
#define _tscanf_l       _wscanf_l
#define _tscanf_s       wscanf_s
#define _tscanf_s_l     _wscanf_s_l
#define _tcscanf        _cwscanf
#define _tcscanf_l      _cwsca
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值