非阻塞模式 多线程 《客户端与服务器算数运算》 TCP/IP通信 之一 客户端实现

  1. 代码来源于 》》》》》》》》》》》》》》》》》》》》》 Windows Sockets网络开发VC++ 这本书  
  1. 在stdafx.h 中添加 #pragma comment(lib,"ws2_32.lib")  

//funtiondec.h

#include "winsock2.h" 

#define CLIENT_SETUP_FAIL 1 //启动客户端失败
#define CLIENT_CREATETHREAD_FAIL 2 //创建线程失败
#define TIMEFOR_THREAD_EXIT 5000 //主线程睡眠时间
#define TIMEFOR_THREAD_SLEEP 500 //等待客户端请求线程睡眠时间
#define TIMEFOR_THREAD_CLIENT 500 //线程睡眠
#define TIMEFOR_THREAD_HELP  500 
#define SERVERPORT 5556  //服务器TCP端口
#define MAX_NUM_BUF 48  //缓冲区的最大长度

//数据包类型和包头长度
#define EXPRESSION 'E' //算数表达式
#define BYEBYE 'B'  //消息byebye
#define HEADERLEN (sizeof(hdr)) //头长度

void InitMember(void);
BOOL ConnectServer(void);
BOOL CreateSendAndRecvThread(void);
void InputAndOutPut(void);
BOOL PackExpression(const char *pExpr);
BOOL PackByebye(const char *pExpr);
void ExitClient(void);
DWORD __stdcall SendDataThread(void* pParam);
DWORD __stdcall RecvDataThread(void* pParam);
void ShowDataRecultMsg();

//数据包头结构,该结构在win32下为4byte
typedef struct _head
{
	char type;  //类型
	unsigned short len;  //数据包的长度(包括头的长度)
}hdr, *phdr;

//数据包中的数据结构
typedef struct _data
{
	char buf[MAX_NUM_BUF];  //数据

}DATABUF, *pDataBuf;

// ClientCounting.cpp : 定义控制台应用程序的入口点。
// 在BOOL ConnectServer(void) 函数中  记得重新设置   本机IP地址


#include "stdafx.h"
#include "winsock2.h" 
#include "funtiondec.h"   //所需函数声明  
#include 

using namespace std;

SOCKET sClient;  //套接字
HANDLE hThreadSend;  //发送数据线程
HANDLE hThreadRecv;  //接收数据线程
DATABUF bufSend;  //发送数据缓冲区
DATABUF bufRecv;  //接收数据缓冲区
CRITICAL_SECTION csSend;  //临界区对象 锁定bufSend
CRITICAL_SECTION csRecv;  //临界区对象 锁定bufRecv
BOOL bSendData;  //通知发送数据线程
HANDLE hEventShowDataResult;  //显示计算结果的事件
BOOL bConnecting;  //与服务器的连接状态
HANDLE arrThread[2];  //子线程数组

/*
	初始化全局变量
*/
void InitMember(void)
{
	//初始化临界区
	InitializeCriticalSection(&csSend);
	InitializeCriticalSection(&csRecv);

	sClient = INVALID_SOCKET;  //套接字
	hThreadRecv = NULL;  //接收数据线程句柄
	hThreadSend = NULL;  //发送数据线程句柄
	bConnecting = FALSE;  //为连接状态
	bSendData = FALSE;  //不发送数据状态

	//初始化数据缓冲区
	memset(bufSend.buf, 0, MAX_NUM_BUF);
	memset(bufRecv.buf, 0, MAX_NUM_BUF);
	memset(arrThread, 0, 2);

	//手动设置事件,初始化为无信号状态
	hEventShowDataResult = (HANDLE)CreateEvent(NULL, TRUE, FALSE, NULL);

}

/*
	创建非阻塞套接字
*/
BOOL InitSocket(void)
{
	int reVal;  //返回值
	WSADATA  wsData;  //
	reVal = WSAStartup(MAKEWORD(2, 2), &wsData);  //初始化Windows Sockets

	//创建套接字
	sClient = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sClient)
	{
		return FALSE;
	}

	//设置套接字非阻塞模式
	unsigned long ul = 1;
	reVal = ioctlsocket(sClient, FIONBIO, (unsigned long*)&ul);
	if (reVal == SOCKET_ERROR)
		return FALSE;

	return TRUE;

}

