关闭

Windows网络编程Select模型的封装和queue作为数据缓存的使用

标签: 多线程select网络编程数据缓冲socket
367人阅读 评论(0) 收藏 举报
分类:

首先是对Select网络模型的封装,由于会用到数据缓存,所以接收到的数据需要对应一个socket套接字,这样回复数据的时候才知道对象。

#pragma once

//存储从某个套接字接受到的数据,通过套接字可以发送处理后的数据
struct NetDataBuffer
{
	char strBuffer[BUFFER_SIZE];
	SOCKET sSocket;
};

class CIOSelectOper
{
public:
	CIOSelectOper();
	~CIOSelectOper();

public:
	/**************************************************
	 *描述  : Listen
	 *参数  : 
	 *返回值:
	 *说明  : 函数用于完成网络初始化,绑定和监听
	 **************************************************/
	void Listen();
	/**************************************************
	 *描述  : Recv
	 *参数  : 
	 *返回值:
	 *说明  : 函数用于Select判断是否有数据并接收数据存入缓存
	 **************************************************/
	bool Recv(DB_OUT NetDataBuffer &strData);
	/**************************************************
	 *描述  : Send
	 *参数  : 
	 *返回值:
	 *说明  : 函数用于对在m_fdRead中保存的套接字句柄进行发送数据
	 **************************************************/
	void Send(DB_IN char *pStrBuffer);
	/**************************************************
	 *描述  : Send
	 *参数  : 
	 *返回值:
	 *说明  : 
	 **************************************************/
	void Send(DB_IN NetDataBuffer strBuffer, DB_IN int nLength);
	/**************************************************
	 *描述  : Handle
	 *参数  : strData		指定某个套接字发送过来的数据
	 *返回值:
	 *说明  : 函数用于对指定套接字进行接收数据判断协议处理
	 **************************************************/
	bool Handle(DB_IN NetDataBuffer &strData);

private:
	bool m_Init;//用来标志网络是否初始化成功
	SOCKET m_sListen;
	fd_set m_fdRead;//保存从一直以来的套接字,不使用select进行筛选
	fd_set m_fdTempRead;//当某一时刻某套接字没有数据进行select会被删除掉,所以需要备份的fdRead去select
};
#include "IOSelectOper.h"

CIOSelectOper::CIOSelectOper()
{
	FD_ZERO(&m_fdRead);
	FD_ZERO(&m_fdClient);
	m_Init = false;
	vector<UserData>().swap(m_vUserData);
}

CIOSelectOper::~CIOSelectOper()
{
	if (m_sListen)
	{
		closesocket(m_sListen);
		m_sListen = 0;
	}
	if (m_Init)
	{
		WSACleanup();
	}
	vector<UserData>().swap(m_vUserData);
}

void CIOSelectOper::Listen()
{
	WSADATA wsaData;
	int nRet = WSAStartup(0x0202, &wsaData);
	if (0 == nRet)
	{
		m_sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (INVALID_SOCKET != m_sListen)
		{
			SOCKADDR_IN addrServer;
			addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
			addrServer.sin_family = AF_INET;
			addrServer.sin_port = htons(atoi(m_cPluginOper.m_sPort.c_str()));
			nRet = bind(m_sListen, (SOCKADDR*)&addrServer, sizeof(addrServer));
			if (0 == nRet)
			{
				nRet = listen(m_sListen, FD_SETSIZE);
				if (0 == nRet)
				{			
					FD_SET(m_sListen, &m_fdRead);
					m_Init = true;
				}
			}
		}
	}	
}

