中兴捧月大赛之经验教训总结(上)

        参加这个比赛的过程中,自己也摸索和学习了很多新的知识,特别是网络编程这块,以前学得太简单,这次到了前所未有的学习深度。这里一并总结一下。

技术要点:

1.C语言实现简单数据结构。

        由于大赛要求必须在c环境下,而且不允许用第三方库,所以必须在编码实现方案之前,实现一些可能用到的数据结构,包括双链表,map。

1.1 简单双链表的实现。

        我这里参考了linux内核中链表的实现来实现简单双链表。

        链表节点定义:

/*链表节点*/
typedef struct _ListNode
{
	struct _ListNode* next;//链表上的下游节点
	struct _ListNode* pre;//链表上的上游节点
}ListNode;
        整数链表节点定义:

/*整数链表节点*/
typedef struct _IntNode
{
	ListNode listCtrl;//必须在结构体的最前面
	int data;//数据域
}IntNode;
        链表遍历:

/*遍历链表。pos为遍历游标,head为需要遍历的链表*/
#define for_each(pos,head) \
	for(pos=(head)->next;pos!=(head);pos=pos->next) \

/*安全遍历链表。pos为遍历游标,n为临时变量,head为表头。*/
#define for_each_safe(pos,n,head) \
	for(pos=(head)->next;n=pos->next,pos!=(head);pos=n) \
1.2 map的实现。

        map其实就是存储key-value对。我这里直接在简单链表的基础上实现map:简单链表上每个节点有两个整数key和value。节点定义:

/*map节点*/
typedef struct _MapNode
{
	ListNode listCtrl;//用双链表来组织map节点
	int key;//键
	int value;//值
}MapNode;
2。 日志模块

        在设计方案时,考虑到网络编程复杂性,设计了一个日志模块辅助开发和调试。日志模块的实现很简单,参考printf函数的实现实现一个自己的日志打印函数LogPrintf:

int LogPrintf(const char* fmt,...)
{
	int ret = 0;
	va_list args;

	//解析输入格式
	va_start(args, fmt);

	//如果指定了日志文件,则将日志输出到日志文件。
        if(gLogFile)
	{		
		ret = vfprintf(gLogFile,fmt,args);

		//及时将日志内容刷到文件。
		fflush(gLogFile);
		return ret;
	}
	else
	{
		//将日志信息输出到屏幕
		return vfprintf(stdout,fmt,args);
	}
}
3。快速刷新控制台

        本比赛中没有设计GUI界面,用了一个cmd终端来做界面。于是遇到了一个快速高效刷新控制台的问题,解决办法很简单,在用printf函数前将光标定位,这样可以将需要变化的字符刷新即可,其他不需要变化的字符,如界面的label,就不需要刷新,其实就是局部刷新的意思啦。

        vc下光标定位函数:

void gotoxy(int x,int y){
	COORD coord;
	coord.X=x;
	coord.Y=y;
	SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), coord );
}
        vc下某处打印字符串函数:

void printfString(int x,int y,int totalLen,int type,const char* fmt,...)
{
	char temp[100];
	
	va_list args;
	
	//解析输出格式
	va_start(args, fmt);	
	
	gotoxy(x,y);
	
	//将信息输出到字符数组
	vsprintf(temp,fmt,args);	
	
	getSpaceString(temp,totalLen,type);
	
	printf("%s",buffer);
	
	//gotoxy(0,23);
}
4.setsockopt相关

地址重用:

setsockopt(gLocalSocket,SOL_SOCKET,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof( BOOL));

收发超时:

	//设置接收超时
	setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char *)&recvTm,sizeof(int));

	recvTm = SOCKET_TIMEOUT_MS;
	//设置发送超时
	setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char *)&recvTm,sizeof(int));

设置阻塞非阻塞:

void SetBlock(SOCKET sock,int blockFlag)
{
	//设置非阻塞
	unsigned long ul = (blockFlag==FALSE) ? 1:0; 	//1为非阻塞,0为阻塞
	int ret;
	ret = ioctlsocket(sock, FIONBIO, (unsigned long*)&ul); 
	if(ret==SOCKET_ERROR){
		LogPrintf("set %s faield\n",blockFlag==FALSE?"unblock":"block");
	}
}

异步连接快速判断是否可以连接服务器:

                //建立和服务器的连接
		if(connect(sock,(struct sockaddr*)&serverAddress,sizeof(serverAddress)) == SOCKET_ERROR)
		{
			errorCode = GetLastError();
			
			//sock为非法socket 则重新创建socket
			if(errorCode == WSAENOTSOCK)
			{
				while((sock = socket(AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET);
				continue;
			}

			while(connectRetryCount > 0)
			{				
				FD_ZERO(&fdWrite);
				FD_SET(sock,&fdWrite);
				
				errorCode = select(0,NULL,&fdWrite,0,&timeout);

				//已经连接上
				if(errorCode > 0)
				{
					SetBlock(sock,TRUE);
					return sock;
				}
				else if(errorCode==0)//连接超时
				{
					connectRetryCount--;
				}
				else
				{
					LogPrintf("NTCreateSocket:select error %d!\n",GetLastError());
				}	
			}
		}
		else
		{
			SetBlock(sock,TRUE);

			//连接成功,则返回已经连接服务器的socket
			return sock;
		}
5。网络错误处理

发送数据时,一般需要处理的错误码是WSAENETRESET、WSAECONNRESET、WSAECONNABORTED、WSAETIMEDOUT。

接收数据时,一般需要处理的错误码是WSAEINTR(这种情况属于正常情况)、WSAECONNRESET、WSAETIMEDOUT、 WSAECONNABORTED。








你可能感兴趣的:(中兴捧月大赛之经验教训总结(上))