[笔记]windows网络编程之常见模型(上)

简介: [笔记]windows网络编程之常见模型

windows 常见模型

select模型

是什么?

对多个socket 进行管理 调用select()可以获取指定socket状态,即select 选择获得有响应的指定的socket

为什么?

解决基本C/S模型中,accept()、recv()、send()阻塞的问题

select模型与C/S模型的不同点

C/S模型中accept()会阻塞一直傻等socket来链接

select模型只解决accept()傻等的问题,不解决recv(),send()执行阻塞问题

怎么用?

服务端

1.一般创建非阻塞步骤(初始化,创建,绑定ip和端口,监听,ioctlsocket设置非阻塞)

2.装填socket数组 FD_SET(socketServer, &allSockets);

3.调用select() 对有响应的socket 做相应处理(accept,send,recv)

具体参照
《windows网络编程》第八章 8.3 基于select模型的socket编程

参考:网络编程——select模型(总结)

WSAAsyncSelect模型

是什么?

应用程序可以在一个socket上接收以windows消息为基础的网络事件通知,它实现了读写数据的异步通知功能,但不提供异步的数据传输。

为什么?

WSAAsyncSelect是select模型的异步版本。在应用程序使用select函数时会发生阻塞现象。可以通过select的timeout参数设置阻塞的时间。在设置的时间内,select函数等待,直到一个或多个套接字满足可读或可写的条件。

而WSAAsyncSelect是非阻塞的。Windows sockets程序在调用recv或send之前,调用WSAAsyncSelect注册网络事件。WSAAsyncSelect函数立即返回。当系统中数据准备好时,会向应用程序发送消息。此此消息的处理函数中可以调用recv或send进行接收或发送数据。

WSAAsyncSelect模型与select模型的相同点是它们都可以对多个套接字进行管理。但它们也有不小的区别。首先WSAAsyncSelect模型是异步的,且通知方式不同。更重要的一点是:WSAAsyncSelect模型应用在基于消息的Windows环境下,使用该模型时必须创建窗口,而select模型可以广泛应用在Unix系统,使用该模型不需要创建窗口。最后一点区别:应用程序在调用WSAAsyncSelect函数后,套接字就被设置为非阻塞状态。而使用select函数不改变套接字的工作方式。

怎么用?

1.初始化socket环境,并创建win32自定义事件的socket事件 WM_SOCKET

2.调用WSAAsyncSelect()绑定事件到窗口消息队列

3.通过GetMessage()轮询消息,当消息事件发生时,实现对socket的处理(包括accept,send,recv)

具体参照
《windows网络编程》第八章 8.5 基于WSAAsyncSelect模型的服务器编程

WSAEventSelect模型

是什么?

允许在多个Socket上接收以事件为基础的网络事件通知,应用程序在创建Socket后,调用WSAEventSelect()函数将事件对象与网络事件集合相关联。当网络事件发生时,应用程序以事件的形式接收网络事件通知。

为什么?

WSAEventSelect与WSAAsyncSelect在网络事件发生时系统通知应用程序的形式不同。

Select 主动获取指定socket状态,

WSAEventSelect,WSAAsyncSelect 则会被动选择系统通知应用程序socket状态变化。

WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统,并将socket设置成非阻塞模式。二者不同之处在于socket事件的通知方法:WSAAsyncSelec模型利用窗口句柄和消息映射函数通知网络事件,而WSAEventSelect模型利用WSAEVENT通知网络事件。

怎么用?

服务端:

1.初始化socket环境,并创建用于监听的socket

2.创建事件对象 WSACreateEvent()

3.将新建的事件对象与监听Socket相关联,并注册该Socket关注的网络事件集合,通常为FD_ACCEPT 和 FD_CLOSE

4.等待所有事件对象上发生注册的网络事件,并对网络事件进行处理。

WSWaitForMultipleEvents()

WSAEnumNetWorkEvents();

5.如果触发了FD_ACCEPT事件,则程序接收来自客户端的请求,得到与客户端通信的Socket,并为该Socket创建相关联的事件

