Windows Socket五种I/O模型——代码全攻略

Windows 套接字I/O 模型 windows提供了一些I/O 模型帮助应用程序以同步或者异步方式在一个或者多个套接字上管理I/O。大体上,这样的I/O 模型共有6 种。 阻塞(blocking)模型选择(select)模型WSAAsyncSelect模型WSAEventSelect 模型重叠(overlapped)模型完成端口(completion port)模型 1.阻塞(blocking)模型 在阻塞模式下, 阅读详情

如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。

我会以一个回应反射式服务器(与《Windows网络编程》第八章一样)来介绍这五种I/O模型。
我们假设客户端的代码如下(为代码直观,省去所有错误检查,以下同):

#include <WINSOCK2.H>
#include <stdio.h>

#define SERVER_ADDRESS "137.117.2.148"
#define PORT           5150
#define MSGSIZE        1024

#pragma comment(lib, "ws2_32.lib")

int main()
{
  WSADATA     wsaData;
  SOCKET      sClient;
  SOCKADDR_IN server;
  char        szMessage[MSGSIZE];
  int         ret;
 
  // Initialize Windows socket library
  WSAStartup(0x0202, &wsaData);

  // Create client socket
  sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Connect to server
  memset(&server, 0, sizeof(SOCKADDR_IN));
  server.sin_family = AF_INET;
  server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);
  server.sin_port = htons(PORT);

  connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));

  while (TRUE)
  {
    printf("Send:");
  gets(szMessage);

    // Send message
    send(sClient, szMessage, strlen(szMessage), 0);

    // Receive message
    ret = recv(sClient, szMessage, MSGSIZE, 0);
    szMessage[ret] = '/0';

    printf("Received [%d bytes]: '%s'/n", ret, szMessage);
  }

  // Clean up
  closesocket(sClient);
  WSACleanup();
  return 0;
}

客户端所做的事情相当简单,创建套接字,连接服务器,然后不停的发送和接收数据。

比较容易想到的一种服务器模型就是采用一个主线程,负责监听客户端的连接请求,当接收到某个客户端的连接请求后,创建一个专门用于和该客户端通信的套接字和一个辅助线程。以后该客户端和服务器的交互都在这个辅助线程内完成。这种方法比较直观,程序非常简单而且可移植性好,但是不能利用平台相关的特性。例如,如果连接数增多的时候(成千上万的连接),那么线程数成倍增长,操作系统忙于频繁的线程间切换,而且大部分线程在其生命周期内都是处于非活动状态的,这大大浪费了系统的资源。所以,如果你已经知道你的代码只会运行在Windows平台上,建议采用Winsock I/O模型。

一.选择模型
Select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“Select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理。最初设计该模型时,主要面向的是某些使用UNIX操作系统的计算机,它们采用的是Berkeley套接字方案。Select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。由于Winsock 1.1向后兼容于Berkeley套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。(节选自《Windows网络编程》第八章)
下面的这段程序就是利用选择模型实现的Echo服务器的代码(已经不能再精简了):

#include <winsock.h>
#include <stdio.h>

#define PORT       5150
#define MSGSIZE    1024

#pragma comment(lib, "ws2_32.lib")

int    g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];

DWORD WINAPI WorkerThread(LPVOID lpParameter);

int main()
{
  WSADATA     wsaData;
  SOCKET      sListen, sClient;
  SOCKADDR_IN local, client;
  int         iaddrSize = sizeof(SOCKADDR_IN);
  DWORD       dwThreadId;

  // Initialize Windows socket library
  WSAStartup(0x0202, &wsaData);

  // Create listening socket
  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Bind
  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
  listen(sListen, 3);

  // Create worker thread
  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId); 

  while (TRUE)
  {
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d/n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    // Add socket to g_CliSocketArr
    g_CliSocketArr[g_iTotalConn++] = sClient;
  }
 
  return 0;
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
  int            i;
  fd_set         fdread;
  int            ret;
  struct timeval tv = {1, 0};
  char           szMessage[MSGSIZE];
 
  while (TRUE)
  {
    FD_ZERO(&fdread);
    for (i = 0; i < g_iTotalConn; i++)
    {
      FD_SET(g_CliSocketArr[i], &fdread);
    }

    // We only care read event
    ret = select(0, &fdread, NULL, NULL, &tv);

    if (ret == 0)
    {
      // Time expired
      continue;
    }

    for (i = 0; i < g_iTotalConn; i++)
    {
      if (FD_ISSET(g_CliSocketArr[i], &fdread))
      {
        // A read event happened on g_CliSocketArr[i]
        ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
    {
     // Client socket closed
          printf("Client socket %d closed./n", g_CliSocketArr[i]);
     closesocket(g_CliSocketArr[i]);
     if (i < g_iTotalConn - 1)
          {           
            g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];
          }
        }
    else
    {
     // We received a message from client
          szMessage[ret] = '/0';
     send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0);
        }
      }
    }
  }
 
  return 0;
}

