windows套接字I/O模型

1,select模型
利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。
目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫进入阻塞状态。

select参数和返回值意义如下:
int select (
IN int nfds, //0,无意义
IN OUT fd_set* readfds, //检查可读性
IN OUT fd_set* writefds, //检查可写性
IN OUT fd_set* exceptfds, //例外数据
IN const struct timeval* timeout); //函数的返回时间

struct timeval {
long tv_sec; //秒
long tv_usec; //毫秒
};
select返回fd_set中可用的套接字个数。

fd_set是一个SOCKET队列,以下宏可以对该队列进行操作:
FD_CLR( s, *set) 从队列set删除句柄s;
FD_ISSET( s, *set) 检查句柄s是否存在与队列set中;
FD_SET( s, *set )把句柄s添加到队列set中;
FD_ZERO( *set ) 把set队列初始化成空队列.

如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中,然后调用select。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,然后利用FD_ISSET宏只要检查该套接字句柄是否还存在于可读性队列中,就可以知道到底有没有数据需要接收了。

WSADATA wd;
::WSAStartup(MAKEWORD(2,2), &wd);

sockaddr_in addr;
addr.sin_family            = AF_INET;
addr.sin_port              = htons(5000);
addr.sin_addr.s_addr       = inet_addr("127.0.0.1");

SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
::bind(s, (const sockaddr*)&addr, sizeof(addr));

sockaddr_in client_addr;
int fromlen = sizeof(sockaddr_in);


fd_set fs;            //定义fd_set队列
timeval tm;            //timeval时间结构体
tm.tv_sec = 3;        //3秒
tm.tv_usec = 0;

//收数据
while(1)
{
    FD_ZERO(&fs);        //清空队列
    FD_SET(s, &fs);        //将套接字放入队列

    int i = ::select(0, &fs, NULL, NULL, &tm);
    cout<<i<<endl;        //输出可用套接字个数
    if(0 == i)
        continue;        //无可读数据的套接字,继续循环

    char cBuffer[1024] = {0};
    ::recvfrom(s, cBuffer, 1024, 0, (sockaddr*)&client_addr, &fromlen);
    cout<<inet_ntoa(client_addr.sin_addr)<<endl<<cBuffer<<endl;
}

::closesocket(s);
::WSACleanup();

2,WSAAsyncSelect模型
通过WSAAsyncSelect函数设定当指定套接字收到请求时,向指定窗口发送指定的消息,自动将套接口设置为非阻塞模式,需要创建窗口接收消息。

int WSAAsyncSelect ( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );
s 标识一个需要事件通知的套接口的描述符.
hWnd 标识一个在网络事件发生时需要接收消息的窗口句柄.
wMsg 在网络事件发生时要接收的消息.
lEvent 位屏蔽码,用于指明应用程序感兴趣的网络事件集合.

FD_READ 欲接收读准备好的通知.
FD_WRITE 欲接收写准备好的通知.
FD_OOB 欲接收带边数据到达的通知.
FD_ACCEPT 欲接收将要连接的通知.
FD_CONNECT 欲接收已连接好的通知.
FD_CLOSE 欲接收套接口关闭的通知.

include

include

include “tchar.h”

include

include

pragma comment (lib, “Ws2_32.lib”)

using namespace std;

//定义消息

define WM_MYSOCK WM_USER+1

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

SOCKET NonConnect(HWND hwnd)
{
WSADATA wd;
::WSAStartup(MAKEWORD(2,2), &wd);

sockaddr_in addr;
addr.sin_family            = AF_INET;
addr.sin_port              = htons(5000);
addr.sin_addr.s_addr       = inet_addr("127.0.0.1");

SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
::bind(s, (const sockaddr*)&addr, sizeof(addr));

sockaddr_in client_addr;
int fromlen = sizeof(sockaddr_in);

//当s中有可读数据时,向hwnd发送消息WM_MYSOCK
::WSAAsyncSelect(s, hwnd, WM_MYSOCK, FD_READ);

return s;

}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
const TCHAR wcAppName[] = L”CharConversion”;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = wcAppName ;

if (!RegisterClass (&wndclass))
{
    MessageBox (    NULL, TEXT ("This program requires Windows NT!"),
        wcAppName, MB_ICONERROR) ;
    return 0 ;
}

HWND hwnd;
hwnd = CreateWindow (wcAppName, L"TestCharacters",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    300, 200,
    NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, nShowCmd) ;
UpdateWindow (hwnd) ;

//创建socket
SOCKET s = NonConnect(hwnd);

MSG msg;
while (GetMessage (&msg, NULL, 0, 0))
{
    TranslateMessage (&msg) ;
    DispatchMessage (&msg) ;
}

//收尾工作
::closesocket(s);
::WSACleanup();

return (int)msg.wParam ;

}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
return 0;

case WM_PAINT:
    LPPAINTSTRUCT ps;
    BeginPaint (hwnd, ps) ;
    EndPaint (hwnd, ps) ;
    return 0;

