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

  1. 代码来源于 》》》》》》》》》》》》》》》》》》》》》 Windows Sockets网络开发VC++ 这本书  

  1. 在stdafx.h 中添加 #pragma comment(lib,"ws2_32.lib")  

//funtiondec.h

#ifndef FUNTIONDEC_H 
#define FUNTIONDEC_H

#include "winsock2.h" 

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

#define EXPRESSION 'E' //算数表达式
#define BYEBYE 'B'  //消息byebye,Byebye,OK
#define HEADERLEN (sizeof(hdr)) //头长度

void InitMember(void);
BOOL InitSocket(void);
BOOL StartServer(void);
BOOL CreateHelperAndAcceptThread(void);
void StopService(void);
void ExitServer(void);
DWORD __stdcall HelperThread(void* pParam);
DWORD __stdcall AcceptThread(void* pParam);

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

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

}DATABUF,*pDataBuf;

/*
	数据包的操作
*/
class CClient
{
public :
	CClient(const SOCKET sClient, const sockaddr_in &addrClient);
	virtual ~CClient();
public:
	BOOL StartRuning(void); //创建发送和接收数据线程
	void HandleData(const char* pExpr);  //计算表达式
	BOOL IsConning(void)
	{
		return m_bConning;
	}
	void DisConning(void)  //断开与客户端的连接
	{
		m_bConning = FALSE;
	}
	BOOL IsExit(void)  //接收和发送线程是否已经退出
	{
		return m_bExit;
	}
	char* Getm_addr()
	{
		return inet_ntoa(m_addr.sin_addr);;
	}

public:
	static DWORD __stdcall RecvDataThread(void* pParam);  //接收客户端数据
	static DWORD __stdcall SendDataThread(void* pParam); //向客户端发送数据

private:
	CClient();

private:
	SOCKET m_socket;  //套接字
	sockaddr_in m_addr;  //地址
	DATABUF m_data;  //数据
	HANDLE m_hEvent;  //事件对象
	HANDLE m_hThreadSend;  //发送数据线程句柄
	HANDLE m_hThreadRecv;  //接收数据线程句柄
	CRITICAL_SECTION m_cs;  //临界区对象
	BOOL  m_bConning; //客户端连接状态
	BOOL m_bExit; //线程退出
};


#endif


//CClient.cpp

#include "stdafx.h"
#include "funtiondec.h" 

//构造函数
CClient::CClient(const SOCKET sClient, const sockaddr_in &addrClient)
{
	//初始化变量
	m_hThreadRecv = NULL;
	m_hThreadSend = NULL;
	m_socket = sClient;
	m_addr = addrClient;
	m_bConning = FALSE;
	m_bExit = FALSE;
	memset(m_data.buf, 0, MAX_NUM_BUF);
	m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //手动设置信号状态 ,初始化为无信号状态
	InitializeCriticalSection(&m_cs); //初始化临界区
}

//析构函数
CClient::~CClient()
{
	closesocket(m_socket);  //关闭套接字
	m_socket = INVALID_SOCKET;  //套机诶无效
	DeleteCriticalSection(&m_cs); //释放临界区对象
	CloseHandle(m_hEvent); //释放事件对象
}

/*
	创建发送和接收数据线程
*/
BOOL CClient::StartRuning(void)
{
	m_bConning = TRUE;  //设置连接状态

	//创建接收数据线程
	unsigned long ulThreadId;
	m_hThreadRecv = CreateThread(NULL, 0, RecvDataThread, this, 0, &ulThreadId);
	if (NULL == m_hThreadRecv)
	{
		return FALSE;
	}
	else
	{
		CloseHandle(m_hThreadRecv);
	}

	//创建接收数据线程
	m_hThreadSend = CreateThread(NULL, 0, SendDataThread, this, 0, &ulThreadId);
	if (NULL == m_hThreadSend)
	{
		return FALSE;
	}
	else
	{
		CloseHandle(m_hThreadSend);
	}
	return TRUE;
}

