- 代码来源于 》》》》》》》》》》》》》》》》》》》》》 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
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;
}