case WM_MYSOCK:
    //处理消息
    PostQuitMessage(0) ;
    return 0;

case WM_DESTROY:
    PostQuitMessage(0) ;
    return 0 ;
}

return DefWindowProc (hwnd, msg, wParam, lParam) ;

}

3,WSAEventSelect模型
与WSAAsyncSelect模型类似,因为它也接收FD_XXX类型的网络事件。
1、利用WSACreateEvent创建event。
2、利用WSAEventSelect关联套接字和event,并指定接收的网络事件类型。
3、将event和套接字放入分别放入数组中,利用WSAWaitForMultipleEvents等待event数组中的event受信,返回受信event索引。
4、利用WSAEnumNetworkEvents读取对应socket信息,并重置对应event。
5、从上一步的信息中判断网络事件,进行操作。

WSACreateEvent创建的event,初始为非受信,人工重置。
数组最多可容纳WSA_MAXIMUM_WAIT_EVENTS(64)个socket或event。
WSAWaitForMultipleEvents(
IN DWORD cEvents, //数组元素个数
IN const WSAEVENT FAR * lphEvents, //event数组
IN BOOL fWaitAll, //FALSE:有一个event受信就返回,TRUE:全部event受信才返回
IN DWORD dwTimeout, //timeout,单位为毫秒,INFINITE表示无限
IN BOOL fAlertable //FALSE
);
WSAEnumNetworkEvents第二个参数可重置event至非受信状态,也可不传入,用WSAResetEvent设置。
WSANETWORKEVENTS结构体中,lNetworkEvents表示网络事件类型,iErrorCode是一个错误代码数组,例如FD_READ对应的错误代码是iErrorCode[FD_READ_BIT],若该值为非0,则FD_READ出错。

WSADATA wd;
::WSAStartup(MAKEWORD(2,2), &wd);

sockaddr_in addr;
addr.sin_family            = AF_INET;
addr.sin_port              = htons(5000);
addr.sin_addr.s_addr       = inet_addr("127.0.0.1");

SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
::bind(s, (const sockaddr*)&addr, sizeof(addr));

sockaddr_in client_addr;
int fromlen = sizeof(sockaddr_in);

//创建Event,类型为人工重置,初始状态为未受信
WSAEVENT wEvent = ::WSACreateEvent();

//创建套接字数组和event数组,并将以创建的套接字和event放入数组
SOCKET        socketArray[WSA_MAXIMUM_WAIT_EVENTS] = {NULL};
WSAEVENT    eventArray[WSA_MAXIMUM_WAIT_EVENTS] = {NULL};
DWORD        dwSocketNum        = 0;
socketArray[dwSocketNum]    = s;
eventArray[dwSocketNum]        = wEvent;
//关联套接字和event
::WSAEventSelect(s, wEvent, FD_READ);
++dwSocketNum;

while(1)
{
    //等待event数组中的event受信
    DWORD dwIndex = ::WSAWaitForMultipleEvents(dwSocketNum, eventArray, FALSE, INFINITE, FALSE);

    //受信后读取对应socket的信息,并重置对应event
    WSANETWORKEVENTS wNetworkEvent;
    ::WSAEnumNetworkEvents(socketArray[dwIndex], eventArray[dwIndex], &wNetworkEvent);

    //若网络事件匹配,则收数据
    if((FD_READ == wNetworkEvent.lNetworkEvents) && (0 == wNetworkEvent.iErrorCode[FD_READ_BIT]))
    {
        char cBuffer[1024] = {0};
        ::recvfrom(s, cBuffer, 1024, 0, (sockaddr*)&client_addr, &fromlen);
        cout<<inet_ntoa(client_addr.sin_addr)<<endl<<cBuffer<<endl;
    }
}

//关闭event句柄
::WSACloseEvent(wEvent);
::closesocket(s);
::WSACleanup();

4,OverLapped事件模型
1、创建SOCKET.
2、创建event,与WSAOVERLAPPED结构体成员关联。
3、创建buffer,与WSABUF关联。
4、使用类似WSARecvFrom这样的函数,传入WSAOVERLAPPED结构体,函数立即返回。若IO完成,则指定WSAOVERLAPPED中的event状态变为受信。
5、使用WSAWaitForMultipleEvents等待event状态,若受信则表示IO完成,用WSAResetEvent手动设为非受信。
6、使用WSAGetOverlappedResult判断IO完成状态。