服务器的几个主要动作如下:
1.创建监听套接字,绑定,监听;
2.创建工作者线程;
3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
4.接受客户端的连接。这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。如下所示:

int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
 if (当前连接数 < FD_SETSIZE)
  return CF_ACCEPT;
 else
  return CF_REJECT;
}

工作者线程里面是一个死循环,一次循环完成的动作是:
1.将当前所有的客户端套接字加入到读集fdread中;
2.调用select函数;
3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)

除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。

二.异步选择
Winsock提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型最早出现于Winsock的1.1版本中,用于帮助应用程序开发者面向一些早期的16位Windows平台(如Windows for Workgroups),适应其“落后”的多任务消息环境。应用程序仍可从这种模型中得到好处,特别是它们用一个标准的Windows例程(常称为"WndProc"),对窗口消息进行管理的时候。该模型亦得到了Microsoft Foundation Class(微软基本类,MFC)对象CSocket的采纳。(节选自《Windows网络编程》第八章)
我还是先贴出代码,然后做详细解释:
#include <winsock.h>
#include <tchar.h>

#define PORT      5150
#define MSGSIZE   1024
#define WM_SOCKET WM_USER+0

#pragma comment(lib, "ws2_32.lib")

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
  static TCHAR szAppName[] = _T("AsyncSelect Model");
  HWND         hwnd ;
  MSG          msg ;
  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 = szAppName ;

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

  hwnd = CreateWindow (szAppName,                  // window class name
                       TEXT ("AsyncSelect Model"), // window caption
                       WS_OVERLAPPEDWINDOW,        // window style
                  

winsock IO五种模型 这是winsocket的五种IO模型实现,压缩里面有2个VC控制台应用程序工程: 1、winsock_server工程,服务器端。这里实现了五种winsoket IO模型,你要看那个模型的代码,只需要把那个模型的注释代码放开,其他模型的代码屏蔽就可以了,我已经调试过,完全可以编译成功。五种IO模型都是实现跟客户端进行回应反射式的功能。 2、Winsock_client工程,是客户端。它只是连接服务器,然后与服务器进行收发数据而已。 立即下载

相关推荐

浅谈socket同步和异步、阻塞和非阻塞、I/O模型

在进行网络编程时,常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式 同步/异步主要针对C端: 同步:c端发出一个功能调用时,在没有得到结果之前,c端死等结果 例如:普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事 异步:c端一个异步过程调用发出后,调用者不会立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。 例如:ajax请求(异步): 事件触发->服务器处理(浏览器可做其他的)->处理完毕,ajax回调函数处理结果 阻塞/非阻塞主要针对S端: 阻塞(等待)

ArduPilot + Gazebo + Mavros + QGC + 遥控器:从仿真到实机的自主轨迹控制实践

本文详细介绍了基于ArduPilot、Gazebo、Mavros和QGC的无人机自主轨迹控制全流程实践。从仿真环境搭建、多终端协同配置,到遥控器集成与自主圆形轨迹算法实现,提供了完整的代码示例和实机迁移要点,帮助开发者快速掌握无人机自主控制技术。

corn8的博客 940

Oracle19c-集群节点重新添加

Oracle RAC集群在极端情况下,如硬件、主机,集群软件损坏,导致集群节点永久无法使用,需要对集群节点进行修复,删除节点集群信息并重新加入集群,以恢复集群原有的架构。 本文主要讲述一套两节点的Oracle19c RAC集群,节点一orcl01由于主机异常无法正常启动,在对主机进行重装之后,需要将orcl01节点重新加入集群的操作步骤。环境信息: 操作系统:Linux 7 RAC集群:19C 主机节点:orcl0...

sinat_36757755的博客 2351

提示词注入攻击全面解析与防御策略

提示词注入(Prompt Injection)是指攻击者通过精心构造的输入内容,操纵或欺骗AI系统执行非预期行为的技术手段。这种攻击类似于传统的SQL注入或XSS攻击,但其目标是AI模型的提示词处理机制而非数据库或网页。提示词注入利用了大语言模型的一个基本特性:它们被设计为遵循指令。攻击者通过在用户输入中嵌入特定指令,试图覆盖或绕过系统原有的安全限制和行为规则,让模型执行本不应该执行的操作。深度防御:实施多层次防御策略,不依赖单一安全措施持续更新:随着攻击技术的发展不断更新防御措施安全设计优先。

免责声明:本资料部分整理自合法网络公开渠道,部分为个人学习积累成果,仅作学习参考、学术交流使用。所收费用仅为资料搜集整理的时间劳务酬劳。版权归原作者及出版方所有,本人不承担相关版权及内容法律责任,若有侵权请告知,即刻删除处理。 4206

Ubuntu运行vmware时报错Vmware Unable to install all modules!Use shipped Linux kernel AIO access library解决。

Ubuntu运行vmware时报错Vmware Unable to install all modules! vmware --version 提示下面内容: [AppLoader] Use shipped Linux kernel AIO access library. An up-to-date "libaio" or "libaio1" package from your system is preferred. VMware Workstation 16.1.2 build-17966106

wto882dim的博客 2848

别再被无限debugger卡住了!Chrome DevTools实战绕过技巧(含Hook与文件替换)

本文详细介绍了如何通过Chrome DevTools绕过无限debugger封锁的实战技巧,包括Hook技术与文件替换方法。针对常见的定时器轰炸、循环陷阱和动态注入等反调试手段,提供了高效的解决方案,帮助开发者快速突破调试障碍,提升爬虫和逆向工程效率。

weixin_30326515的博客 241

VCS多lib编译实战:如何高效处理Verilog模块重命名与分开编译

本文深入探讨了在复杂SoC设计中,如何利用VCS工具实现多库编译,以高效处理Verilog模块重命名与分开编译的工程难题。通过解析synopsys_sim.setup配置、Config映射机制,并提供完整的自动化脚本实战指南,帮助工程师构建健壮、可维护的编译环境,从而显著提升大型项目的编译效率和代码管理灵活性。

weixin_29169505的博客 357

AUTOSAR进阶图解==>AUTOSAR_SWS_COM

AUTOSAR COM模块是AUTOSAR通信堆栈的核心组件,提供信号导向的通信服务。该模块位于通信服务层,连接RTE与PDU Router,主要功能包括信号封装/提取、传输模式管理、信号路由、超时监控和字节序转换等。COM模块架构分为应用层、通信服务层和通信抽象层,包含信号管理、I-PDU处理、传输模式管理等组件。其配置结构包括顶层容器、I-PDU组、I-PDU和信号等多层次配置对象,支持丰富的参数设置。模块采用严格的状态管理机制,包括模块状态和I-PDU组状态转换,确保通信可靠性。

一名留德华的,康德的信徒的,专注于汽车电子领域的博客 1243

【STK】手把手教你利用STK进行覆盖分析02-生成详细分析报告和图表

这篇文章主要针对上一篇文章中最后的案例1进行进一步的说明,详细介绍我们可以生成哪些结论性的成果。STK的Coverage模块的生成结果包括分析报告和图表两种样式,每种样式还分为动态和静态两种,也就是说,我们做完一个STK覆盖场景的仿真后,一般可以生成静态分析报告、动态分析报告、静态分析图表和动态分析图表这4种形式的分析成果,下面进行详细介绍。我们在选择了想要分析的区域或者点后,就可以利用CovDef生成分析报告了,覆盖定义的分析报告分为静态报告和动态报告两种,下面分析进行介绍: 首先我们先在左侧目标浏览器C

weixin_44184852的博客 3441

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

上一篇: 《Windows程序设计》(第五版)勘误表
下一篇: 在Eclipse中开发C/C++项目
mlite
博客等级 码龄23年 11粉丝 8原创
评论 13
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值