额,我发现我竟然1年多没上CSDN写东西了,而且关于cocos2d-x上得通用socket通信的文章竟然是阅读量最大的。确实没想到,而且当时那篇文章只是记录了一个想法。那时候对网络编程不是很熟悉,并且对UI的多线程处理也没什么经验,所以没有具体的实现,看到评论里面大家都求代码,应该这方面需求量还是挺大的。而且我当时记录文章的时候,还在用cocos2d-x 2.1.3,里面比较成熟的应该是CCHttpClient吧,用的时curl库(其实curl库中是能得到socket对象的)实现了HTTP请求的发送和接收,BSD的socket实现还没有例子。然后当时记得是用了一个ODSocket的简单实现,郁闷的是当时搜索相关的文章讲在cocos2d-x下实现socket通信的还真不多,当时还觉得好怪异,为啥没这方面的文章,现在回过头来想想,实在是这个问题提得不是很合理,以为BSD的socket其实在Win32、iOS、Android的NDK上都有标准支持,一般写过对应客户端的网络通信的都会写,所以,我把它归类为大家不屑于写这方面的代码。
文章比较多的还是提供思路上的的引导,而非代码上的,所以要代码的同学可能是要失望了,不过如果能看完这篇文章,自己应该能大致有个概念,因为写代码还是需要查阅不少资料的呀,比如stl库的实用,讲开去实在不是我能力范围,憋了快两年才来写这个文章续篇的人的水平也就这样了。
还有就是,写教程的难度太大,好的教程,作者还是要花费很多的精力来做到语言和表意上的准确,还有好多的校对工作,最重要的还不能误人子弟,压力太大。丑话说在前头,我这个不是教程,仅仅是自己总结的一些经验,只能保证说,我不是有意来“误人子弟”的,大家借鉴借鉴即可,别认真到直接拿去商用啥的,责任太大,扛不起啊。不过我还是想帮助对这方面知识有困扰的朋友,毕竟想想当年遇到这些问题时各种搜索无门的郁闷也是醉了。
#ifdef WIN32
#include
typedef int socklen_t;
#else
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef int SOCKET;
//#pragma region define win32 const variable in linux
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
//#pragma endregion
#endif
class GameSocket {
public:
GameSocket(SOCKET sock = INVALID_SOCKET);
~GameSocket();
// 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(GameSocket& 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);
int SetNonblock();
GameSocket& operator = (SOCKET s);
void SetConnected(bool bConned);
// Statusbool IsInited();
bool IsConnected();bool IsCreated();
public:// AttributesSOCKET m_sock;protected:// Attributesstatic bool m_bInited;bool m_bCreated;
bool m_bConnected;};
看下头文件,大致能了解这个做了啥,基本的就是把socket对应的API分平台的封装了一系列接口。
m_bInited这个是静态变量,其实只是针对Win32平台设置的,其余两个平台都是直接赋值为true,而Win32平台因为需要初始化socket环境。只要初始化成功后就不需要再初始化了。
注意,Connect函数的第一个参数需要ip地址,域名是不行的,但是有提供DnsParse静态函数,把域名转换为ip地址,这就搞定了。还有个设置socket为非阻塞模式的成员函数SetNonblock()。(啊,这个我要坦白,此模式不甚清楚,可能是设置当前套接字在发送数据时,直接返回啥的)
#include "GameSocket.h"
#ifdef WIN32
#pragma comment(lib, "Ws2_32")
#endif
// 初始化标志 WIN32 只需要初始化一次
bool GameSocket::m_bInited = false;
GameSocket::GameSocket(SOCKET sock)
{
m_sock = sock;
// 记录初始化标志
m_bInited = Init() == 0 ? true : false;
m_bCreated = false;
m_bConnected = false;
}
GameSocket::~GameSocket()
{
Close();
Clean();
}
int GameSocket::Init()
{
if (!m_bInited)
{
#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
m_bInited = true;
}
return 0;
}
//this is just for windows
int GameSocket::Clean()
{
m_bInited = false;
#ifdef WIN32
return (WSACleanup());
#endif
return 0;
}
GameSocket& GameSocket::operator = (SOCKET s)
{
m_sock = s;
return (*this);
}
//create a socket object win/lin is the same
// af:
bool GameSocket::Create(int af, int type, int protocol)
{
m_sock = socket(af, type, protocol);
if ( m_sock == INVALID_SOCKET )
{
return false;
}
m_bCreated = true;
return true;
}
bool GameSocket::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 )
{
m_bConnected = false;
return false;
}
m_bConnected = true;
return true;
}
bool GameSocket::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 GameSocket::Listen(int backlog)
{
int ret = listen(m_sock, backlog);
if ( ret == SOCKET_ERROR ) {
return false;
}
return true;
}
bool GameSocket::Accept(GameSocket& 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 GameSocket::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 GameSocket::Recv(char* buf, int len, int flags)
{
if (m_sock ==INVALID_SOCKET) {
return 0;
}
return (recv(m_sock, buf, len, flags));
}
int GameSocket::Close()
{
m_bCreated = false;
m_bConnected = false;
#ifdef WIN32
return (closesocket(m_sock));
#else
{
int nRet = 0;
if (m_sock!=INVALID_SOCKET)
{
nRet = shutdown(m_sock, SHUT_RDWR);
m_sock = INVALID_SOCKET;
}
return nRet;
}
#endif
}
int GameSocket::GetError()
{
#ifdef WIN32
return (WSAGetLastError());
#else
return (errno);
#endif
}
bool GameSocket::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;
}
//将一个socket设置成非阻塞模式
//不论什么平台编写网络程序,都应该使用NONBLOCK socket的方式。这样可以保证你的程序至少不会在recv/send/accept/connect这些操作上发生block从而将整个网络服务都停下来
int GameSocket::SetNonblock()
{
#ifdef WIN32
// setsockopt(m_sock, SO_BROADCAST)
#else
int flags;
// fcntl()用来操作文件描述符的一些特性
if ((flags = fcntl(m_sock, F_GETFL)) == -1)
{
return -1;
}
if (fcntl(m_sock, F_SETFL, flags | O_NONBLOCK) == -1) {
return -1;
}
#endif // WIN32
return 0;
}
void GameSocket::SetConnected(bool bConned)
{
m_bConnected = bConned;
}
bool GameSocket::IsConnected()
{
return m_bConnected;
}
bool GameSocket::IsInited()
{
return m_bInited;
}
bool GameSocket::IsCreated()
{
return m_bCreated;
}
#define BREAK_IF(cond) if (cond) break
/* 初始化socket对象 */
GameSocket sock;
do
{
/* 针对WIN32 */
bool bRet = GameSocket::IsInited();
BREAK_IF(!bRet);
/* 创建socket对象,表示使用TCP/IP协议,传递的是流数据 */
bRet = sock.Create(AF_INET, SOCK_STREAM, IPPROTO_TCP);
BREAK_IF(!bRet);
/* 连接到服务器,假设IP地址是192.1681.1.100,监听端口是9001 */
bRet = sock.Connect("192.168.1.100", 9001);
BREAK_IF(!bRet);
/* 发送数据到服务器 */
const char* pszData = "Yo! Server!";
int nLen = sock.Send(pszData, strlen(pszData), 0);
BREAK_IF(nLen < 0);
/* 接收等待接收服务器的数据 */
char szRecvBuff[1024] = {0};
nLen = sock.Recv(szRecvBuff, sizeof(szRecvBuff));
BREAK_IF(nLen <= 0);
/* 清理工作,在sock对象析构的时候会执行 */
} while(0);
这个明显只是为了使用而使用,如果类似这个代码都运行不过的话,就有三种可能,1. 我伪代码有问题,2. 你代码写错了,3.拿伪代码去编译了。对于最后一种我无力吐槽,前两种的话,您再推敲推敲。