/*
	连接服务器
*/
BOOL ConnectServer(void)
{
	int reVal;  //返回值
	sockaddr_in serAddr;  //服务器地址

	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(SERVERPORT);
	serAddr.sin_addr.S_un.S_addr = inet_addr("192.168.78.1");  //本机IP地址
	for (;;)
	{
		//连接服务器
		reVal = connect(sClient, (struct sockaddr*)&serAddr, sizeof(serAddr));

		//处理连接错误
		if (SOCKET_ERROR == reVal)
		{
			int nErrCode = WSAGetLastError();
			if (WSAEWOULDBLOCK == nErrCode  //连接还没有完成
				|| WSAEINVAL == nErrCode)
			{
				continue;
			}
			else if (WSAEISCONN == nErrCode)  //连接已经完成
			{
				break;
			}
			else
			{
				return FALSE;  //其他原因,连接失败
			}
		}
		if (reVal == 0)  //连接成功
			break;
	}

	bConnecting = TRUE;
	 
	return TRUE;
}

/*
	创建发送和接收数据线程
*/
BOOL CreateSendAndRecvThread(void)
{
	//创建接收数据的线程

	unsigned long ulThreadId;
	hThreadRecv = CreateThread(NULL, 0, RecvDataThread, NULL, 0, &ulThreadId);
	if (NULL == hThreadRecv)
		return FALSE;

	//创建发送数据的线程
	hThreadSend = CreateThread(NULL, 0, SendDataThread, NULL,0, &ulThreadId);
	if (NULL == hThreadSend)
		return FALSE;

	//添加到线程数组
	arrThread[0] = hThreadRecv;
	arrThread[1] = hThreadSend;

	return TRUE;
}

/*
	输入数据和显示结果
*/
void InputAndOutPut(void)
{
	char cInput[MAX_NUM_BUF];  //用户输入缓冲区
	BOOL bFirstInput = TRUE;  //第一次只能输入算数表达式

	for (; bConnecting;)  //连接状态
	{
		memset(cInput, 0, MAX_NUM_BUF);

		//提示输入表达式
		printf_s("输入表达式,形如:a+b=\n");
		printf_s("若想退出客户端输入:byebye 或 Byebye\n");
		printf_s("第一次不能输入byebye\n");
		cin >> cInput;
		
		char *pTemp = cInput;
		if (bFirstInput)
		{
			if (!PackExpression(pTemp))  //算数表达式打包
			{
				continue;   //重新输入
			}
			bFirstInput = FALSE;  //成功输入第一个算数表达式
		}
		else if (!PackByebye(pTemp))  //:"Byebye" "byebye" 打包
		{
			if (!PackExpression(pTemp))  //算数表达式打包
			{
				continue;  //重新输入
			}
		}
		
		//等待显示计算结果
		if (WAIT_OBJECT_0 == WaitForSingleObject(hEventShowDataResult, INFINITE))
		{
			ResetEvent(hEventShowDataResult);  //设置为无信号状态
			if (!bConnecting)  //客户端被动退出,此时接收和发送数据线程已经退出
			{
				break;
			}

			ShowDataRecultMsg();  //显示数据结果

			if (0 == strcmp(bufRecv.buf, "OK"))  //客户端主动退出
			{
				bConnecting = FALSE; 
				Sleep(TIMEFOR_THREAD_EXIT);  //给数据接收和发送线程退出时间
			}
		}

	}

	if (!bConnecting)  //与服务器连接已经断开
	{
		printf_s("与服务器连接已经断开\n");  //显示信息
	}

	//等待数据发送和接收线程退出
	DWORD reVal = WaitForMultipleObjects(2, arrThread, TRUE, INFINITE);
	if (WAIT_ABANDONED_0 == reVal)
	{
		int nErrCode = GetLastError();
	}

}

