[笔记]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模型之完成例程


相关文章
|
1月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
113 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
1月前
|
机器学习/深度学习 数据可视化 计算机视觉
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
这篇文章详细介绍了如何通过可视化深度学习中每层特征层来理解网络的内部运作,并使用ResNet系列网络作为例子,展示了如何在训练过程中加入代码来绘制和保存特征图。
63 1
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
|
11天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
36 2
|
12天前
|
运维 网络协议 算法
7 层 OSI 参考模型:详解网络通信的层次结构
7 层 OSI 参考模型:详解网络通信的层次结构
33 1
|
1月前
|
监控 Ubuntu Linux
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
这篇文章介绍了如何在Ubuntu和Windows系统中通过设置相同的时区并使用ntp服务来解决时间同步问题。
66 4
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
|
1月前
|
机器学习/深度学习 编解码 算法
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
MobileNetV3是谷歌为移动设备优化的神经网络模型,通过神经架构搜索和新设计计算块提升效率和精度。它引入了h-swish激活函数和高效的分割解码器LR-ASPP,实现了移动端分类、检测和分割的最新SOTA成果。大模型在ImageNet分类上比MobileNetV2更准确,延迟降低20%;小模型准确度提升,延迟相当。
59 1
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
|
1月前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
75 1
目标检测笔记(一):不同模型的网络架构介绍和代码
|
23天前
|
网络协议 算法 网络性能优化
计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议
计算机网络常见面试题(一):TCP/IP五层模型、应用层常见的协议、TCP与UDP的区别,TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议、ARP协议
|
28天前
|
机器学习/深度学习 人工智能 算法
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
车辆车型识别,使用Python作为主要编程语言,通过收集多种车辆车型图像数据集,然后基于TensorFlow搭建卷积网络算法模型,并对数据集进行训练,最后得到一个识别精度较高的模型文件。再基于Django搭建web网页端操作界面,实现用户上传一张车辆图片识别其类型。
72 0
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
|
1月前
|
机器学习/深度学习 数据采集 算法
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)
这篇博客文章介绍了如何使用包含多个网络和多种训练策略的框架来完成多目标分类任务,涵盖了从数据准备到训练、测试和部署的完整流程,并提供了相关代码和配置文件。
56 0
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)
下一篇
无影云桌面