- 代碼來源於 》》》》》》》》》》》》》》》》》》》》》 Windows Sockets網絡開發VC++ 這本書
- 在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;
}