相较于同步非阻塞多线程模型 select模型会更节省时间
同步非阻塞多线程模型中 每一个服务者(waiter)套接字都需要区内核区对应一个客人(client)
当客户端发送请求时
每次都需要将服务者链表的所有成员遍历 来找到与之对应的服务者 这样会很费时间
所以我们定义了一个工具 m_fd 他一个人就可以将你想找的套接字的集合在内核区找 来判断当前哪几个服务者应该响应
原理大概如下
我们把想查找的 套接字放入集合中
再将集合交给select管理
select去内核区找相应的套接字 没找到的就从临时集合中删除 找到的保留
校验 将select处理完的临时集合传回来 判断哪个套接字需要相应
完整代码如下
类声明
#ifndef CTCPMAP_H
#define CTCPMAP_H
#include<list>
#include<winsock2.h>
#include<windows.h>
#include<iostream>
#include<map>
using namespace std;
struct DataInfo
{
char* pszbuf;
int nPacksize;
int noffest;
};
class CTCPmap
{
public:
//构造
CTCPmap();
//析构
~CTCPmap();
//初始化网络
bool InitnewWork();
//销毁网络
void DeleteWork();
//线程函数
static DWORD WINAPI ThreadSelect(LPVOID lpvoid);
static DWORD WINAPI ThreadAccept(LPVOID lpvoid);
static DWORD WINAPI ThreadRecv(LPVOID lpvoid);
//发送数据
bool sendDate(SOCKET sockWaiter,char*szbuf,int nLen);
//接收数据
void recvDate();
private:
//监听者句柄
SOCKET m_soclisten;
//总线程池
std::list<HANDLE> my_Threadpool;
//服务者线程池
std::list<SOCKET> my_ThreadWaiter;
//线程结束标识
bool my_FlagQuit;
//传服务者sockWaiter用到的容器
std::map<DWORD,SOCKET> my_map;
//结构体与套接字的映射
std::map<SOCKET,DataInfo*> m_sdmap;
fd_set m_fd;
};
#endif // CTCPMAP_H
类定义
#include "ctcpmap.h"
CTCPmap::CTCPmap()
{
m_soclisten=0;
my_FlagQuit=true;
FD_ZERO(&m_fd);
}
CTCPmap::~CTCPmap()
{
}
bool CTCPmap::InitnewWork()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
DeleteWork();
return false;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return false;
}
//创建套接字
m_soclisten=socket(AF_INET,SOCK_STREAM,0);
if(m_soclisten==INVALID_SOCKET)
{
DeleteWork();
return false;
}
//绑定
sockaddr_in addrserver;
//绑定IP
addrserver.sin_addr.S_un.S_addr=0;
addrserver.sin_port=htons(8899);
addrserver.sin_family=AF_INET;
if(SOCKET_ERROR==bind(m_soclisten,(sockaddr*)&addrserver,sizeof(addrserver)))
{
DeleteWork();
return false;
}
//监听
if(SOCKET_ERROR==listen(m_soclisten,10))
{
DeleteWork();
return false;
}
//将套接字加入到集合
FD_SET(m_soclisten,&m_fd);
//创建线程
HANDLE hand=CreateThread(0,0,&ThreadSelect,this,0,0);
if(hand)
my_Threadpool.push_back(hand);
// HANDLE hand=CreateThread(0,0,&ThreadAccept,this,0,0);
// if(hand)
// my_Threadpool.push_back(hand);
// hand=CreateThread(0,0,&ThreadRecv,this,0,0);
// //创建服务者线程 加入到线程池
// if(hand)
// my_Threadpool.push_back(hand);
return true;
}
void CTCPmap::DeleteWork()
{
//线程销毁
my_FlagQuit=false;
auto ite=my_Threadpool.begin();
while(ite!=my_Threadpool.end())
{
if(WAIT_TIMEOUT==WaitForSingleObject(*ite,100))
TerminateThread(*ite,-1);
CloseHandle(*ite);
*ite=NULL;
ite++;
}
my_Threadpool.clear();
//关闭句柄
if(m_soclisten)
{
closesocket(m_soclisten);
m_soclisten=0;
}
//卸载服务器
WSACleanup();
}
//将集合交给select线程函数
DWORD WINAPI CTCPmap::ThreadSelect(LPVOID lpvoid)
{
CTCPmap *pthis=(CTCPmap*)lpvoid;
fd_set fdtemp;
TIMEVAL tv;
tv.tv_sec=0;
tv.tv_usec=100;
sockaddr_in addrclient;
int nRecvNum;
int nsize=sizeof(addrclient);
while(pthis->my_FlagQuit)
{
fdtemp =pthis->m_fd;
//将套接字交给集合
select(0,&fdtemp,0,0,&tv);
if(FD_ISSET(pthis->m_soclisten,&fdtemp))
{
//代表有人连接我了
SOCKET sockWaiter=accept(pthis->m_soclisten,(sockaddr*)&addrclient,&nsize);
cout<<"client ip:"<<inet_ntoa(addrclient.sin_addr)<<"accept"<<endl;
//对应失败 跳出 继续循环
if(sockWaiter==INVALID_SOCKET)continue;
//将waiter加入到集合内
FD_SET(sockWaiter,&pthis->m_fd);
}
//校验
for(unsigned int i=1;i<pthis->m_fd.fd_count;i++)
{
if(FD_ISSET(pthis->m_fd.fd_array[i],&fdtemp))
{
DataInfo *pData=pthis->m_sdmap[pthis->m_fd.fd_array[i]];
//代表waiter 有数据来了
if(!pData)
{
pData=new DataInfo;
pData->noffest=0;
pData->nPacksize=0;
pthis->m_sdmap[pthis->m_fd.fd_array[i]]=pData;
//waiter come
nRecvNum=recv(pthis->m_fd.fd_array[i],(char*)&pData->nPacksize,sizeof(int),0);
if(pData->nPacksize>0)
{
pData->pszbuf=new char[pData->nPacksize];
}
}
else
{
nRecvNum=recv(pthis->m_fd.fd_array[i],pData->pszbuf+pData->noffest,pData->nPacksize,0);
if(nRecvNum>0)
{
pData->noffest+=nRecvNum;
pData->nPacksize-=nRecvNum;
if(pData->nPacksize==0)
{
//数据包接受完整
cout<<pData->pszbuf<<endl;
delete []pData->pszbuf;
pData->pszbuf=NULL;
delete [] pData;
pData=NULL;
pthis->m_sdmap[pthis->m_fd.fd_array[i]]=NULL;
}
}
}
}
}
}
return 0;
}
//接收客户端的线程函数
DWORD WINAPI CTCPmap::ThreadAccept(LPVOID lpvoid)
{
CTCPmap *pthis=(CTCPmap*)lpvoid;
sockaddr_in addrclient;
int nsize=sizeof(addrclient);
u_long iMode=1;
while(pthis->my_FlagQuit)
{
//店长接受客人 分配给服务员 --接受链接--accept();
//服务者与他的客人对应起来
SOCKET sockWaiter=accept(pthis->m_soclisten,(sockaddr*)&addrclient,&nsize);
cout<<"client ip:"<<inet_ntoa(addrclient.sin_addr)<<"accept"<<endl;
//对应失败 跳出 继续循环
if(sockWaiter==INVALID_SOCKET)continue;
//对应成功 线程池添加了一个服务者线程
//将Waiter 介入到链表 将waiter设置为非阻塞
ioctlsocket(sockWaiter,FIONBIO,&iMode);
//将服务者套接字加入到集合
//FD_SET(sockWaiter,&(pthis->m_fd));
pthis->my_ThreadWaiter.push_back(sockWaiter);
}
return 0;
}
DWORD WINAPI CTCPmap::ThreadRecv(LPVOID lpvoid)
{
CTCPmap *pthis=(CTCPmap*)lpvoid;
while(pthis->my_FlagQuit)
{
// 接受
pthis->recvDate();
Sleep(10);
}
return 0;
}
void CTCPmap::recvDate()//接收函数
{
// 服务员等客人说话 --recv();
char *pszbuf=NULL;
//当前位置标识
int offset=0;
//包长度
int nPackSize;
int nRecvNum;
SOCKET sockWaiter;
auto ite=my_ThreadWaiter.begin();
while(ite!=my_ThreadWaiter.end())
{
sockWaiter=*ite;
//jieshoubaodaxiao
nRecvNum=recv(sockWaiter,(char*)&nPackSize,sizeof(int ),0);
if(nRecvNum<=0)
{
//判断服务器是否下线
if(WSAGetLastError()==10054)
{
//删除映射
auto ite=my_map.begin();
while(ite!=my_map.end())
{
if(ite->second==sockWaiter)
{
my_map.erase(ite);
closesocket(sockWaiter);
break;
}
//ite++;
}
break;
}
continue;
ite++;
}
//获得接受内容
pszbuf=new char[nPackSize];
while(nPackSize)//看当前剩余的还有多少没接受完
{
nRecvNum=recv(sockWaiter,pszbuf+offset,nPackSize,0);
if(nRecvNum>0)
{
nPackSize-=nRecvNum;
offset+=nRecvNum;
}
}
cout<<"client say:"<<pszbuf<<endl;
sendDate(sockWaiter,pszbuf, nRecvNum);
delete []pszbuf;
pszbuf=NULL;
ite++;
}
}
bool CTCPmap:: sendDate(SOCKET sockWaiter,char*szbuf,int nLen)
{
if(sockWaiter==INVALID_SOCKET||!szbuf||nLen<=0)
return false;
if(send(sockWaiter,(char*)&nLen,sizeof(int ),0)<=0)
return false;
if(send(sockWaiter,szbuf,nLen,0)<=0)
return false;
int nRecvNum;
int nRecvNuumSIze=sizeof(szbuf);
while(my_FlagQuit)
{
cin>>szbuf;
send(sockWaiter,(char*)&nRecvNuumSIze,sizeof(int),0);
send(sockWaiter,szbuf,sizeof(szbuf),0);
//recv()
nRecvNum= recv(sockWaiter,szbuf,sizeof(szbuf),0);
if(sockWaiter>0)
{
cout<<"fuwuqi say:"<<szbuf<<endl;
}
}
return true;
}
主函数
#include <QCoreApplication>
#include<winsock2.h>
#include<iostream>
#include"ctcpmap.h"
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
/*
*1.选择种类 --WSAStartup();
*2.雇店长 --创建套接字 socket();
*3.找地 --绑定 --bind();
*4.店长宣传 --监听 --listen();
*5.店长接受客人 分配给服务员 --接受链接--accept();
*6.客人与服务员 服务员等客人说话 --recv();
*7.回复 --send();
*8.下班--closesocket();
*9.关门--WASCleanup();
*
*/
CTCPmap ctcp;
if(ctcp.InitnewWork())
cout<<"initnework sucess"<<endl;
return a.exec();
}