/*
	判断byebye
*/
BOOL PackByebye(const char *pExpr)
{
	if (strcmp(pExpr, "Byebye") == 0|| strcmp(pExpr, "byebye") == 0)
	{
		//表达式读入发送数据缓冲区
		EnterCriticalSection(&csSend);  //进入临界区
		//数据包头
		phdr pHeader = (phdr)(bufSend.buf);
		pHeader->type = BYEBYE;
		pHeader->len = 7 + HEADERLEN;

		//拷贝数据
		memcpy(bufSend.buf + HEADERLEN, pExpr, 7);
		LeaveCriticalSection(&csSend);  //离开临界区
		pHeader = NULL;

		bSendData = TRUE;  //通知发送数据线程发送数据
		return TRUE;
	}
	else
	{
		return FALSE;
	}


}

/*
	打包计算表达式的数据
*/
BOOL PackExpression(const char *pExpr)
{
	char *pTemp = (char*)pExpr;  //算数表达式数字开始的位置
	while (!*pTemp)  //第一个数字位置
		pTemp++;

	char* pos1 = pTemp; //第一个数字位置
	char* pos2 = NULL;  //运算符位置
	char* pos3 = NULL;  //第二个数字位置
	int len1 = 0;  //第一个数字长度
	int len2 = 0;  //运算符长度
	int len3 = 0;  //第二个数字长度

	//第一个字符是+ - 或者是数字
	if ((*pTemp != '+')
		&& (*pTemp != '-')
		&& ((*pTemp < '0') || (*pTemp > '9')))
	{
		return FALSE;
	}

	//第一个字符是'+' 第二个是数字
	if ((*pTemp++ == '+') && (*pTemp < '0' || *pTemp > '9'))
		return FALSE;
	--pTemp;

	//第一个字符是'-' 第二个是数字
	if ((*pTemp++ == '-') && (*pTemp < '0' || *pTemp > '9'))
		return FALSE;
	--pTemp;

	char* pNum = pTemp;  //数字开始的位置
	if (*pTemp == '+' || *pTemp == '-')
		pTemp++;

	while (*pTemp >= '0' && *pTemp <= '9')
		pTemp++;

	len1 = pTemp - pNum; 

	//可能有空格
	while (!*pTemp)
		pTemp++;

	//算数运算符
	if (('+' != *pTemp)
		&& ('-' != *pTemp)
		&& ('*' != *pTemp)
		&& ('/' != *pTemp))
		return FALSE;

	pos2 = pTemp;
	len2 = 1;

	//下移指针
	pTemp++;
	//可能有空格
	while (!*pTemp)
		pTemp++;

	//第2个数字位置
	pos3 = pTemp;
	if (*pTemp < '0' || *pTemp > '9')
		return FALSE; 

	while (*pTemp >= '0' && *pTemp <= '9')
		pTemp++;

	if ('=' != *pTemp)
		return FALSE;

	len3 = pTemp - pos3;  //数字长度

	int nExprlen = len1 + len2 + len3;

	//表达式读入发送数据缓冲区
	EnterCriticalSection(&csSend);  //进入临界区
	//数据包头
	phdr pHeader = (phdr)(bufSend.buf); 
	pHeader->type = EXPRESSION;
	pHeader->len = nExprlen + HEADERLEN;

	//拷贝数据
	memcpy(bufSend.buf + HEADERLEN, pos1, len1);
	memcpy(bufSend.buf + HEADERLEN + len1, pos2, len2);
	memcpy(bufSend.buf + HEADERLEN + len1 + len2, pos3, len3);
	LeaveCriticalSection(&csSend);  //离开临界区
	pHeader = NULL;

	bSendData = TRUE;  //通知发送数据线程发送数据
	return TRUE; 

}

/*
释放资源
*/
void ExitClient(void)
{
	DeleteCriticalSection(&csSend);  //释放临界区对象
	DeleteCriticalSection(&csRecv);  //释放临界区对象
	closesocket(sClient); //关闭SOCKET
	WSACleanup(); //卸载Windowss Sockets DLL
}