为了使用重叠模型,用WSASocket创建SOCKET的时候需要指定WSA_FLAG_OVERLAPPED,用socket()创建会默认指定该标志。
使用重叠模型时,需要使用此类函数,例如WSASend 、WSARecv ,传入WSAOVERLAPPED结构体参数,并且这些函数会立即返回,即使SOCKET为阻塞类型也不会阻塞。如果是OverLapped事件模型,最后一个参数为NULL。
WSAOVERLAPPED结构体的hEvent成员需要与event关联
WSABUF结构体的buf成员为数据buffer,len成员为buffer大小。
WSAGetOverlappedResult(
IN SOCKET s, //SOCKET
IN LPWSAOVERLAPPED lpOverlapped, //WSAOVERLAPPED
OUT LPDWORD lpcbTransfer, //传输字节数,若为0,在面向连接的socket中,表示对方已断开连接
IN BOOL fWait, //TRUE:除非重叠操作完成,否则函数不会返回;FALSE:如果IO操作未结束返回FALSE,否则返回TRUE
OUT LPDWORD lpdwFlags
);
重叠I/O模型也允许应用程序以一种重叠方式,实现对连接的接受。具体的做法是在监听套接字上调用AcceptEx函数。

//面向连接的Socket
WSADATA wd;
::WSAStartup(MAKEWORD(2,2), &wd);

sockaddr_in addr;
addr.sin_family            = AF_INET;
addr.sin_port            = htons(1555);
addr.sin_addr.s_addr    = inet_addr("127.0.0.1");

SOCKET s = ::socket(AF_INET, SOCK_STREAM, 0);
::bind(s, (const sockaddr*)&addr, sizeof(addr));
::listen(s, 2);
sockaddr_in client_addr;
SOCKET ns = ::accept(s, (sockaddr*)&client_addr, NULL);
int fromlen = sizeof(sockaddr_in);

//创建event数组,将event放入数组
WSAEVENT wEvent = ::WSACreateEvent();
WSAEVENT    eventArray[WSA_MAXIMUM_WAIT_EVENTS] = {NULL};
DWORD        dwEventNum        = 0;
eventArray[dwEventNum]        = wEvent;
++dwEventNum;

//创建WSAOVERLAPPED结构体,与event关联
WSAOVERLAPPED    wOverLapped;
ZeroMemory(&wOverLapped, sizeof(WSAOVERLAPPED));
wOverLapped.hEvent = wEvent;

//提供接收数据的buffer
WSABUF    wBuffer;
char    cBuffer[1024] = {0};
wBuffer.buf = cBuffer;
wBuffer.len = 1024;
DWORD    dwRecv = 0;        //接收的数据字节
DWORD    dwFlag = 0;        //设为0的参数

while(1)
{
    //指定SOCKET,WSABUF,WSAOVERLAPPED,立即反回;若IO完成,则指定WSAOVERLAPPED中的event受信
    ::WSARecvFrom(ns, &wBuffer, 1, &dwRecv, &dwFlag, (sockaddr*)&client_addr, &fromlen, &wOverLapped, NULL);

    //等待event数组中的event受信
    DWORD dwIndex = ::WSAWaitForMultipleEvents(dwEventNum, eventArray, FALSE, INFINITE, FALSE);

    //将受信的event手动设为非受信
    ::WSAResetEvent(eventArray[dwIndex]);
    DWORD dwResult = 0;        //传送的字节数
    ::WSAGetOverlappedResult(ns, &wOverLapped, &dwResult, FALSE, &dwFlag);
    if(0 == dwResult)
    {
        //若为0,在面向连接的socket中,表示对方已断开连接
        break;
    }
    //输出收到的数据
    cout<<inet_ntoa(client_addr.sin_addr)<<endl<<cBuffer<<endl;
}

//关闭event句柄
::WSACloseEvent(wEvent);
::closesocket(ns);
::closesocket(s);
::WSACleanup();

OverLapped完成例程模型
与重叠事件模型不同的是,事件模型在IO结束后使event变为受信,而这里是调用一个回调函数。利用SleepEx等待回调函数结束。
1、创建SOCKET.
2、创建WSAOVERLAPPED结构体。
3、创建buffer,与WSABUF关联。
4、定义WSAOVERLAPPED_COMPLETION_ROUTINE回调函数。
5、使用类似WSARecvFrom这样的函数,传入WSAOVERLAPPED结构体和回调函数,函数立即返回。若IO完成,则指定的回调函数被调用。
6、使用SleepEx等待回调函数结束。

typedef
void
(CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
IN DWORD dwError, //错误代码,非0即出错
IN DWORD cbTransferred, //传送字节数,面向连接时为0表示对方断开连接
IN LPWSAOVERLAPPED lpOverlapped,
IN DWORD dwFlags
);
利用SleepEx等待回调函数结束时,第二个参数为TRUE;也可用WSAWaitForMultipleEvents等待,将最后一个参数设为TRUE,需要创建event对象,只作为参数输入,没有实际意义。

//回调函数
void CALLBACK MyCompletionRoutine(
IN DWORD dwError, //错误代码
IN DWORD cbTransferred, //传输的字节
IN LPWSAOVERLAPPED lpOverlapped,
IN DWORD dwFlags)
{
if((0 != dwError) || (0 == cbTransferred))
{
cout<<”disconnect”<

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值