非阻塞模式 多線程 《客戶端與服務器算數運算》 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 <iostream>

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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章