1.选择模型
1.1选择模型介绍
1.2select函数介绍
int select(
__in int nfds,//参数会被忽略
__in_out fd_set* readfds,//检查可读性
__in_out fd_set* writefds,//检查可写性
__in_out fd_set* exceptfds,//检查例外数据
__in const struct timeval* timeout//空指针计数器
);
1.2.1fd_set 结构
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
#define FD_SETSIZE 64
● 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds 集合包括符合下述任何一个条件的套接字:● 如果已完成了对一个非锁定连接调用的处理,连接就会成功。
exceptfds 集合包括符合下述任何一个条件的套接字:● 有带外(Out-of-band,OOB)数据可供读取。
1.2.2timeval结构
1.2.3select函数返回值
select 成功完成后,会在 fdset 结构中返回刚好有未完成的 I/O操作的所有套接字句柄的总量。若超过 timeval 设定的时间,便会返回0。若 select 调用失败,都会返回 SOCKET_ERROR,应该调用 WSAGetLastError 获取错误码!
1.2.4select函数用法
Winsock 提供了下列宏操作,可用来针对 I/O活动,对 fdset 进行处理与检查:
select操作套接字句柄的过程如下:
1.3选择模型的优势与不足
优势:
当完成一次I/O操作经历了两次Windows Sockets函数的调用。例如,当接收对方数据时,第一步,调用selcet()函数等待该套接字的满足条件。第二步,调用recv()函数接收数据。这种结果与一个阻塞模式的套接字上调用recv()函数是一样的。因此,使用select()函数的Windows Sockets程序,其效率可能受损。因为,每一个Windows Sockets I/O调用都会经过该函数,因而会导致严重的CPU额外负担。在CPU使用效率不是关键因素时,这种效率可以接受。但是,当需要高效率时,肯定会产生问题。
1.4代码实现
#pragma once
#include
#include
#pragma comment(lib, "ws2_32.lib")
#define _DEFAULTPORT 1234
#define MAXNUM 10
#define UM_DATA WM_USER + 1
using namespace std;
enum NetType{NT_READ,NT_WRITE};
class CINet
{
public:
CINet(void);
~CINet(void);
public:
//1.初始化网络
bool InitNetWork(HWND hwnd);
//2.卸载网络
void UnInitNetWork();
//3.发送数据
bool SendData(char *szContent,int nLen);
//接收客户端连接
static DWORD WINAPI ThreadAccept(void*);
static DWORD WINAPI ThreadRecv(void*);
//获得本机iP
static long GetValidIp()
{
char szhostname[100] = {0};
hostent *remoteHost = NULL;
in_addr addr;
//1.获得本机的名称
if(!gethostname(szhostname,sizeof(szhostname)))
{
//2.通过名称获得IP地址
remoteHost = gethostbyname(szhostname);
if (remoteHost->h_addrtype == AF_INET)
{
if(remoteHost->h_addr_list[0] != 0)
{
addr.s_addr = *(u_long *) remoteHost->h_addr_list[0];
return addr.s_addr;
}
}
}
return 0;
}
bool SelectSocket(SOCKET sock,NetType ntype);
private:
SOCKET m_socketListen;
SOCKET socketWaiter;
HANDLE m_hThreadAccept;
HANDLE m_hThreadRecv;
bool m_bflagQuit;
HWND m_hWnd;
list m_lstSocket;
//1.定义数组
fd_set fdsets;
SOCKET socketEvent[10];
int m_nEventNum;
};
INet.cpp
#include "stdafx.h"
#include "INet.h"
CINet::CINet(void)
{
m_socketListen = NULL;
m_hThreadAccept = NULL;
m_hThreadRecv = NULL;
m_bflagQuit = false;
m_nEventNum = 0;
//2.初始化数组
FD_ZERO(&fdsets);
}
CINet::~CINet(void)
{
}
bool CINet::InitNetWork(HWND hwnd)
{ //1.yi er san -- 加载库
if(NULL == hwnd)return false;
m_hWnd = hwnd;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
UnInitNetWork();
return false;
}
//2.创建饭店-店长 --创建SOCKET 套接字
m_socketListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP );
if(INVALID_SOCKET == m_socketListen)
{
UnInitNetWork();
return false;
}
//3.穿上衣服(店地址,店名)--绑定 bind
sockaddr_in addr;
int n = htons(1234);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = GetValidIp();
addr.sin_port = htons(_DEFAULTPORT);
if(SOCKET_ERROR == bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
{
UnInitNetWork();
return false;
}
//4.在一楼门口看着 --listen
if(SOCKET_ERROR == listen(m_socketListen,MAXNUM))
{
UnInitNetWork();
return false;
}
//5.接收客户端--线程
m_bflagQuit = true;
m_hThreadAccept = CreateThread(NULL,0,&ThreadAccept,this,0,0);
m_hThreadRecv = CreateThread(NULL,0,&ThreadRecv,this,0,0);
return true;
}
DWORD WINAPI CINet::ThreadAccept(void* lpvoid)
{
CINet *pthis = (CINet *)lpvoid;
u_long argp = 1;
while(pthis->m_bflagQuit)
{
pthis->socketWaiter = accept(pthis->m_socketListen,NULL,NULL);
if(INVALID_SOCKET == pthis->socketWaiter)continue;
char szbuf[100] = "客户端连接成功";
PostMessage(pthis->m_hWnd,UM_DATA,(WPARAM)szbuf,0);
//3.将sock 放入到数组中
FD_SET(pthis->socketWaiter,&pthis->fdsets);
//将socket属性改为非阻塞
// ioctlsocket(pthis->socketWaiter,FIONBIO,&argp);
pthis->m_lstSocket.push_back(pthis->socketWaiter);
}
return 0;
}
DWORD WINAPI CINet::ThreadRecv(void* lpvoid)
{
CINet *pthis = (CINet *)lpvoid;
char szbuf[1024] = {0};
int nRelNum = 0;
list::iterator ite;
while(pthis->m_bflagQuit)
{
ZeroMemory(szbuf,1024);
// ite = pthis->m_lstSocket.begin();
// while(ite != pthis->m_lstSocket.end())
// {
if(pthis->SelectSocket(0,NT_READ))
{
nRelNum = recv(pthis->socketEvent[--pthis->m_nEventNum],szbuf,1024,0);
if(nRelNum >0)
{
SendMessage(pthis->m_hWnd,UM_DATA,(WPARAM)szbuf,0);
}
}
ite++;
}
}
return 0;
}
bool CINet::SelectSocket(SOCKET sock,NetType ntype)
{
TIMEVAL tv;
tv.tv_sec = 0;
tv.tv_usec = 100;
fd_set fdsetTEMP = fdsets;
//2.初始化数组
// FD_ZERO(&fdsets);
// FD_SET(socketWaiter,&fdsets);
//4.将数组交给select 去管理
if(ntype == NT_READ)
{//按顺序逐个查看socket的状态
select(NULL,&fdsets,NULL,NULL,&tv);
}
else if(ntype == NT_WRITE)
{
select(NULL,NULL,&fdsets,NULL,&tv);
}
bool bflag = true;
// list::iterator ite = m_lstSocket.begin();
// int i = 0;
// m_nEventNum = 0;
// while(ite != m_lstSocket.end())
// {
if(0 != FD_ISSET(sock,&fdsets))
{
return false;
}
}
return bflag;
}
void CINet::UnInitNetWork()
{
WSACleanup();
if(m_socketListen)
{
closesocket(m_socketListen);
m_socketListen = NULL;
}
m_bflagQuit = false;
if(m_hThreadAccept)
{
if( WAIT_TIMEOUT == WaitForSingleObject(m_hThreadAccept,100))
{
TerminateThread(m_hThreadAccept,-1);
}
if( WAIT_TIMEOUT == WaitForSingleObject(m_hThreadRecv,100))
{
TerminateThread(m_hThreadRecv,-1);
}
CloseHandle(m_hThreadAccept);
CloseHandle(m_hThreadRecv);
m_hThreadAccept = NULL;
m_hThreadRecv = NULL;
}
}
//3.发送数据
bool CINet::SendData(char *szContent,int nLen)
{
return true;
}
2.异步选择模型(WSAAsyncSelect)
2.1异步选择模型介绍
与Select选择模型的异同
相同点:
他们都可以对Windows套接字应用程序所使用的多个套接字进行有效的管理。
不同点:2.2WSAAsyncSelect 函数结构
int WSAAsyncSelect(
__in SOCKET s,
__in HWND hWnd,
__in unsigned int wMsg,
__in long lEvent
);
关于lEvent的说明:
注意:
2.3WSAAsyncSelect窗口机制
应用程序在一个套接字上成功调用了WSAAsyncSelect之后,会在与hWnd窗口句柄对应的窗口例程中,以Windows消息的形式,接收网络事件通知。LRESULT CALLBACK WindowProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
● hWnd 参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。
步骤:
网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。
这里有一个特殊的宏: WSAGETSELECTERROR,可用它返回高字位包含的错误信息。此时可使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。
2.4优势和不足
优势:
1.该模型的使用方便了在基于消息的Windows环境下开发套接字的应用程序。开发人员可以像处理其他消息一样对网络事件消息进行处理。
2.该模型确保接收所有数据提供了很好的机制。通过注册FD_CLOSE网络事件,从容关闭服务器与客户端的连接保证了数据全部接收。
不足:
1.该模型局限在,他基于Windows的消息机制,必须在应用程序中创建窗口。当然,在开发中可以根据具体情况是否显示该窗口。MFC的CSocketWnd类就是用来创建一个不显示的窗口,并在该类中声明接收网络事件消息处理函数。
2.由于调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞状态。当应用程序为接收到网络事件调用相应函数时,未必能够成功返回。这无疑增加了开发人员使用该模型的难度。对于这一点可以从MFC CSocket类的Accept()、Receive()和Send()函数的实现得到验证。
2.5代码实现
3.1事件选择IO介绍
WSAEventSelect模型是Windows Sockets提供的另外一个有用的异步I/O模型。该模型允许一个或多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSlect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。和 WSAAsyncSelect 模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知;最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递到一个窗口例程。从应用程序接收网络事件通知的方式来说,WSAEventSelect模型与WSAAsyncSelect模型都是被动的,当网络事件发生时,系统通知应用程序。然而select模型是主动的,应用程序主动调用该函数看是否发生了网络事件。
3.2WSAEventSelect函数介绍
int WSAEventSelect(
__in SOCKET s,
__in WSAEVENT hEventObject,
__in long lNetworkEvents
);
WSAEventSelect 函数的返回值很简单,就是一个创建好的事件对象句柄,接下来必须将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型(FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等)。
WSAEventSelect 函数的参数
● s 参数代表感兴趣的套接字。
● hEventObject 参数指定要与套接字关联在一起的事件对象—用WSACreateEvent 取得的那一个。
● lNetworkEvents 参数则对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。、
会用到的函数:
WSAEventSelect()
WSACreateEvent()
WSAResetEvent()
WSAWaitForMultipleEvents()
3.3WSAEventSelect优势和不足
优势:
可以在一个非窗口的Windows Sockets程序中,实现多个套接字的管理。
不足:2.由于使用该模型开发套接字应用程序需要调用几个相关函数才能完成。因此,该模型增加了开发的难度,增加了开发人员的编码量。从这个角度讲,该模型不如WSAAysnceSelect模型方便。
3.4代码实现
WSAEventSelect.h
#pragma once
#include
WSAEventSelect.cpp
#include "stdafx.h"
#include "WSAEventSelect.h"
#include
CWSAEventSelect::CWSAEventSelect(void)
{
m_nEventNum = 0;
}
CWSAEventSelect::~CWSAEventSelect(void)
{
}
bool CWSAEventSelect::InitNetWork(HWND hwnd)
{ //1.yi er san -- 加载库
if(NULL == hwnd)return false;
m_hWnd = hwnd;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
UnInitNetWork();
return false;
}
//2.创建饭店-店长 --创建SOCKET 套接字
m_socketListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP );
if(INVALID_SOCKET == m_socketListen)
{
UnInitNetWork();
return false;
}
//3.穿上衣服(店地址,店名)--绑定 bind
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = GetValidIp();
addr.sin_port = htons(_DEFAULTPORT);
if(SOCKET_ERROR == bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
{
UnInitNetWork();
return false;
}
//4.在一楼门口看着 --listen
if(SOCKET_ERROR == listen(m_socketListen,MAXNUM))
{
UnInitNetWork();
return false;
}
//5.注册信息--socket 网络事件,事件
WSAEVENT we = WSACreateEvent();
if(!WSAEventSelect(m_socketListen,we,FD_ACCEPT))
{
m_aryEvent[m_nEventNum] = we;
m_arySocket[m_nEventNum] = m_socketListen;
m_nEventNum++;
}
_beginthreadex(NULL,0,&ThreadProc,this,0,NULL);
return true;
}
unsigned _stdcall CWSAEventSelect::ThreadProc( void * lpvoid )
{
CWSAEventSelect *pthis = ( CWSAEventSelect *)lpvoid;
WSANETWORKEVENTS wwe;
while(1)
{
int nindex = WSAWaitForMultipleEvents(pthis->m_nEventNum,pthis->m_aryEvent,FALSE,WSA_INFINITE,TRUE);
if(WSA_WAIT_FAILED == nindex)
{
continue;
}
nindex -= WSA_WAIT_EVENT_0;
//判断当前socket 发生什么网络事件
if(!WSAEnumNetworkEvents(pthis->m_arySocket[nindex],pthis->m_aryEvent[nindex],&wwe))
{
if( wwe.lNetworkEvents & FD_ACCEPT)
{
SOCKET socketWaiter = accept(pthis->m_arySocket[nindex],NULL,NULL);
if(INVALID_SOCKET == socketWaiter)continue;
WSAEVENT we = WSACreateEvent();
if(!WSAEventSelect(socketWaiter,we,FD_READ|FD_WRITE|FD_CLOSE))
{
pthis->m_aryEvent[pthis->m_nEventNum] = we;
pthis->m_arySocket[pthis->m_nEventNum] =socketWaiter;
pthis->m_nEventNum++;
}
}
if(wwe.lNetworkEvents & FD_READ)
{
char szbuf[1024] = {0};
int nres = recv(pthis->m_arySocket[nindex],szbuf,1024,0);
if(nres > 0)
{
SendMessage(pthis->m_hWnd,UM_DATA,(WPARAM)szbuf,0);
}
}
}
}
return 0;
}
void CWSAEventSelect::UnInitNetWork()
{
}
//3.发送数据
bool CWSAEventSelect::SendData(char *szContent,int nLen)
{
return true;
}
4.重叠IO(Overlapped I/O)
在 Winsock 中,重叠 I/O(Overlapped I/O)模型能达到更佳的系统性能,高于之前讲过的三种。重叠模型的基本设计原理便是让应用程序使用一个重叠的数据结构(WSAOVERLAPPED),一次投递一个或多个 Winsock I/O 请求。针对这些提交的请求,在它们完成之后,我们的应用程序会收到通知,于是我们就可以对数据进行处理了。套接字的重叠I/O模型才是真正意义上的异步I/O模型。在应用程序中调用输入或输出函数后,立即返回。当I/O操作完成后,系统通知应用程序。利用该模型,应用程序在调用输入或者输出函数后,只需要等待I/O操作完成的通知即可。从发送和接收数据操作的角度看,前3个模型不是异步模型。因为在这3个模型中,I/O操作还是同步的。重叠I/O模型才是真正意义上的异步模型。
4.1重叠IO的实现
1.要想在一个套接字上使用重叠 I/O 模型,首先必须使用 WSA_FLAG_OVERLAPPED 这个标志,创建一个套接字。例如:
SOCKET s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
2.创建套接字的时候,假如使用的是 socket 函数,那么会默认设置 WSA_FLAG_OVERLAPPED 标志。
4.2重叠IO的编程步骤
4.3代码实现:
WSASelect.h
#pragma once
#include "MyWnd.h"
#define _DEFAULTPORT 1234
#define MAXNUM 10
#define UM_DATA WM_USER + 1
#define UM_ACCEPT WM_USER + 2
#define UM_TALKING WM_USER + 3
class CWSASelect
{
public:
CWSASelect(void);
~CWSASelect(void);
public:
//1.初始化网络
bool InitNetWork(HWND hwnd);
//2.卸载网络
void UnInitNetWork();
//3.发送数据
bool SendData(char *szContent,int nLen);
void OnAccept(WPARAM wparam,LPARAM lparam);
void OnTalking(WPARAM wparam,LPARAM lparam);
//获得本机iP
static long GetValidIp()
{
char szhostname[100] = {0};
hostent *remoteHost = NULL;
in_addr addr;
//1.获得本机的名称
if(!gethostname(szhostname,sizeof(szhostname)))
{
//2.通过名称获得IP地址
remoteHost = gethostbyname(szhostname);
if (remoteHost->h_addrtype == AF_INET)
{
if(remoteHost->h_addr_list[0] != 0)
{
addr.s_addr = *(u_long *) remoteHost->h_addr_list[0];
return addr.s_addr;
}
}
}
return 0;
}
private:
HWND m_hWnd;
SOCKET m_socketListen;
// CMyWnd m_pWnd;
};
WSASelect.cpp
#include "stdafx.h"
#include "wsaselect.h"
CWSASelect::CWSASelect(void)
{
m_socketListen = NULL;
}
CWSASelect::~CWSASelect(void)
{
}
bool CWSASelect::InitNetWork(HWND hwnd)
{ //1.yi er san -- 加载库
if(NULL == hwnd)return false;
m_hWnd = hwnd;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
UnInitNetWork();
return false;
}
//2.创建饭店-店长 --创建SOCKET 套接字
m_socketListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP );
if(INVALID_SOCKET == m_socketListen)
{
UnInitNetWork();
return false;
}
//3.穿上衣服(店地址,店名)--绑定 bind
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = GetValidIp();
addr.sin_port = htons(_DEFAULTPORT);
if(SOCKET_ERROR == bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
{
UnInitNetWork();
return false;
}
//4.在一楼门口看着 --listen
if(SOCKET_ERROR == listen(m_socketListen,MAXNUM))
{
UnInitNetWork();
return false;
}
//创建窗口--处理消息
if(!CMyWnd::CreateMyObject()->Create(NULL,"MyWnd"))
{
UnInitNetWork();
return false;
}
CMyWnd::CreateMyObject()->SetServer(this);
//5.注册信息--socket 网络事件,消息
WSAAsyncSelect(m_socketListen,CMyWnd::CreateMyObject()->m_hWnd,UM_ACCEPT,FD_ACCEPT);
return true;
}
void CWSASelect::OnAccept(WPARAM wparam,LPARAM lparam)
{
//接收连接
SOCKET SocketWaiter = accept(m_socketListen,NULL,NULL);
//再次向windows注册
WSAAsyncSelect(SocketWaiter,CMyWnd::CreateMyObject()->m_hWnd,UM_TALKING,FD_CLOSE|FD_READ |FD_WRITE);
}
void CWSASelect::OnTalking(WPARAM wparam,LPARAM lparam)
{
SOCKET sock = ( SOCKET )wparam;
//判断当前发生什么网络事件--lparam
//谁发生了网络是事件--wparam
char szbuf[1024] = {0};
switch (lparam)
{
case FD_READ:
{
int nres = recv(sock,szbuf,1024,0);
if(nres > 0)
{
SendMessage(m_hWnd,UM_DATA,(WPARAM)szbuf,0);
}
}
break;
case FD_WRITE: //1.accept //2.connect //3.send发送失败
TRACE("FD_WRITE\n");
break;
case FD_CLOSE:
TRACE("FD_CLOSE\n");
break;
default:
break;
}
// recv(,)
}
void CWSASelect::UnInitNetWork()
{
WSACleanup();
if(m_socketListen)
{
closesocket(m_socketListen);
m_socketListen = NULL;
}
}
//3.发送数据
bool CWSASelect::SendData(char *szContent,int nLen)
{
return true;
}
3事件选择(WSAEventSelect)
5.完成端口
5.1完成端口介绍
完成端口是Win32一种核心对象。利用完成端口模型,套接字应用程序能够管理数百个甚至上千个套接字。应用程序创建一个Win32完成端口对象,通过指定一定数量的服务线程,为已经完成的重叠I/O操作提供服务。该模型往往可以达到最好的系统性能。完成端口是真正意义上的异步模型。该模型解决了“one-thread-per-client”的问题。当应用程序需要管理成百上千个套接字,并且希望随着系统安装的CPU数量的增加,应用程序的性能得到提升时,I/O完成端口模型是最好的选择。完成端口目标是实现高效的服务器程序,他克服了并发模型的不足。其方法一是为完成端口指定并发线程的数量;二是在初始化套接字时创建一定数量的服务线程,即所谓的线程池。当客户端请求到来时,这些线程立即为之服务。完成端口的理论基础是并行运行的线程数量必须有一个上限。这个数值就是CPU的个数。如果一台机器有两个CPU,那么多于两个可运行的线程就没有意义了。因为一旦运行线程数目超出CPU数目,系统就不得花费时间来进行线程上下文的切换,这将浪费宝贵的CPU周期。完成端口并行运行的线程数目和应用程序创建的线程数量是两个不同的概念。服务器应用程序需要创建多少个服务器线程,一般规律是CPU数目乘以2.例如,单CPU的机器,套接字应用程序应该创建2个线程的线程池。接下来的问题是,完成端口如何实现对线程池的有效管理,使这些服务线程高效运行起来。当系统完成I/O操作后,向服务器完成端口发送I/O completion packet。这个过程发生在系统内部,对应用程序是不可见的。在应用程序方面,此时线程池中的线程在完成端口上排队等待I/O操作完成。如果在完成端口上没有接收到I/O completion packet时,这些线程处于睡吧状态。当I/O completion packet 被送到完成端口时,这些线程按照后进先出(LIFO Last-in-First-out)方式被唤醒。完成端口之所以采用这种方式,其目的是为了提高性能。例如,有3个线程在完成端口上等待,当一个I/O completion packet到达后,队中最后一个线程被唤醒。该线程为客户端完成服务后,继续在完成端口上等待。如果 此时又有一个I/O completion packet 到达完成端口,则该线程线程又被唤醒,为该客户端提供服务。如果完成端口不采用LIFO方式,完成端口唤醒另外一个线程,则必然要进行线程之间的上下文切换。通过使用LIFO方式,还可以使得不被唤醒的线程内存资源从缓存中清除。在前面讲到的,应用程序需要创建一个线程池,在完成端口上等待。线程池中的线程数目一定大于完成端口并发运行的线程数目,似乎应用程序创建了多余的线程,其实不然,之所以这样做是因为保证CPU尽可能的忙碌。例如,在一台单CPU的计算机上,创建一个完成端口的应用程序,为其制定并发线程数目为1.在应用程序中,创建2个线程在完成端口上等待。假如在一次为客户端服务时,被唤醒的线程因调用Sleep()之类的函数而处于阻塞状态,此时,另外一个I/O completion packet 被发送到完成端口上。完成端口会唤醒另外一个线程为该客户提供服务。这就是线程池中线程数目要大于完成端口指定的并发线程数量的原因。根据上面分析,在某些情况下,完成端口并行运行的线程数量会超过指定数量。但是,当服务线程为客户端完成服务后,在完成端口等待时,并发的线程数量还会下降。总之,完成端口为套接字应用程序管理线程池,避免反复创建线程的开销,同时,根据CPU的数量决定并发线程的数量,减少线程的调度,从而提高服务器程序性能。
5.2与重叠IO的比较
重叠I/O模型和完成端口模型相同点在于他们都是异步模型,都可以使得套接字应用程序性能得到改善。重叠I/O模型与完成端口模型相比存在以下不足:在事件通知方式的套接字应用程序中,使用WSAWaitForMultipleEvents()函数,应用程序最多等待WSA_MAXIMUM_WAIT_EVENTS个事件对象。在Win32 SDK 中,该值为64.作为一个服务器程序,该函数限制了服务器为之提供服务的客户端的数量。应用程序必须维护一个“事件—套接字—重叠结构”关系表格。根据发生的事件对象,确定套接字和重叠结构。一个套接字可以关联一个、两个或多个事件对象,而事件对象与重叠结构之间保持着一一对应的关系。应用程序管理这个关系表格时,如果出现一点疏漏,就会造成严重的后果。
完成端口优点
完成端口实际上一个通知队列。当某项I/O操作完成时,由操作系统向完成端口发送通知包。一方面,这些通知包在完成端口上排队,按照FIFO(First_in_First_out)方式被提取。另一方面,在完成端口上一定数量的现场等待接收通知包,这些线程按照LIFO方式被唤醒。套接字在被创建后,可以在任何时候与某个完成端口进行关联。对发起重叠操作的数量不存在限制。支持scalable架构。Scalable系统是指随着RAM、磁盘空间或者CPU个数的增加而能够提升应用程序效能的一种系统。
5.3CreateCompletionPort函数
使用这种模型之前,首先要创建一个 I/O 完成端口对象,用它面向任意数量的套接字句柄,管理多个 I/O 请求。要做到这一点,需要调用 CreateCompletionPort 函数,其定义如下:
HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle,
__in HANDLE ExistingCompletionPort,
__in ULONG_PTR CompletionKey,
__in DWORD NumberOfConcurrentThreads
);
要注意该函数有两个功能:
理想情况下,我们希望每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程“场景”(即线程上下文)切换。若将该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个工作者线程!可用下述代码创建一个 I/O 完成端口:HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)。成功创建一个完成端口后,便可开始将套接字句柄与其关联到一起。但在关联套接字之前,首先必须创建一个或多个“工作者线程”,以便在 I/O 请求投递给完成端口后,为完成端口提供服务。在这个时候,大家或许会觉得奇怪,到底应创建多少个线程,以便为完成端口提供服务呢?在此,要记住的一点,我们调用 CreateIoComletionPort 时指定的并发线程数量,与打算创建的工作者线程数量相比,它们代表的不是同一件事情。CreateIoCompletionPort 函数的 NumberOfConcurrentThreads 参数明确指示系统:在一个完成端口上,一次只允许 n 个工作者线程运行。假如在完成端口上创建的工作者线程数量超出 n 个,那么在同一时刻,最多只允许n个线程运行。但实际上,在一段较短的时间内,系统有可能超过这个值,但很快便会把它减少至事先在 CreateIoCompletionPort 函数中设定的值。那么,为何实际创建的工作者线程数量有时要比 CreateIoCompletionPort 函数设定的多一些呢?这样做有必要吗?这主要取决于应用程序的总体设计情况。假定我们的某个工作者线程调用了一个函数,比如 Sleep 或 WaitForSingleObject,进入了暂停(锁定或挂起)状态,那么允许另一个线程代替它的位置。换言之,我们希望随时都能执行尽可能多的线程;当然,最大的线程数量是事先在 CreateIoCompletonPort 调用里设定好的。这样一来,假如事先预计到自己的线程有可能暂时处于停顿状态,那么最好能够创建比CreateIoCompletonPort 的 NumberOfConcurrentThreads 参数的值多的线程,以便到时候充分发挥系统的潜力。
5.4完成端口步骤
5.5完成端口代码
IOServer.h
#pragma once
#include
using namespace std;
#define _DEFAULTPORT 1234
#define MAXNUM 10
#define UM_DATA WM_USER + 1
enum NetType{NT_UNKOWN,NT_ACCEPT,NT_READ,NT_WRITE};
struct Node
{
Node()
{
m_olp.hEvent = NULL;
m_socket = NULL;
m_ntype = NT_UNKOWN;
ZeroMemory(m_szbuf,sizeof(m_szbuf));
}
OVERLAPPED m_olp; //事件 --通知
SOCKET m_socket; //发送网络事件的socket
NetType m_ntype; //发送的网络事件
char m_szbuf[1024]; //接收数据的缓冲区
};
class CIOServer
{
public:
CIOServer(void);
~CIOServer(void);
public:
//1.初始化网络
bool InitNetWork(HWND hwnd);
//2.卸载网络
void UnInitNetWork();
//3.发送数据
bool SendData(char *szContent,int nLen);
//4.投递接收连接的请求
bool PostAccept();
//5.投递接收数据的请求
bool PostRecv(Node *pNode);
//线程
static unsigned __stdcall ThreadProc( void* lpvoid );
//获得本机iP
static long GetValidIp()
{
char szhostname[100] = {0};
hostent *remoteHost = NULL;
in_addr addr;
//1.获得本机的名称
if(!gethostname(szhostname,sizeof(szhostname)))
{
//2.通过名称获得IP地址
remoteHost = gethostbyname(szhostname);
if (remoteHost->h_addrtype == AF_INET)
{
if(remoteHost->h_addr_list[0] != 0)
{
addr.s_addr = *(u_long *) remoteHost->h_addr_list[0];
return addr.s_addr;
}
}
}
return 0;
}
private:
HWND m_hWnd;
SOCKET m_socketListen;
HANDLE m_hIoCom;
list m_lstHandle;
list m_lstNode;
bool m_bflagQuit;
};
IOServer.cpp
#include "stdafx.h"
#include "IOServer.h"
#include
#include < Mswsock.h >
CIOServer::CIOServer(void)
{
m_bflagQuit = false;
}
CIOServer::~CIOServer(void)
{
}
bool CIOServer::InitNetWork(HWND hwnd)
{ //1.yi er san -- 加载库
if(NULL == hwnd)return false;
m_hWnd = hwnd;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
UnInitNetWork();
return false;
}
//2.创建饭店-店长 --创建SOCKET 套接字
m_socketListen = WSASocket (AF_INET,SOCK_STREAM,IPPROTO_TCP,0,0,WSA_FLAG_OVERLAPPED);
if(INVALID_SOCKET == m_socketListen)
{
UnInitNetWork();
return false;
}
//3.穿上衣服(店地址,店名)--绑定 bind
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = GetValidIp();
addr.sin_port = htons(_DEFAULTPORT);
if(SOCKET_ERROR == bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
{
UnInitNetWork();
return false;
}
//4.在一楼门口看着 --listen
if(SOCKET_ERROR == listen(m_socketListen,MAXNUM))
{
UnInitNetWork();
return false;
}
//5.主动投递接收连接请求
for(int i =0; i m_bflagQuit)
{
//观察完成端口的状态
bflag = GetQueuedCompletionStatus(pthis->m_hIoCom,&dwNumberOfBytes,(PULONG_PTR)&sock,(LPOVERLAPPED*)&pNode,WSA_INFINITE);
if(!bflag)
continue;
//处理数据
if(pNode && sock)
{
switch (pNode->m_ntype)
{
case NT_ACCEPT:
{
//将waiter交给完成端口管理
CreateIoCompletionPort((HANDLE)pNode->m_socket,pthis->m_hIoCom,pNode->m_socket,0);
//投递接收数据的请求
pthis->PostRecv(pNode);
//接收连接的请求
pthis->PostAccept();
}
break;
case NT_READ:
{
SendMessage(pthis->m_hWnd,UM_DATA,(WPARAM)pNode->m_szbuf,0);
//投递接收数据的请求
pthis->PostRecv(pNode);
}
break;
case NT_WRITE:
break;
default:
break;
}
}
}
return 0;
}
bool CIOServer::PostRecv(Node *pNode)
{
WSABUF wb;
DWORD dwNumberOfBytesRecvd;
DWORD dwFlags = false;
wb.buf = pNode->m_szbuf;
wb.len = sizeof(pNode->m_szbuf);
pNode->m_ntype = NT_READ;
if(WSARecv(pNode->m_socket,&wb,1,&dwNumberOfBytesRecvd,&dwFlags,&pNode->m_olp,NULL))
{
if(WSAGetLastError() != WSA_IO_PENDING)
{
return false;
}
}
return true;
}
bool CIOServer::PostAccept()
{
SOCKET socketWaiter = WSASocket (AF_INET,SOCK_STREAM,IPPROTO_TCP,0,0,WSA_FLAG_OVERLAPPED);
DWORD dwBytesReceived;
Node *pNewNode = new Node;
pNewNode->m_socket = socketWaiter;
pNewNode->m_olp.hEvent = WSACreateEvent();
pNewNode->m_ntype = NT_ACCEPT;
if(!AcceptEx(m_socketListen,socketWaiter,pNewNode->m_szbuf,0,
sizeof(sockaddr_in)+ 16, sizeof(sockaddr_in)+ 16,&dwBytesReceived,&pNewNode->m_olp))
{
if(WSAGetLastError() != ERROR_IO_PENDING)
{
return false;
}
}
m_lstNode.push_back(pNewNode);
return true;
}
void CIOServer::UnInitNetWork()
{
m_bflagQuit = false;
//向完成端口发退出通知
int nNum = m_lstHandle.size();
while(nNum-- >0)
{
PostQueuedCompletionStatus(m_hIoCom,NULL,NULL,NULL);
}
Sleep(500);
list::iterator ite = m_lstHandle.begin();
while(ite != m_lstHandle.end())
{
if(WAIT_TIMEOUT == WaitForSingleObject((*ite),100))
{
TerminateThread(*ite,-1);
}
CloseHandle(*ite);
*ite = NULL;
ite++;
}
list::iterator iteNode = m_lstNode.begin();
while(iteNode != m_lstNode.end())
{
if(*iteNode)
{
delete *iteNode;
*iteNode = NULL;
}
iteNode++;
}
WSACleanup();
//关闭socket
if(m_socketListen)
{
closesocket(m_socketListen);
m_socketListen= NULL;
}
}
//3.发送数据
bool CIOServer::SendData(char *szContent,int nLen)
{
return true;
}