/*
	发送数据线程
*/
DWORD __stdcall SendDataThread(void* pParam)
{
	while (bConnecting)
	{
		if (bSendData)
		{
			EnterCriticalSection(&csSend);  //进入临界区
			for (;;)
			{
				int nBuflen = ((phdr)(bufSend.buf))->len;
				int val = send(sClient, bufSend.buf, nBuflen, 0);

				//处理返回错误
				if (SOCKET_ERROR == val)
				{
					int nErrCode = WSAGetLastError();
					if (WSAEWOULDBLOCK == nErrCode) //发送缓冲区不可用
					{
						continue;  //继续循环
					}
					else
					{
						LeaveCriticalSection(&csSend);  //离开临界区
						bConnecting = FALSE;  //断开状态
						SetEvent(hEventShowDataResult);  //通知主线程,防止在无限期的等待

						return 0;
					}

				}
				bSendData = FALSE; //发送状态
				break;  //跳出for
			}
			LeaveCriticalSection(&csSend);  //离开临界区
		}
		Sleep(TIMEFOR_THREAD_SLEEP);  //线程睡眠
	}
	return 0;
}

/*
	接收数据线程
*/
DWORD __stdcall RecvDataThread(void* pParam)
{
	int reVal;  //返回值
	char temp[MAX_NUM_BUF];  
	memset(temp, 0, MAX_NUM_BUF);

	while (bConnecting)
	{
		reVal = recv(sClient, temp, MAX_NUM_BUF, 0); //接收数据
		
		if (SOCKET_ERROR == reVal)
		{
			int nErrCode = WSAGetLastError();
			if (WSAEWOULDBLOCK == nErrCode)  //接受数据缓冲区不可用
			{
				Sleep(TIMEFOR_THREAD_SLEEP);  //线程睡眠
				continue;
			}
			else
			{
				bConnecting = FALSE;
				SetEvent(hEventShowDataResult); //通知住线程,防止在无限期的等待
				return 0;
			}
		}

		if (reVal == 0)  //服务器关闭了连接
		{
			bConnecting = FALSE;
			SetEvent(hEventShowDataResult); //通知主线程,放置在无限期的等待
			return 0; 

		}
		if (reVal > HEADERLEN && -1 != reVal)  //收到数据
		{
			// 对数据解包,将数据结果赋值到接收数据缓冲区
			phdr header = (phdr)(temp);
			EnterCriticalSection(&csRecv);
			memset(bufRecv.buf, 0, MAX_NUM_BUF);
			memcpy(bufRecv.buf, temp + HEADERLEN, header->len - HEADERLEN);
			LeaveCriticalSection(&csRecv);
			
			SetEvent(hEventShowDataResult);  //通知主线程显示计算结果
			memset(temp, 0, MAX_NUM_BUF);
		}
		Sleep(TIMEFOR_THREAD_SLEEP);  //线程睡眠
	}

	return 0;
}

void ShowDataRecultMsg()
{
	printf("ClientRecvLine:%s\n", bufRecv.buf);
}

int _tmain(int argc, _TCHAR* argv[])
{
	
	//初始化全局变量
	InitMember();
	
	//初始化客户端
	if (!InitSocket())
	{
		printf("初始化失败\n");
		ExitClient();
		return CLIENT_SETUP_FAIL;
	}
	else
	{
		printf("初始化成功\n");
	}
	
	//连接服务器

	if (ConnectServer())
	{
		printf("连接服务器成功!\n");
	}
	else
	{
		printf("连接服务器失败!\n");
		ExitClient();
		return CLIENT_SETUP_FAIL;
	}
	
	//创建发送和接收数据线程
	if (!CreateSendAndRecvThread())
	{
		ExitClient();
		return CLIENT_CREATETHREAD_FAIL;
	}

	//用户输入数据和显示结果
	InputAndOutPut();

	//退出
	ExitClient();

	return 0;
}


你可能感兴趣的:(Windows网络编程)