网络模块分析3

本文详细介绍了网络工作模块,包括封装的select模型,支持多个socket的处理。强调了readfds、writefds和exceptfds数组在监测网络数据中的作用,如检测连接状态、数据读写及异常情况。同时提到了http协议报文,以及线程如何周期性检查网络数据。客户端的可写状态标志着连接成功,服务端的可读则表明有新的请求到达。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

外部网络工作模块

封装了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;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值