对象,注册该Socket关注网络事件集合,通常为FD_READ,FD_CLOSE和FD_WRITE

6.如果触发了FD_CLOSE事件,则关闭Socket,释放其占用资源

7.如果触发了FD_READ事件,则调用recv函数接收来则客户端的请求

8.如果触发了FD_WRITE事件,则调用send()函数向客户端发送数据

具体参照
《windows网络编程》第八章 8.5 基于WSAEventSelect模型的服务器编程

重叠I/O模型

是什么?

重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。针对这些提交的请求,在它们完成之后,应用程序会收到通知,于是就可以通过自己另外的代码来处理这些数据了。

重叠i/o是真正意义上的异步io模型 调用输入/输出函数后 立即返回(WSARecv,WSASend)。

为什么?

1.可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。

2.比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。

因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。不需要等待调用recv时才拷贝

而这4种模型种,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。

3. 从《windows网络编程》中提供的试验结果中可以看到,在使用了P4 1.7G Xero处理器(CPU很强啊)以及768MB的回应服务器中,最大可以处理4万多个SOCKET连接,在处理1万2千个连接的时候CPU占用率才40% 左右 ―― 非常好的性能,已经直逼完成端口。

怎么用?

使用事件通知来管理重叠i/o操作

通过事件来通知应用程序I/O 操作已完成

在WSASend()或者WSARecv函数中,当重叠操作完成后,如果lpCompletionRoutine参数为NULL,则lpOverlapped的hEvent参数将被设为已授信状态。应用程序调用WSAWaitForMultipleEvents()函数或者WSAGetOverlappedResult函数等待或者轮询事件对象变为未授信状态。

WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL) //lpCompletionRoutine参数为NULL,使用事件通知

// OverlappedServer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include "winsock2.h"
#pragma   comment(lib,"ws2_32.lib")
#define DATA_BUFSIZE 4096
int _tmain(int argc, _TCHAR* argv[])
{
  //-----------------------------------------
  // 声明和初始化变量
  WSABUF DataBuf;             // 发送和接收数据的缓冲区结构体
  char buffer[DATA_BUFSIZE];      // 缓冲区结构体DataBuf中
  DWORD EventTotal = 0,         // 记录事件对象数组中的数据
    RecvBytes = 0,                // 接收的字节数
    Flags = 0,                    // 标识位
    BytesTransferred = 0;         // 在读、写操作中实际传输的字节数
  // 数组对象数组
  WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
  WSAOVERLAPPED AcceptOverlapped;   // 重叠结构体
  SOCKET ListenSocket, AcceptSocket;    // 监听套接字和与客户端进行通信的套接字
  //-----------------------------------------
  // 初始化Windows Sockets
  WSADATA wsaData;
  WSAStartup(MAKEWORD(2,2), &wsaData);
  //-----------------------------------------
  // 创建监听套接字,并将其绑定到本地IP地址和9990端口
  ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  u_short port = 9990;
  char* ip;
  sockaddr_in service;
  service.sin_family = AF_INET;
  service.sin_port = htons(port);
  hostent* thisHost;
  thisHost = gethostbyname("");
  ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list); 
  service.sin_addr.s_addr = inet_addr(ip);
  bind(ListenSocket, (SOCKADDR *) &service, sizeof(SOCKADDR));
  //-----------------------------------------
  // 开始监听
  listen(ListenSocket, 1);
  printf("Listening...\n");
  //-----------------------------------------
  // 接收连接请求
  AcceptSocket = accept(ListenSocket, NULL, NULL);
  printf("Client Accepted...\n");
  //-----------------------------------------
  // 创建事件对象,建立重叠结构
  EventArray[EventTotal] = WSACreateEvent();
  ZeroMemory(buffer, DATA_BUFSIZE);
  ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));   // 初始化重叠结构
  AcceptOverlapped.hEvent = EventArray[EventTotal];         // 设置重叠结构中的hEvent字段
  DataBuf.len = DATA_BUFSIZE;                           // 设置缓冲区
  DataBuf.buf = buffer;
  EventTotal++;                                       // 事件对象总数加1
  //-----------------------------------------
  // 处理在套接字上接收到数据
  while (1) {
    DWORD Index;    // 保存处于授信状态的事件对象句柄
    //-----------------------------------------
    // 调用WSARecv()函数在AcceptSocket套接字上以重叠I/O方式接收数据,保存到DataBuf缓冲区中
    if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL) == SOCKET_ERROR) {
      if (WSAGetLastError() != WSA_IO_PENDING)
        printf("Error occured at WSARecv()\n");
    }
    //-----------------------------------------
    // 等待完成的重叠I/O调用
    Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
    //-----------------------------------------
    // 决定重叠事件的状态
    WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);
    //-----------------------------------------
    // 如果连接已经关闭,则关闭AcceptSocket套接字
    if (BytesTransferred == 0) {
      printf("Closing Socket %d\n", AcceptSocket);
      closesocket(AcceptSocket);
      WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
      return -1;
    }
    //-----------------------------------------
    // 如果有数据到达,则将收到的数据则发送回客户端
    if (WSASend(AcceptSocket, &DataBuf, 1, &RecvBytes, Flags, &AcceptOverlapped, NULL) == SOCKET_ERROR)
      printf("WSASend() is busted\n");
    //-----------------------------------------
    // 重置已授信的事件对象
    WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
    //-----------------------------------------   
    // 重置Flags变量和重叠结构
    Flags = 0;
    ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
    ZeroMemory(buffer, DATA_BUFSIZE);
    AcceptOverlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];
    //-----------------------------------------
    // 重置缓冲区
    DataBuf.len = DATA_BUFSIZE;
    DataBuf.buf = buffer;
  }
  system("pause");
  return 0;
}
使用完成例程来管理重叠i/o操作

