在cocos2dx中要实现网络实时交互,需要使用socket保持连接,并与服务器进行数据交互
socket通信部分需要使用多线程
1、开启一个线程,进行socket通信的连接
int ClientSocket::Start()
{
int errcode = 0;
do
{
pthread_attr_t l_attr;
errcode = pthread_attr_init(&l_attr);
CC_BREAK_IF(errcode != 0);
errcode = pthread_attr_setdetachstate(&l_attr, PTHREAD_CREATE_DETACHED); //PTHREAD_CREATE_DETACHED=1
if (errcode != 0)
{
pthread_attr_destroy(&l_attr);
break;
}
errcode = pthread_create(&_SocketThread, &l_attr,ThreadRun,this);
pthread_attr_destroy(&l_attr);
}
while (0);
return errcode;
}
2、连接socket
void* ThreadRun(void* data)
{
class NetOpr
{
public:
static void InitSockAddr(sockaddr_in& sockAddr, const char* ip, int port)
{
memset(&sockAddr,0,sizeof(sockAddr));
//判断是否合法IP
if(INADDR_NONE != inet_addr(ip))
sockAddr.sin_addr.s_addr = inet_addr(ip);
else
{
struct hostent *hptr=NULL;
char **pptr=NULL;
if((hptr = gethostbyname(ip)) == NULL)
{
CCLOG("InitSockAddr Error: gethostbyname error for host:%s", ip);
return;
}
else
{
CCLOG("InitSockAddr official hostname:%s\n",hptr->h_name);
for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
CCLOG("InitSockAddr h_aliases:%s",*pptr);
switch(hptr->h_addrtype)
{
case AF_INET:
CCLOG("init ipv4");
case AF_INET6:
for(pptr = hptr->h_addr_list; *pptr != NULL; pptr++)
CCLOG("InitSockAddr h_addr_list:%s",*pptr);
memcpy(&(sockAddr.sin_addr.s_addr), hptr->h_addr, hptr->h_length);
break;
default:
CCLOG("InitSockAddr Error: unknown address type");
break;
}
}
}
sockAddr.sin_port = htons(port);
sockAddr.sin_family = AF_INET;
}
//判断是否IPv6网络
static bool isIPV6Net(const std::string domainStr = "www.baidu.com")
{
bool isIPV6Net = false;
struct addrinfo *result = NULL,*curr;
struct sockaddr_in6 dest;
memset(&dest, 0, sizeof(dest));
dest.sin6_family = AF_INET6;
int ret = getaddrinfo(domainStr.c_str(),NULL,NULL,&result);
if (ret == 0)
{
for (curr = result; curr != NULL; curr = curr->ai_next)
{
switch (curr->ai_family)
{
case AF_INET6:
{
isIPV6Net = true;
break;
}
case AF_INET:
break;
default:
break;
}
}
}
freeaddrinfo(result);
return isIPV6Net;
}
static int Connect(ClientSocket* pClient)
{
int ret;
char ipbuf[128];
struct addrinfo hints,*res, *cur;
memset(&hints,0,sizeof(struct addrinfo));
if(isIPV6Net())
{
hints.ai_family = AF_INET6;
}
else
{
hints.ai_family = AF_INET;
}
//hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_socktype = SOCK_STREAM;
pClient->_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (pClient->_Socket < 0)
{
pClient->SetMsg("pClient->_Socket < 0");
pClient->DoOnSocketError();
return -1;
}
struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
//inet_aton(pClient->_ServerIP.c_str(), &server_addr.sin_addr);
server_addr.sin_addr.s_addr = inet_addr(pClient->_ServerIP.c_str());
server_addr.sin_port = htons(pClient->_ServerPort);
socklen_t server_addr_length = sizeof(server_addr);
int conRet = connect(pClient->_Socket, (struct sockaddr*)&server_addr, server_addr_length);
if(conRet == 0) {
return 0;
}
return -1;
}
static char* GetIpPort(struct sockaddr *sa, char *s, size_t maxlen,int port)
{
switch(sa->sa_family) {
case AF_INET:
((struct sockaddr_in *)sa)->sin_port = htons(port);
inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), s, maxlen);
break;
case AF_INET6:
((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),s, maxlen);
break;
default:
strncpy(s, "Unknown AF", maxlen);
return NULL;
}
return s;
}
static bool InitNetwork()
{
#ifdef WIN32
WSADATA wsaData;
WORD version = MAKEWORD(2, 0);
if(WSAStartup(version, &wsaData) != 0)
return false;
#endif
return true;
}
static bool SetSendRecvTimeOut(int socket)
{
#ifdef WIN32
//设置接收和发送超时时间
struct timeval tv;
tv.tv_sec = 6;
tv.tv_usec = 0;
if(setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)) == -1)
return false;
if(setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv)) == -1)
return false;
#endif
return true;
}
};
ClientSocket* pClient = (ClientSocket*)data;
CCLOG("------------>ClientSocket::ConnectServer IP=%s Port=%d", pClient->_ServerIP.c_str(), pClient->_ServerPort);
bool IsFirstRun = true;
fd_set fds;
timeval timeout = {0, 100*1000};
int maxfd;
NetOpr::InitNetwork();
int connRet = NetOpr::Connect(pClient);
if (connRet == -1)
{
CCLOG("------------->ClientSocket::ConnectServer error%d", connRet);
pClient->SetMsg("connRet == -1");
pClient->DoOnConnectFail();
pClient->Close();
return NULL;
}
pClient->_IsConnected = true;
pClient->DoOnConnect();
while (pClient->_IsConnected)
{
FD_ZERO(&fds);
FD_SET(pClient->_Socket, &fds);
maxfd = pClient->_Socket + 1;
timeout.tv_sec = 0;
timeout.tv_usec = 100 * 1000;
switch (select(maxfd, &fds, NULL, NULL, &timeout))
{
case -1: //select错误,退出程序
pClient->SetMsg("select(maxfd, &fds, NULL, NULL, &timeout) == -1");
pClient->DoOnSocketError();
pClient->Close();
return NULL;
case 0:
break; //再次轮询
default:
if(pClient->_Socket == -1)
{
pClient->SetMsg("pClient->_Socket == -1");
return NULL;
}
if (FD_ISSET(pClient->_Socket, &fds)) //可读
{
pClient->OnRecvData();
}
break;
}
pClient->DoOnRunExData(); //接受消息之后做出处理
}
return NULL;
}
3、通知主线程之后,主线程负责开启新的线程进行监听recv,监听服务器的返回
void ClientSocket::OnRecvData()
{
SetMsg("OnRecvDataIn");
int len = recv(_Socket, _RecvBuf + _RecvLen, sizeof(_RecvBuf) - _RecvLen, 0);
if (len == 0)
{
SetMsg("OnRecvDataLen == 0");
DoOnSocketError();
Close();
}
else if (len < 0)
{
const int ERR_NO = errno;
if (ERR_NO != EINTR && ERR_NO != EAGAIN && ERR_NO != ETIMEDOUT)
{
SetMsg("OnRecvDataLen < 0");
DoOnSocketError();
Close();
}
}
else
{
_RecvLen += len;
int pos = 0;
unsigned short dataLen = 0;
unsigned short cmd = 0;
while (_RecvLen >= 4)
{
memcpy(&dataLen, _RecvBuf + pos, sizeof(dataLen)); //包体长度
memcpy(&cmd, _RecvBuf + pos + 2, sizeof(cmd)); //cmd
if (_RecvLen < dataLen + 4) //发现粘包
break;
DoOnData(_RecvBuf + pos, dataLen + 4);
pos += dataLen + 4;
_RecvLen -= dataLen + 4;
}
if (pos>0 && _RecvLen > 0)
{
memmove(_RecvBuf, _RecvBuf + pos, _RecvLen);
}
}
SetMsg("OnRecvDataOut");
}
void GameScene::OnRunExData(ClientSocket* s)
{
ExDataNum++;
for(;;)
{
ark_NetMsgPtr pMsg = NetMsgMgr::getInstance()->extractSendMsg();
if(!pMsg)
break;
unsigned char* p = (unsigned char*) pMsg->getBuffer();
int size = s->Send((const char*)p, pMsg->getSize());
if(size != (int)pMsg->getSize())
CCLOG("ClientSocket::send Error sentSize=%d, msgSize=%d", size, pMsg->getSize());
delete pMsg;
}
}