/*
	接收客户端数据
*/
DWORD CClient::RecvDataThread(void* pParam)
{
	CClient *pClient = (CClient*)pParam;  //客户端对象指针
	int reVal;  //返回值
	char temp[MAX_NUM_BUF];  //临时变量

	memset(temp, 0, MAX_NUM_BUF);  

	for (; pClient->m_bConning;)  //连接状态
	{
		reVal = recv(pClient->m_socket, temp, MAX_NUM_BUF, 0);  //接收数据

		//处理错误返回值
		if (SOCKET_ERROR == reVal)
		{
			int nErrCode = WSAGetLastError();
			if (WSAEWOULDBLOCK == nErrCode)  //接受数据缓冲区不可用
			{
				continue;  //继续循环
			}
			else if (WSAENETDOWN == nErrCode   //客户端关闭了连接
				|| WSAETIMEDOUT == nErrCode
				|| WSAECONNRESET == nErrCode)
			{
				break; //线程退出
			}
		}

		//客户端关闭了连接
		if (reVal == 0)
		{
			break;
		}
		
		//收到数据
		if (reVal > HEADERLEN)
		{
			
			pClient->HandleData(temp);  //处理数据
			SetEvent(pClient->m_hEvent);  //通知发送数据线程
			memset(temp, 0, MAX_NUM_BUF);  //清空临时变量
		}

		Sleep(TIMEFOR_THREAD_CLIENT);  //线程睡眠
	}

	pClient->m_bConning = FALSE; //与客户端的连接断开
	SetEvent(pClient->m_hEvent); //通知发送数据线程退出

	return 0; //退出
}

/*
	计算表达式,打包数据
*/
void CClient::HandleData(const char* pExpr)
{
	memset(m_data.buf, 0, MAX_NUM_BUF);//清空m_data

	//如果"byebye" 或者 "Byebye"
	if (BYEBYE == ((phdr)pExpr)->type)
	{
		EnterCriticalSection(&m_cs);

		phdr pHeaderSend = (phdr)m_data.buf;  //发送的数据
		pHeaderSend->type = BYEBYE;  //单词类型
		pHeaderSend->len = HEADERLEN + strlen("OK"); //数据包长度
		memcpy(m_data.buf + HEADERLEN, "OK", strlen("OK")); //复制数据到m_data
		LeaveCriticalSection(&m_cs);
	}
	else
	{//算数表达式
		int nFirNum;  //第一个数字
		int nSecNum;  //第二个数字
		char cOper;  //算数运算符
		int nResult; //计算结果

		//格式化读入数据
		
		sscanf_s(pExpr + HEADERLEN, "%d%c%d", &nFirNum, &cOper,&nSecNum);
		sscanf_s(pExpr + HEADERLEN, "%*d %*c %d", &nSecNum);

		//计算
		switch (cOper)
		{
		case '+':
		{
					nResult = nFirNum + nSecNum;
					break;
		}
		case '-':
		{
					nResult = nFirNum - nSecNum;
					break;
		}
		case '*':
		{
					nResult = nFirNum * nSecNum;
					break;
		}
		case '/':
		{
					if (0 == nSecNum)  //无效的数字
					{
						nResult = -1;
					}
					else
						nResult = nFirNum / nSecNum;
					break;
		}
		default:
			nResult = -1;  //无效的操作符
			break;
		}

		//将算数表达式和计算的结果写入字符数组中
		char temp[MAX_NUM_BUF];
		char cEqu = '='; 
		sprintf_s(temp, "%d%c%d%c%d", nFirNum, cOper, nSecNum, cEqu, nResult);

		//打包数据
		EnterCriticalSection(&m_cs);
		phdr pHeaderSend = (phdr)m_data.buf;  //发送的数据
		pHeaderSend->type = EXPRESSION; //数据类型为算数表达式
		pHeaderSend->len = HEADERLEN + strlen(temp);  //数据包的长度
		memcpy(m_data.buf + HEADERLEN, temp, strlen(temp));  //复制数据到m_data
		

		LeaveCriticalSection(&m_cs);
	}
}