完成例程则指定应用程序在完成i/o操作后调用一个事先定义的回调函数。

在WSASend()或者WSARecv函数中,如果lpCompletionRoutine参数不为NULL,则hEvent参数将被忽略,而是将上下文信息传送给完成例程函数,调用WSAGet-OverlapperResult函数查询重叠操作的结果。

完成例程函数原型如下:

void CALLBACK CompletionROUTINE( DWORD dwError, // 重叠操作的完成状态

DWORD cbTransferred, // 发送的字节数

LPWSAOVERLAPPED lpOverlapped, // 指定重叠操作的结构体

DWORD dwFlags) // 标识位

服务端

1.初始化windows socket环境

2.创建监听socket sListen 并将其绑定到本地地址,端口

3.在socket sListen上进行监听

4.创建工作线程,对客户端发送来的数据进行处理

5.循环处理来自客户端的连接请求,接收连接,并将得到的与客户端的socket保存到g_sNewClientConnectio中,将变量g_bNewConnectionArried设置为TRUE,表示存在新的客户端连接。

// CompletionRoutineServer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <winsock2.h> 
#include <stdio.h> 
#define PORT 9990       // 监听的端口
#define MSGSIZE 1024    // 发送和接收消息的最大长度
# pragma comment( lib, "ws2_32.lib" ) 
// I/O操作的数据
typedef struct 
{ 
  WSAOVERLAPPED overlap;        // 重叠结构体
  WSABUF Buffer;                // 缓冲区对象
  char szMessage[MSGSIZE] ;     // 缓冲区字符数组
  DWORD NumberOfBytesRecvd;   // 接收字节数
  DWORD Flags;                  // 标识位
  SOCKET sClient;               // 套接字
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA; 
// 工作线程,用于接收用户数据
DWORD WINAPI WorkerThread( LPVOID); 
// 在工作线程中调用WSARecv()函数接收数据时指定的完成例程函数
void CALLBACK CompletionROUTINE( DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
SOCKET g_sNewClientConnection;          // 接收客户端连接请求后得到的
BOOL g_bNewConnectionArrived = FALSE ;    // 标识是否存在未经WorkerThread()函数处理的新的连接
int _tmain(int argc, _TCHAR* argv[])
{
  WSADATA wsaData;                    // Windows Sockets对象
  SOCKET sListen;                     // 与客户端进行通信的套接字
  SOCKADDR_IN local, client;              // 服务器本地地址和客户端地址
  DWORD dwThreadId;                 // 工作线程的线程ID
  int iaddrSize = sizeof(SOCKADDR_IN) ;   // 地址的大小  
  // 初始化Windows Socket环境
  WSAStartup( 0x0202, & wsaData) ; 
  // 创建监听套接字
  sListen = socket ( AF_INET , SOCK_STREAM , IPPROTO_TCP ) ; 
  // 绑定
  local.sin_addr. S_un. S_addr = htonl(INADDR_ANY); 
  local.sin_family = AF_INET ; 
  local.sin_port = htons ( PORT) ; 
  bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN)) ; 
  // 监听
  listen(sListen, 3) ; 
  // 创建工作线程
  CreateThread( NULL , 0, WorkerThread, NULL , 0, & dwThreadId) ; 
  // 循环处理来自客户端的连接请求
  while(TRUE) 
  { 
    // 接收连接,得到与客户端进行通信的套接字g_sNewClientConnection
    g_sNewClientConnection = accept(sListen, (struct sockaddr *)&client, &iaddrSize) ; 
    // 标识有新的连接
    g_bNewConnectionArrived = TRUE ; 
    // 打印接入的客户端
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client. sin_port)); 
  } 
} 
// 工作线程
DWORD WINAPI WorkerThread( LPVOID lpParam) 
{ 
  LPPER_IO_OPERATION_DATA lpPerIOData = NULL;   // 保存I/O操作的数据
  while (TRUE) 
  { 
    if ( g_bNewConnectionArrived)   // 如果有新的连接请求
    { 
      // 为新的连接执行一个异步操作
      // 为LPPER_IO_OPERATION_DATA结构体分配堆空间
      lpPerIOData = (LPPER_IO_OPERATION_DATA) HeapAlloc( 
        GetProcessHeap( ), 
        HEAP_ZERO_MEMORY, 
        sizeof (PER_IO_OPERATION_DATA)) ; 
      // 初始化结构体lpPerIOData
      lpPerIOData->Buffer.len = MSGSIZE; 
      lpPerIOData->Buffer.buf = lpPerIOData->szMessage; 
      lpPerIOData->sClient = g_sNewClientConnection; 
      // 接收数据
      WSARecv( lpPerIOData->sClient,          // 接收数据的套接字
        &lpPerIOData->Buffer,               // 接收数据的缓冲区
        1,                                // 缓冲区对象的数量
        &lpPerIOData->NumberOfBytesRecvd,   // 接收数据的字节数
        &lpPerIOData->Flags,                // 标识位
        &lpPerIOData->overlap,              // 重叠结构
        CompletionROUTINE) ;                // 完成例程函数,将会在接收数据完成的时候进行相应的调用 
      g_bNewConnectionArrived = FALSE ;     // 标识新的连接已经处理完成
    } 
    SleepEx(1000, TRUE) ;                   // 休息1秒钟,然后继续
  } 
  return 0; 
} 
// 完成例程函数
void CALLBACK CompletionROUTINE( DWORD dwError, // 重叠操作的完成状态
        DWORD cbTransferred,                      // 发送的字节数
        LPWSAOVERLAPPED lpOverlapped,             // 指定重叠操作的结构体
        DWORD dwFlags)                            // 标识位
{ 
  // 将LPWSAOVERLAPPED类型的lpOverlapped转化成了LPPER_IO_OPERATION_DATA
  LPPER_IO_OPERATION_DATA lpPerIOData = ( LPPER_IO_OPERATION_DATA) lpOverlapped;
  // 如果发生错误或者没有数据传输,则关闭套接字,释放资源
  if (dwError != 0 || cbTransferred == 0)   
  { 
    closesocket( lpPerIOData-> sClient) ; 
    HeapFree( GetProcessHeap(), 0, lpPerIOData) ; 
  } 
  else 
  { 
    lpPerIOData->szMessage[cbTransferred] = '\0';   // 标识接收数据的结束
    // 向客户端发送接收到的数据
    send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, 0) ; 
    // 执行另一个异步操作,接收数据
    memset (&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED)); 
    lpPerIOData->Buffer.len = MSGSIZE; 
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage; 
    // 接收数据
    WSARecv( lpPerIOData->sClient, 
            &lpPerIOData->Buffer, 
            1, 
            &lpPerIOData->NumberOfBytesRecvd, 
            &lpPerIOData->Flags, 
            &lpPerIOData->overlap, 
            CompletionROUTINE); 
  } 
}

