外部网络工作模块
封装了select模型,最大支持3个socket,
分发消息类型
/*
用户调用API
*/
#ifndef __MY_NETWORK_H__
#define __MY_NETWORK_H__
#include "CMyNetModual.h"
//支持最大socket个数
#define MAX_NET 3
//支持4K数据一起发送
/*POST数据时要填充的结构体*/
struct NET_DATA_STRUCT
{
char *data;
int len;
};
class CMyNetWork{
public:
CMyNetWork();
~CMyNetWork();
//循环检测socket事件
void run(void);
//创建 返回索引
//mode是1(tcp) or 0(udps)
//生成cmynetMoudel socket对象,调用连接,
int CreateNet(char *Address, int port, int mode, NET_CALLBACK callback, char* postContent = NULL, int postlen = 0);
//删除index对应的socket
int DeleteNet(int index);
//http: 判断接收成功与否 200 206返回1 其它返回 0
//socket: 返回1 不用调用
int isSuccess(int index);
int isInDownload( int index );
void NetCallback( CMyNetModual* net,int event, int index, void* buf, int len );
//返回下载百分比(0-100)
int GetDownLoadRate(int index );
//设置超时时间 默认是NETOUTTIMES (60*60)
void SetTimeOut(int time);
//TCP UDP数据发送
int Send(int index, void* data, int len);
//设置回调函数
int SetNetCallBack( int index, void* callback );
public:
CMyNetModual *net_obj[MAX_NET]; //允许同时有3个网络工作
int net_time; //网络超时时间
private:
int Create(char *Address, int port, int mode, NET_CALLBACK callback,char* postContent, int postlen);
};
#endif
http协议报文
#define HTTP_HEAD_STRING "%s %s HTTP/1.1\r\n\
Host: %s \r\n\
Accept: */* \r\n\
Content-Length: %d\r\n\
Connection: keep-alive \r\n\r\n"
#define TEST_HEAD "GET /help HTTP/1.1\r\n\
Host: 10.0.0.247:8000\r\n\
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3\r\n\
Accept-Encoding: gzip, deflate\r\n\
Connection: keep-alive\r\n\
Cache-Control: max-age=0\r\n\r\n"
#define TEST_POST_HEAD "POST /user?token=1380185955 HTTP/1.1\r\nHost: 112.124.39.46\r\nAccept: */*\r\nContent-Length: 8\r\nConnection: keep-alive\r\n\r\n"
CMyNetWork初始化网络模块
<pre name="code" class="cpp">CMyNetWork::CMyNetWork()
{
net_time = NETOUTTIMES;
memset( net_obj, 0, sizeof(CMyNetModual *)*MAX_NET );
#ifdef _WIN32
static int isInitSocketFlag = 0;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested =MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
#endif
}
CMyNetWork::~CMyNetWork()
{
for ( int i = 0; i < MAX_NET; i++ )
{
if ( net_obj[i] )
{
net_obj[i]->Close();
DeleteNet( i );
net_obj[i] = NULL;
}
}
}
//生成cmynetMoudel socket对象,调用socket对象连接
//创建 返回索引
//mode: TCP_MODE, UDP_MODE, HTTP_GET_MODE, HTTP_HOST_MODE
int CMyNetWork::CreateNet(char *Address, int port, int mode, NET_CALLBACK callback, char* postContent, int postlen)
{
//检查参数合法性 callback不能为空 后面不用判断此回调
if ( callback == NULL || Address == NULL || port == 0 || mode == 0 )
{
return -1;
}
int index = Create(Address, port, mode, callback, postContent, postlen );
CCLog( "network Create url = %s\n port = %d ", Address, port );
if ( index == -1 )
{
return -1;
}
//初始连接
//create 套接字
if ( net_obj[index]->Create() != NET_CREATE_ERROR )
{
int nRet = net_obj[index]->Connect();
//如果连接成功在run里面调用回调
if ( nRet != NET_CON_ERROR )
{
net_obj[index]->net_mode = mode;
return index;
}
}
//创建失败则删除
delete net_obj[index];
net_obj[index] = NULL;
return -2;
}
Create 创建cmynetMoudel对象
int CMyNetWork::Create(char *Address, int port, int mode, NET_CALLBACK callback,char* postContent, int postlen )
{
for ( int i = 0; i < MAX_NET; i++ )
{
if ( net_obj[i] == NULL )
{
net_obj[i] = new CMyNetModual(Address, port, mode, 0, callback, postContent, postlen);
if ( net_obj[i] == NULL )
{
return -1;
}
net_obj[i]->timecount = 0;
return i;
}
}
return -1;
}
线程的run函数 1秒1次 用来检测网络数据
客户端第一次可写表示套接字连接成功
服务端第一次可读 表示有客户端请求过来
readfds数组将包括满足以下条件的套接字:
1:有数据可读。此时在此套接字上调用recv,立即收到对方的数据。
2:连接已经关闭、重设或终止。
3:正在请求建立连接的套接字。此时调用accept函数会成功。
writefds数组包含满足下列条件的套接字:
1:有数据可以发出。此时在此套接字上调用send,可以向对方发送数据。
2:调用connect函数,并连接成功的套接字。
exceptfds数组将包括满足下列条件的套接字:
1:调用connection函数,但连接失败的套接字。
2:有带外(out of band)数据可读。
//循环检测socket事件
//加入超时机制(系统超时时间太长了)
//超时是按定时器跑的次数决定
void CMyNetWork::run(void)
{
//读 写 错误事件
fd_set rfd;
fd_set wfd;
fd_set efd;
struct timeval time;
time.tv_sec = 0;
time.tv_usec = 0;
//加入队列的sock数量
int socknum = 0;
//清空集合
FD_ZERO( &rfd );
FD_ZERO( &wfd );
FD_ZERO( &efd );
for ( int i = 0; i < MAX_NET; i++ )
{
if ( net_obj[i] == NULL )
{
continue;
}
//处理超时
net_obj[i]->timecount++;
if ( net_obj[i]->timecount > net_time )
{
/*CCLog( "network timecount------%d",net_obj[i]->timecount);*/
NetCallback( net_obj[i], NET_TIME_OUT, i, NULL, 0 );
}
else
{
int fd = net_obj[i]->net_handle;
FD_SET( fd, &wfd );
FD_SET( fd, &rfd );
FD_SET( fd, &efd );
socknum++;
}
}
//如果没有socket则返回
if ( socknum == 0 )
{
return;
}
int nRet = select(10240, &rfd, &wfd, &efd, &time );
if ( nRet <= 0 )
{
return;
}
//循环检测是否有事件
for ( int i = 0; i < MAX_NET; i++ )
{
if ( net_obj[i] == NULL )
{
continue;
}
int fd = net_obj[i]->net_handle;
//第一次可读表示已连接上服务器
if ( net_obj[i]->net_status == NET_CON_ING )
{
CCLog( "network select NET_CON_ING" );
//可写事件,主要用于http短连,当连接成功后就发送,tcp模式随时调用seng发送数据
if ( FD_ISSET( fd, &wfd ) )
{
net_obj[i]->timecount = 0;
//linux连接错误会产生写事件
if ( net_obj[i]->IsConnectError() == 1 )
{
CCLog( "network IsConnectError() = 1" );
net_obj[i]->net_status = NET_CON_ERROR;
//发送连接出错消息
NetCallback( net_obj[i], net_obj[i]->net_status, i, NULL, 0 );
//如果出错则检测下一个socket
continue;
}
CCLog( "network connect success" );
net_obj[i]->net_status = NET_CON_SUCCESS;
//发送连接成功消息
NetCallback( net_obj[i], net_obj[i]->net_status, i, NULL, 0 );
//如果是HTTP则发送消息
if ( net_obj[i]->net_mode == HTTP_GET_MODE || net_obj[i]->net_mode == HTTP_POST_MODE )
{
char *headstr = (char*)net_obj[i]->net_headbuf;
char hoststr[32] = {0};//主机地址
char getStr[256] = {0};//http协议需要的uri :/loading?id=xxx&name=xx
int len=0;
CCLog( "network http mode" );
net_obj[i]->AnalizeAdress( net_obj[i]->net_url,hoststr );//解析出主机地址
//取GET 后面的一段数据
//strstr() 函数搜索一个字符串在另一个字符串中的第一次出现。找到所搜索的字符串,则该函数返回第一次匹配的字符串的地址;如果未找到所搜索的字符串,则返回NULL。
char *headPtr = strstr( net_obj[i]->net_url, (char*)"http://" );
if ( headPtr )
{
//strchr函数原型:extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置。没有则返回NULL
headPtr = strchr( headPtr + strlen((char*)"http://"), (char)'/' );
}
else
{ //不含http://
headPtr = strchr( net_obj[i]->net_url, (char)'/' );
}
if ( headPtr == NULL )
{
strcpy( getStr, (char*)"/ ");
}
else
{
strcpy( getStr, headPtr );
}
if ( net_obj[i]->net_mode == HTTP_GET_MODE )
{
sprintf( headstr,HTTP_HEAD_STRING, "GET", getStr, hoststr, 0 ); //组合http协议数据
NetCallback( net_obj[i], NET_SEND_HEAD, i, (void*)headstr, strlen(headstr) ); //调用socket通知用户此时已经准备好了要发送http的协议报头
len = strlen( headstr );
}
else if ( net_obj[i]->net_mode == HTTP_POST_MODE )
{
#if 1
NET_DATA_STRUCT data = {0};
//NetCallback( net_obj[i], NET_SEND_POST, i, (void*)&data, 0 );
//if ( data.data == NULL )
//{
// continue;
//}
//请求头
char headerstr[256] = {0};
sprintf( headerstr,HTTP_HEAD_STRING, "POST ", getStr, hoststr, net_obj[i]->m_postlen ); //POST模式需要指定后面跟随数据长度
printf("%s\n",headerstr);
NetCallback( net_obj[i], NET_SEND_HEAD, i, (void*)headerstr, strlen(headerstr) );
len = strlen( headerstr );
//数据超出暂时没有处理
//headstr存储的是post的数据,要向后移
memmove( headstr + len, headstr, net_obj[i]->m_postlen ); //在报头后面追加要发送的数据
memcpy( headstr, headerstr, len );
len += net_obj[i]->m_postlen;
printf("%s\n",headstr);
#else
char headerstr[256] = {0};
memcpy( headerstr, headstr, strlen(headstr) );
memcpy( headstr, TEST_POST_HEAD, strlen(TEST_POST_HEAD) );
memcpy( headstr + strlen(headstr), headerstr, 8 );
len = strlen(headstr);
#endif
}
//测试用
//int nsendlen = net_obj[i]->Send( TEST_HEAD, strlen((char*)TEST_HEAD) );
int nsendlen = net_obj[i]->Send( headstr, len );
CCLog( "network send: nsendlen = %d", nsendlen );
memset( headstr, 0, NET_BUF_LEN );
printf( "send len = %d\r\n",nsendlen );
net_obj[i]->net_status = NET_SEND_HEAD;
}
continue;
}
}
//可读事件
if ( FD_ISSET( fd, &rfd ) )
{ //可读事件:服务器主动发来tcp/udp,或者当前socket状态上次是读状态数据还未读完,或者是http请求发送头之后服务器回复响应头
if ( (net_obj[i]->net_mode == TCP_MODE || net_obj[i]->net_mode == UDP_MODE) || (net_obj[i]->net_status == NET_READ || net_obj[i]->net_status == NET_SEND_HEAD) )
{
char buf[NET_BUF_LEN] = {0};
net_obj[i]->timecount = 0;
CCLog( "network read event" );
while( 1 )
{
memset( buf, 0, sizeof(buf) );
int len = net_obj[i]->Recv( buf, NET_BUF_LEN );//读取缓存大小,返回独到的字节数,把网络数据存如buf
CCLog( "network recv: recvlen = %d", len );
CCLog( "network recv: recv = %s", buf ); //读取数据内容
if ( len == 0)
{
//socket已关闭
net_obj[i]->net_status = NET_CLOSE;
}
else if ( len < 0 )
{
//读取数据错误
//服务器断开网络也会发止消息
net_obj[i]->net_status = NET_READ_ERROR;
}
else
{
net_obj[i]->net_status = NET_READ;
NetCallback( net_obj[i], NET_READ, i, buf, len );
CCLog( "network recv: read callback" );
//此次数据收取完则退出循环 否则继续接收数据
if ( len < NET_BUF_LEN )
{
break;
}
else
{
continue;
}
}
//错误时才会执行下面语句
NetCallback( net_obj[i], net_obj[i]->net_status, i, NULL, 0 );
break;
}
}
}
//socket错误事件
if ( FD_ISSET( fd, &efd ) )
{
net_obj[i]->timecount = 0;
net_obj[i]->net_status = NET_ERROR;
NetCallback( net_obj[i], net_obj[i]->net_status, i, NULL, 0 );
CCLog( "network select error" );
}
}/*for ( int i = 0; i < MAX_NET; i++ )*/
}
数据下载,与分发
//通过这个回调函数分发消息
void CMyNetWork::NetCallback( CMyNetModual *net, int event, int index, void* buf, int len )
{
if ( net == NULL )
{
return ;
}
if ( event == NET_READ )
{
//http接收数据要分析头
if ( net->net_mode == HTTP_GET_MODE || net->net_mode == HTTP_POST_MODE )
{
//没有收完HEAD
if ( net->net_http_status == -1 )
{
int loadlen = strlen(net->net_headbuf);//已下载的长度
char* headflag = strstr( (char*)buf, (char*)"\r\n\r\n" );
if ( headflag )
{
headflag += strlen("\r\n\r\n"); //偏移到协议头最后
int temp=headflag - (char*)buf;//http协议报头末尾-起始位置=报头的数据大小
memcpy( net->net_headbuf + loadlen, buf, headflag - (char*)buf );
//分析http返回值
char *httpstatusflag = strstr( net->net_headbuf, (char*)"HTTP/1.1 " ) + strlen( (char*)"HTTP/1.1 " );
net->net_http_status = atoi( httpstatusflag );
//分析要下载数据的长度
char *httpldlen = strstr( net->net_headbuf, (char*)"Content-Length: " ) + strlen( (char*)"Content-Length: " );
net->net_total_len = atoi( httpldlen );
//服务器返回错误信息
if( net->net_http_status != 206 && net->net_http_status != 200 )
{
net->net_callback( NET_ERROR, index, NULL, 0 );
}
else
{
//剩下的数据发送到应用
if ( headflag - (char*)buf > 0 )
{
net->net_dl_len += len - (headflag - (char*)buf);
net->net_status = NET_READ;
net->net_callback( NET_READ, index, (void*)headflag, len - (headflag - (char*)buf) );
//下载完成
if ( net->net_dl_len >= net->net_total_len )
{
//标记为已完成状态
net->net_status = NET_FINISH;
net->net_callback( NET_FINISH, index, NULL, 0 );
}
}
}
}
else/*if ( headflag )*/
{
//头不会超过4K的长度
memcpy( net->net_headbuf + loadlen, (void*)buf, len );
}
}
else/*if ( net->net_http_status == -1 )*/
{
//下载内容
net->net_dl_len += len;
net->net_callback( NET_READ, index, buf, len );
//下载完成
if ( net->net_dl_len >= net->net_total_len )
{
net->net_callback( NET_FINISH, index, NULL, 0 );
//标记为已完成状态
net->net_status = NET_FINISH;
}
}
}
else/*if ( net->net_mode == HTTP_GET_MODE || net->net_mode == HTTP_POST_MODE )*/
{
net->net_callback( NET_READ, index, buf, len );
}
}
else
{
net->net_callback( event, index, buf, len );
}
//下面可以加入出错关闭socket代码
}
int CMyNetWork::GetDownLoadRate( int index )
{
if ( index >= 0 && index < MAX_NET )
{
if ( net_obj[index] )
{
if( net_obj[index]->net_total_len > 0 )
{
return (int)(net_obj[index]->net_dl_len*100)/net_obj[index]->net_total_len;
}
}
}
return 0;
}
void CMyNetWork::SetTimeOut(int time)
{
net_time = time;
}
int CMyNetWork::Send(int index, void* data, int len)
{
if ( index >= 0 && index < MAX_NET )
{
if ( net_obj[index] )
{
net_obj[index]->timecount = 0;
return net_obj[index]->Send( data, len );
}
}
return -1;
}