bool CIOSelectOper::Recv(DB_OUT NetDataBuffer &strData)
{
	bool bRet = false;
	FD_ZERO(&m_fdTempRead);
	m_fdTempRead = m_fdRead;
	timeval time = {0, 100};
	int nRet = select(0, &m_fdTempRead, NULL, NULL, &time);
	if (nRet > 0)
	{
		int nCount = m_fdRead.fd_count;
		for (int i = 0; i < nCount; i++)
		{
			if (FD_ISSET(m_fdRead.fd_array[i], &m_fdTempRead))
			{
				//当调用了listen而且有一个连接正在建立
				if (m_sListen == m_fdRead.fd_array[i])
				{
					SOCKADDR_IN addrClient;
					int nSize = sizeof(SOCKADDR_IN);
					SOCKET sClient = accept(m_sListen, (SOCKADDR*)&addrClient, &nSize);
					if (INVALID_SOCKET != sClient)
					{
						FD_SET(sClient, &m_fdRead);
					}
				}
				//有数据可以读入
				else
				{
					strData.sSocket = m_fdRead.fd_array[i];
					int nRet = recv(m_fdRead.fd_array[i], strData.strBuffer, BUFFER_SIZE, 0);
					//连接已经关闭,重设,中止
					if (nRet == 0 || (nRet == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
					{
						closesocket(m_fdRead.fd_array[i]);
						FD_CLR(m_fdRead.fd_array[i], &m_fdRead);
					}
					else if (nRet > 0)
					{
     					bRet = true;//返回true说明成功读到了数据
					}
				}
			}
		}
	}
	else
	{
		Sleep(100);
	}
	return bRet;
}

然后是外部调用select封装类进行数据缓冲的存储和读取出来处理,一般是一个线程只负责读取网络数据放进数据缓冲,然后继续读下面的数据,另外一个线程从数据缓冲中读取数据做处理。

#pragma once

#include "IOSelectOper.h"
#include "DeamonLock.h"
#include <queue>
using namespace std;

class CDeamonServer
{
public:
	CDeamonServer();
	~CDeamonServer();

public:
	void RecvNetBuffer();
	void HandNetBuffer();
	BOOL m_bFlag;//控制线程的退出标志

public:
	static UINT WINAPI RecvNetDataThread(LPVOID pParam);
	static UINT WINAPI HandNetDataThread(LPVOID pParam);

private:
	CIOSelectOper m_cIOSelectOper;
	CDeamonLock m_cNetLock;
	HANDLE m_hRecvNet;
	HANDLE m_hHandNet;
	queue<NetDataBuffer> m_vNetDataBuffer;//存储网络端的数据
};
#include "DeamonServer.h"
#include<process.h>

CDeamonServer::CDeamonServer()
{
	m_cIOSelectOper.Listen();
	m_bFlag = TRUE;
	m_hRecvNet = (HANDLE)_beginthreadex(NULL, 0, RecvNetDataThread, (LPVOID)this, 0, 0);
	m_hHandNet = (HANDLE)_beginthreadex(NULL, 0, HandNetDataThread, (LPVOID)this, 0, 0);
}

CDeamonServer::~CDeamonServer()
{
	WaitForSingleObject(m_hRecvNet, INFINITE);
	WaitForSingleObject(m_hHandNet, INFINITE);
	if (NULL != m_hRecvNet)
	{
		CloseHandle(m_hRecvNet);
		m_hObject = NULL;
	}
	if (NULL != m_hHandNet)
	{
		CloseHandle(m_hHandNet);
		m_hObject = NULL;
	}
}

BOOL CDeamonServer::InitInstance()
{
	while (m_bFlag)
	{
		WaitForSingleObject(m_hServerObject, INFINITE);
		m_bFlag = FALSE;
	}
	return FALSE;
}

void CDeamonServer::RecvNetBuffer()
{
	NetDataBuffer strData;
	if (m_cIOSelectOper.Recv(strData))
	{
		m_cNetLock.LockCritical();
		m_vNetDataBuffer.push(strData);
		m_cNetLock.UnLockCritical();
	}
}

void CDeamonServer::HandNetBuffer()
{
	if (!m_vNetDataBuffer.empty())
	{	
		m_cNetLock.LockCritical();
		NetDataBuffer strData = m_vNetDataBuffer.front();
		m_vNetDataBuffer.pop();
		m_cNetLock.UnLockCritical();
	}	
	else
	{
		Sleep(100);
	}
}

UINT WINAPI CDeamonServer::RecvNetDataThread(LPVOID pParam)
{
	CDeamonServer *pThis = (CDeamonServer*)pParam;
	while(pThis->m_bFlag)
	{	
		pThis->RecvNetBuffer();
	}
	return 0;
}

UINT WINAPI CDeamonServer::HandNetDataThread(LPVOID pParam)
{
	CDeamonServer *pThis = (CDeamonServer*)pParam;
	while(pThis->m_bFlag)
	{	
		pThis->HandNetBuffer();
	}
	return 0;
}
这里会用到类中的一点小技巧,把this指针作为线程参数传给线程,线程里面就可以调用类中的接口。线程中一般会用无限循环,但是很多人喜欢让主线程退出,然后线程被迫退出的方式。或者用ExitThread来退出,这样都不是安全友好的方式。其实我们可以设置一个while的循环条件,外部想要退出的时候把条件置为false,就可以安全退出了。然后在主线程中使用WaitForSingleObject来卡住主线程,等待线程安全执行完了,主线程才结束析构。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:22585次
    • 积分:674
    • 等级:
    • 排名:千里之外
    • 原创:44篇
    • 转载:0篇
    • 译文:0篇
    • 评论:29条
    联系方式

    公司:深信服科技股份有限公司


    所在地:中国-广东省-深圳市


    QQ交流群:165650716


    文章分类