/*
	向客户端发送数据
*/

DWORD CClient::SendDataThread(void* pParam)
{
	CClient *pClient = (CClient*)pParam;  //转化数据类型为CClient指针

	for (; pClient->m_bConning;) //连接状态
	{
		//收到事件通知 
		if (WAIT_OBJECT_0 == WaitForSingleObject(pClient->m_hEvent, INFINITE))
		{
			//当客户端的连接断开时,接收数据线程先退出,然后该线程后退出,并设置退出标志
			if (!pClient->m_bConning)
			{
				pClient->m_bExit = TRUE;
				break;
			}

			//进入临界区
			EnterCriticalSection(&pClient->m_cs);

			//发送数据
			phdr pHeader = (phdr)pClient->m_data.buf;
			int nSendlen = pHeader->len;

			
			int val = send(pClient->m_socket, pClient->m_data.buf, nSendlen, 0);

			//处理返回错误
			if (SOCKET_ERROR == val)
			{
				int nErrCode = WSAGetLastError();
				if (nErrCode == WSAEWOULDBLOCK)  //发送数据缓冲区不可用
				{
					continue;
				}
				else if (WSAENETDOWN == nErrCode
					|| WSAETIMEDOUT == nErrCode
					|| WSAECONNRESET == nErrCode) //客户端关闭了连接
				{
					//离开临界区
					LeaveCriticalSection(&pClient->m_cs);
					pClient->m_bConning = FALSE; //连接断开
					pClient->m_bExit = TRUE;  //线程退出
					break;
				}
				else
				{
					//离开临界区
					LeaveCriticalSection(&pClient->m_cs);
					pClient->m_bConning = FALSE;  //连接断开
					pClient->m_bExit = TRUE;  //线程结束
					break;
				}
			}
			//成功发送数据
			printf_s("Send()发送数据成功\n");
			
			//离开临界区
			LeaveCriticalSection(&pClient->m_cs);
			//设置事件为无信号状态
			ResetEvent(&pClient->m_hEvent);
		}
	}
	return 0;
}
// ServerCounting.cpp : 远程算数  服务器
//

#include "stdafx.h"
#include "winsock2.h" 
#include "funtiondec.h"   //所需函数声明  
#include <list>
#include <string>
using namespace std;


HANDLE hThreadAccept;  //接受客户端连接线程句柄
HANDLE hThreadHelp;  //释放资源线程句柄
SOCKET sServer;   //监听套接字
BOOL bServerRunning;  //服务器的工作状态
HANDLE hServerEvent;  //服务器退出事件对象
std::list<CClient*> clientlist;  //管理连接的链表                                                                  
CRITICAL_SECTION  csClientList;  //保护链表的临界区对象

/*
	初始化全局变量
*/
void InitMember(void)
{
	InitializeCriticalSection(&csClientList);  //初始化临界区  临界区对象在使用之前必须初始化,在使用完后删除
	hServerEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //手动设置事件,初始化为无信息状态
	hThreadAccept = NULL;  
	hThreadHelp = NULL;
	sServer = INVALID_SOCKET;  //设置为无效的套接字
	bServerRunning = FALSE;  //服务器为没有运行状态
	clientlist.clear();  //清空链表
}

/*
	初始化SOCKET
*/
BOOL InitSocket(void)
{
	int reVal;  //返回值

	//初始化Windows Sockets DLL
	WSADATA wsData;
	reVal = WSAStartup(MAKEWORD(2, 2), &wsData);
	if (0 != reVal)
	{
		printf("Can not find a usable Windows Sockets dll!");
		return FALSE;
	}

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

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

	//绑定套接字
	sockaddr_in  serAddr;
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(SERVERPORT);
	serAddr.sin_addr.S_un.S_addr = INADDR_ANY;

	reVal = bind(sServer, (struct sockaddr*)&serAddr, sizeof(serAddr));
	if (SOCKET_ERROR == reVal)
		return FALSE;
	
	//监听
	reVal = listen(sServer, SOMAXCONN);
	if (SOCKET_ERROR == reVal)
		return FALSE;
	

	return TRUE;
}