具体参照
《windows网络编程》第八章 8.6 基于重叠io模型的服务器编程

重叠IO模型

重叠IO模型之完成例程


相关文章
|
9天前
|
机器学习/深度学习 PyTorch 算法框架/工具
【从零开始学习深度学习】26.卷积神经网络之AlexNet模型介绍及其Pytorch实现【含完整代码】
【从零开始学习深度学习】26.卷积神经网络之AlexNet模型介绍及其Pytorch实现【含完整代码】
|
9天前
|
机器学习/深度学习 PyTorch 算法框架/工具
【从零开始学习深度学习】28.卷积神经网络之NiN模型介绍及其Pytorch实现【含完整代码】
【从零开始学习深度学习】28.卷积神经网络之NiN模型介绍及其Pytorch实现【含完整代码】
|
3天前
|
机器学习/深度学习 搜索推荐 算法
基于深度学习神经网络协同过滤模型(NCF)的图书推荐系统
登录注册 热门图书 图书分类 图书推荐 借阅图书 购物图书 个人中心 可视化大屏 后台管理
基于深度学习神经网络协同过滤模型(NCF)的图书推荐系统
|
6天前
|
编解码 Windows
FFmpeg开发笔记(二十九)Windows环境给FFmpeg集成libxvid
XviD是开源MPEG-4视频编码器,与DivX相似但后者非开源。早期MP4常使用XviD或DivX编码,现已被H.264取代。在Windows上集成FFmpeg的XviD编解码库libxvid,需访问<https://labs.xvid.com/source/>下载源码,解压后在MSYS环境中配置、编译和安装。之后重新配置FFmpeg,启用libxvid并编译安装。详细步骤包括configure命令、make和make install。成功后,通过`ffmpeg -version`检查是否启用libxvid。更多音视频开发技术可参考《FFmpeg开发实战:从零基础到短视频上线》。
35 0
FFmpeg开发笔记(二十九)Windows环境给FFmpeg集成libxvid
|
9天前
|
机器学习/深度学习 PyTorch 算法框架/工具
【从零开始学习深度学习】27.卷积神经网络之VGG11模型介绍及其Pytorch实现【含完整代码】
【从零开始学习深度学习】27.卷积神经网络之VGG11模型介绍及其Pytorch实现【含完整代码】
|
9天前
|
机器学习/深度学习 算法 PyTorch
【从零开始学习深度学习】25.卷积神经网络之LeNet模型介绍及其Pytorch实现【含完整代码】
【从零开始学习深度学习】25.卷积神经网络之LeNet模型介绍及其Pytorch实现【含完整代码】
YOLOv8打印模型结构配置信息并查看网络模型详细参数:参数量、计算量(GFLOPS)
YOLOv8打印模型结构配置信息并查看网络模型详细参数:参数量、计算量(GFLOPS)
|
9天前
|
机器学习/深度学习 PyTorch 算法框架/工具
【从零开始学习深度学习】29.卷积神经网络之GoogLeNet模型介绍及用Pytorch实现GoogLeNet模型【含完整代码】
【从零开始学习深度学习】29.卷积神经网络之GoogLeNet模型介绍及用Pytorch实现GoogLeNet模型【含完整代码】
|
9天前
|
机器学习/深度学习 数据采集 自然语言处理
【注意力机制重大误区】网络模型增加注意力机制后,性能就一定会得到提升?有哪些影响因素?
【注意力机制重大误区】网络模型增加注意力机制后,性能就一定会得到提升?有哪些影响因素?
|
9天前
|
域名解析 缓存 网络协议