/*
	启动服务
*/
BOOL StartServer(void)
{
	BOOL reVal = TRUE;  // 返回值

	printf("输入s 或 S\n");  //提示用户输入

	char cInput;  //输入字符

	do
	{
		scanf_s("%c", &cInput);
		if ('s' == cInput || 'S' == cInput)
		{
			if (CreateHelperAndAcceptThread())  //创建清理资源和接受客户端请求的线程
			{
				//ShowServerStartMsg(TRUE);  //创建线程成功
				printf("创建线程成功\n");
			}
			else
			{
				reVal = FALSE;
			}
				

			break;  //跳出循环体
		}
		else
		{
			printf("输入正确!\n");
		}

	} while (cInput != 's' && cInput != 'S'); //必须输入's'或者'S'字符

	return reVal;
}

/*
	创建释放资源线程和接收客户端请求线程
*/
BOOL CreateHelperAndAcceptThread(void)
{
	bServerRunning = TRUE;  //设置服务器为运行状态

	//创建释放资源线程
	unsigned long ulThreadId;
	hThreadHelp = CreateThread(NULL, 0, HelperThread, NULL, 0, &ulThreadId);
	if (NULL == hThreadHelp)
	{
		bServerRunning = FALSE;
		return FALSE;
	}
	else
	{
		CloseHandle(hThreadHelp);
	}

	//创建接收客户端请求线程
	hThreadAccept = CreateThread(NULL, 0, AcceptThread, NULL, 0, &ulThreadId);
	if (NULL == hThreadAccept)
	{
		bServerRunning = FALSE;
		return FALSE;
	}
	else
	{
		CloseHandle(hThreadAccept);
	}
	return TRUE;
}

/*
	停止服务
*/
void StopService(void)
{
	BOOL reVal = TRUE;  //返回值

	printf("输入e 或 E\n");

	char cInput;
	for (; bServerRunning;)
	{
		scanf_s("%c", &cInput);
		if (cInput == 'E' || cInput == 'e')
		{
			if (IDOK == MessageBox(NULL, _T("Are you sure?"),
				_T("Server"), MB_OKCANCEL))  //等待用户确认
				break;
			else
			{
				Sleep(TIMEFOR_THREAD_EXIT);  //线程睡眠
			}
		}
		else
		{
			Sleep(TIMEFOR_THREAD_EXIT);  //线程睡眠
		}

	}
	bServerRunning = FALSE; //服务器退出

	printf("服务器退出!\n");

	Sleep(TIMEFOR_THREAD_EXIT);  //给其他线程时间退出

	WaitForSingleObject(hServerEvent, INFINITE);//等待清理资源线程发送的事件
	return;
}

/*
	释放资源
*/
void ExitServer(void)
{
	DeleteCriticalSection(&csClientList);  //释放临界区对象
	CloseHandle(hServerEvent); //释放事件对象句柄
	closesocket(sServer); //关闭SOCKET
	WSACleanup(); //卸载Windowss Sockets DLL
}

/*
	接受客户端请求
*/
DWORD __stdcall AcceptThread(void* pParam)
{
	SOCKET sAccept; //接受客户端连接的套接字
	sockaddr_in addrClient;  //客户端SOCKET地址

	for (; bServerRunning;)  //服务器状态
	{
		memset(&addrClient, 0, sizeof(sockaddr_in));  //初始化
		int lenClient = sizeof(sockaddr_in); //地址长度
		sAccept = accept(sServer, (sockaddr*)&addrClient, &lenClient); //接受客户端请求

		if (INVALID_SOCKET == sAccept)
		{
			
			int nErrCode = WSAGetLastError();
			if (nErrCode == WSAEWOULDBLOCK)  //无法立即完成一个非阻挡性套接字操作
			{
				Sleep(TIMEFOR_THREAD_SLEEP);
				continue;  //继续等待
			}
			else
			{
				return 0;  //线程退出
			}
		}
		else  //接受客户端的请求
		{
			printf_s("accept请求成功\n");
			CClient* pClient = new CClient(sAccept, addrClient);  //创建客户端对象
			EnterCriticalSection(&csClientList);  //进入在临界区
			clientlist.push_back(pClient);  //加入链表
			LeaveCriticalSection(&csClientList);  //离开临界区
			
			pClient->StartRuning();// 为接受的客户端建立接收数据和发送数据线程
		}
	}

	return 0;  //线程退出
}

/*
	清理资源
*/
DWORD __stdcall HelperThread(void* pParam)
{
	for (; bServerRunning;)  //服务器正在运行
	{
		EnterCriticalSection(&csClientList);  //进入临界区

		//清理已经断开的连接客户端内存空间

		list<CClient*>::iterator iter = clientlist.begin();
		for (iter; iter != clientlist.end();)
		{
			CClient *pClient = (CClient*)*iter;
			if (pClient->IsExit())  //客户端线程已经退出
			{
				printf_s("客户端退出IP:%s\n", pClient->Getm_addr());
				clientlist.erase(iter++);  //删除节点
				delete pClient;  //释放内存
				pClient = NULL;
			}
			else
			{
				iter++;  //指针下移
			}
		}

		LeaveCriticalSection(&csClientList);  //离开临界区

		Sleep(TIMEFOR_THREAD_HELP);
	}

	//服务器停止工作
	if (!bServerRunning)
	{
		//断开每个连接,线程退出
		EnterCriticalSection(&csClientList);
		list<CClient*>::iterator iter = clientlist.begin();
		for (iter; iter != clientlist.end();)
		{
			CClient *pClient = (CClient*)*iter;
			//如果客户端的连接还存在,则断开连接,线程退出
			if (pClient->IsConning())
			{
				pClient->DisConning();
			}
			++iter;
		}

		//离开临界区
		LeaveCriticalSection(&csClientList);

		//给连接客户端线程时间,使其自动退出
		Sleep(TIMEFOR_THREAD_SLEEP);

		//进入临界区
		EnterCriticalSection(&csClientList);
		
		//确保为每个客户端分配的内存空间都回收
		//如果不假如while 这层循环,可能存在这样的情况,当pClient->IsExit()时
		//该线程还没有退出
		//那么就需要从链表的开始部分重新判断。

		while (0 != clientlist.size())
		{
			iter = clientlist.begin();
			for (iter; iter != clientlist.end();)
			{
				CClient *pClient = (CClient*)*iter;
				if (pClient->IsExit())  //客户端线程已经退出
				{
					clientlist.erase(iter++); //删除节点
					delete pClient;  //释放内存空间
					pClient = NULL;
				}
				else
				{
					iter++; //指针下移
				}

			}
			//给连接客户端线程时间,使其自动退出
			Sleep(TIMEFOR_THREAD_SLEEP);
		}
		LeaveCriticalSection(&csClientList);  //离开临界区
	}

	clientlist.clear(); //情况链表

	SetEvent(hServerEvent);  //通知主线程退出

	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{

	//初始化全局变量
	InitMember();

	//初始化服务器
	if (!InitSocket())
	{
		printf("初始化服务器失败\n");
		ExitServer();
		return SERVER_SETUP_FAIL;
	}

	//启动服务
	if (!StartServer())
	{
		printf("启动服务器失败\n");
		ExitServer();
		return SERVER_SETUP_FAIL;

	}

	printf("stop\n");
	//停止服务
	StopService();

	//服务器退出
	ExitServer();

	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值