`
yexin218
  • 浏览: 970755 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
社区版块
存档分类
最新评论

Windows系统编程之异步I/O和完成端口

阅读更多

一、  同步I/O和异步I/O

在介绍这部分内容之前先来认识下“异步I/O”。
  说起异步IO,很容易联想到同步I/O,对于同一个I/O对象句柄在同一时刻只允许一个I/O操作,其原理如下图所示:


 显然,当内核真正处理I/O的时间段(T2~T4),用户线程是处于等待状态的,如果这个时间段比较段的话,没有什么影响;倘若这个时间段很长的话,线 程就会长时间处于挂起状态。事实上,该线程完全可以利用这段时间用处理其他事务。

  异步I/O恰好可以解决同步I/O中的问题,而且支持对同一个I/O对象的并行处理,其原理如下图所示:


异步I/O在I/O请求完成时,可以使用让I/O对象或者事件对象受信来通知用户线程,而用户线程中可以使用 GetOverlappedResult来查看I/O的执行情况。
  
由于异步I/O在进行I/O请求后会立即返回,这样就会产生一个问题:“程序是如何取得I/O处理的结果的?”。

  有多种方法可以实现异步I/O,其不同资料上的分类一般都不尽相同,但原理上都类似,这里我把实现异步I/O的方法分为3类,本文就针对这3类方法进 行详细的讨论。
(1)重叠I/O
(2)异步过程调用(APC),扩展I/O
(3)使用完成端口(IOCP)

二、使用重叠I/O实现异步I/O
  
  同一个线程可以对多个I/O对象进行I/O操作,不同的线程也可以对同一个I/O对象进行操作,在我的理解中,重叠的命名就是这么来的。

  在使用重叠I/O时,线程需要创建OVERLAPPED结构以供I/O处理。该结构中最重要的成员是hEvent,它是作为一个同步对象而存在,如果 hEvent为NULL,那么此时的同步对象即为文件句柄、管道句柄等I/O操作对象。当I/O完成后,会使这里的同步对象受信,从而通知用户线程。

  由于在进行I/O请求后会立即返回,但有时用户线程需要知道I/O当前的执行情况,此时就可以使用GetOverlappedResult。如果该函 数的bWait参数为true,那么改函数就会阻塞线程直到目标I/O处理完成为止;如果bWait为false,那么就会立即返回,如果此时的I/O尚 未完,调用GetLastError就会返回ERROR_IO_INCOMPLETE。

代码示例一:

DWORD   nReadByte ;
BYTE   bBuf[BUF_SIZE] ;
OVERLAPPED ov = { 0, 0, 0, 0, NULL } ;  // hEvent = NULL ;
HANDLE hFile = CreateFile ( ……, FILE_FLAG_OVERLAPPED, …… ) ;
ReadFile ( hFile, bBuf, sizeof(bBuf), &nReadByte, &ov ) ;
// 由于此时hEvent=NULL,所以同步对象为hFile,下面两句的效果一样
WaitForSingleObject ( hFile, INFINITE ) ;
//GetOverlappedResult ( hFile, &ov, &nRead, TRUE ) ;

 这里对于hFile有三个重叠的I/O操作,但他们的同步对象却都为hFile。使用 GetOverlappedResult进行等待操作,这里看似在等待第一个I/O处理的完成,其实只要有任何一个I/O处理完成,该函数就会返回,相当 于忽略了其他两个I/O操作的结果。

  其实,这里有一个很重要的原则:对于一个重叠句柄上有多于一个I/O操作的时候,应该使用事件对象而不是文件句柄来实现同步。正确的实现见示例三。
  
代码示例三:

DWORD   nReadByte ;
BYTE   bBuf1[BUF_SIZE],bBuf2[BUF_SIZE],bBuf3[BUF_SIZE] ;
HANDLE  hEvent1 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ; 
HANDLE  hEvent2 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ;
HANDLE  hEvent3 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ;
OVERLAPPED ov1 = { 0, 0, 0, 0, hEvent1 } ;  
OVERLAPPED ov2 = { 0, 0, 0, 0, hEvent2 } ;  
OVERLAPPED ov3 = { 0, 0, 0, 0, hEvent3 } ;  
HANDLE hFile = CreateFile ( ……, FILE_FLAG_OVERLAPPED, …… ) ;
ReadFile ( hFile, bBuf1, sizeof(bBuf1), &nReadByte, &ov1 ) ;
ReadFile ( hFile, bBuf2, sizeof(bBuf2), &nReadByte, &ov2 ) ;
ReadFile ( hFile, bBuf3, sizeof(bBuf3), &nReadByte, &ov3 ) ;
//此时3个I/O操作的同步对象分别为hEvent1,hEvent2,hEvent3
GetOverlappedResult ( hFile, &ov1, &nRead, TRUE ) ;

   这样,这个GetOverlappedResult就可以实现对第一个I/O处理的等待
关于重叠I/O的就讨论到这里,关于重叠I/O的实际应用,可以参考《Windows系统编程之进程通信》其中的命名管道实例。
http://bbs.pediy.com/showthread.php?s=&threadid=26252
 
三、  使用异步过程调用实现异步I/O

异步过程调用(APC),即在特定的上下文中异步的执行一个调用。在异步I/O中可以使用APC,即让操作系统的IO系统在完成异步I/O后立即调用你的 程序。(在有些资料中,把异步I/O中的APC称为“完成例程”,感觉这个名称比较贴切,下文就以“完成例程”来表述。另外通常APC是作为线程同步这一 块的内容,这里尽量淡化这个概念以免混淆。关于APC的详细内容到线程同步时再介绍 )

这里需要注意三点:
(1)  APC总是在调用线程中被调用;
(2)  当执行APC时,调用线程会进入可变等待状态;
(3)  线程需要使用扩展I/O系列函数,例如ReadFileEx,WriteFileEx, 另外可变等待函数也是必须的(至少下面其中之一):

写道
WaitForSingleObjectEx
WaitForMultipleObjectEx
SleepEx
SignalObjectAndWait
MsgWaitForMultipleObjectsEx

  在使用ReadFileEx,WriteFileEx时,重叠结构OVERLAPPED中的hEvent成员并非一定要指定,因为系统会忽略它。当多个 IO操作共用同一个完成例程时,可以使用hEvent来携带序号等信息,用于区别不同的I/O操作,因为该重叠结构会传递给完成例程。如果多个IO操作使 用的完成例程都不相同时,则直接把hEvent设置为NULL就可以了。

在系统调用完成例程有两个条件:
(1)  I/O操作必须完成
(2)  调用线程处于可变等待状态

对于第一个条件比较容易,显然完成例程只有在I/O操作完成时才调用;至于第二个条件就需要进行认为的控制,通过使用可变等待函数,让调用线程处于可变等 待状态,这样就可以执行完成例程了。这里可以通过调节调用可变等待函数的时机来控制完成例程的执行,即可以确保完成例程不会被过早的执行。

当线程具有多个完成例程时,就会形成一个队列。使用可变等待函数使线程进入可变等待状态时有一个表示超时值的参数,如果使用INFINITE,那么只有所 有排队的完成例程被执行或者句柄获得信号时该等待函数才返回。

上面已经对利用完成例程实现异步I/O的一些比较重要的细节进行的简洁的阐述,接下来就以一个实例来说明完成例程的具体实现过程。



实例一:使用完成例程的异步I/O示例

1、  设计目标
体会完成例程的异步I/O实现原理及过程。

2、  问题的分析与设计
设计流程图如下:
  示图说明:



 
  三个IO操作分别是IO_A, IO_B, IO_C, 他们的完成例程分别是APC_A, APC_B, APC_C。IO_A, IO_B是两个 很短的IO操作,IO_C是一个比较费时的IO操作。
3、  详细设计(关键代码如下,具体参见附件中的源代码CompletionRoutine)

VOID WINAPI APC_A ( DWORD dwError, DWORD cbTransferred, LPOVERLAPPED lpo )
{
  pTempInfo.push_back ( "执行IO_A的完成例程" ) ;
}
VOID WINAPI APC_B ( DWORD dwError, DWORD cbTransferred, LPOVERLAPPED lpo )
{
  pTempInfo.push_back ( "执行IO_B的完成例程" ) ;
}
VOID WINAPI APC_C ( DWORD dwError, DWORD cbTransferred, LPOVERLAPPED lpo )
{
  pTempInfo.push_back ( "执行IO_C的完成例程" ) ;
}

void CCompletionRoutineDlg::OnTest() 
{
  // TODO: Add your control notification handler code here
  HANDLE    hFile_A, hFile_B, hFile_C ;
  OVERLAPPED  ov_A = {0}, ov_B = {0}, ov_C = {0} ;

#define C_SIZE 1024 * 1024 * 32

  string  szText_A = "Sample A !" ;
  string  szText_B = "Sampel B !" ;
  string  szText_C ;
  szText_C.resize ( C_SIZE ) ;
  memset ( &(szText_C[0]), 0x40, C_SIZE ) ;
  
  pTempInfo.clear () ;

  hFile_A = CreateFile ( "A.txt", GENERIC_WRITE, 0, NULL, \
              CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL ) ;
  hFile_B = CreateFile ( "B.txt", GENERIC_WRITE, 0, NULL, \
              CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL ) ;
  hFile_C = CreateFile ( "C.txt", GENERIC_WRITE, 0, NULL, \
              CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL ) ;

  WriteFileEx ( hFile_A, &(szText_A[0]), szText_A.length(), &ov_A, APC_A ) ;
  pTempInfo.push_back ( "启动IO_A, 并立即返回" ) ;

  WriteFileEx ( hFile_B, &(szText_B[0]), szText_B.length(), &ov_B, APC_B ) ;
  pTempInfo.push_back ( "启动IO_B, 并立即返回" ) ;

  WriteFileEx ( hFile_C, &(szText_C[0]), szText_C.size(), &ov_C, APC_C ) ;
  pTempInfo.push_back ( "启动IO_C, 并立即返回" ) ;

  pTempInfo.push_back ( "进入可变等待状态" ) ;
  SleepEx ( 1, true ) ;
  pTempInfo.push_back ( "结束可变等待状态" ) ;

  pTempInfo.push_back ( "进入可变等待状态" ) ;
  SleepEx ( 10000, true ) ;
  pTempInfo.push_back ( "结束可变等待状态" ) ;

  CloseHandle ( hFile_A ) ;
  CloseHandle ( hFile_B ) ;
  CloseHandle ( hFile_C ) ;

  m_ListBox.ResetContent () ;
  
  list<string>::iterator p ;
  for ( p = pTempInfo.begin(); p != pTempInfo.end(); p++ )
  {
    m_ListBox.AddString ( p->data() ) ;
  }

  DeleteFile ( "A.txt" ) ;
  DeleteFile ( "B.txt" ) ;
  DeleteFile ( "C.txt" ) ;
}

 执行后的效果如下(WinXP+SP2+VC6.0):



 4、  心得体会
每当一个IO操作结束时会产生一个完成信息,如果该IO操作有完成例程的话就添加到完成例程队列。一旦调用线程进入可变等待状态,就会依次执行队列中的完 成例程。
在这个示例中还有一个问题,如果把这个软件放在系统分区的文件目录下可以正常执行,而放在其他盘符下就会出现问题,执行结果就不同,真是奇怪了。


四、使用完成端口(IOCP)

实例二、使用IOCP的异步I/O示例
1、设计目标
体会完成端口的异步I/O实现原理及过程。

2、  问题的分析与设计



 说明:
  每个客户端与一个管道进行交互,而在交互过程中I/O操作结束后产生的完成包就会进入“I/O完成包队列”。完成端口的线程队列中的线程使用 GetQueuedCompletionStatus来检测“I/O完成包队列”中是否有完成包信息。 
3、详细设计(关键代码如下,具体见附件中的源码)

UINT ServerThread ( LPVOID lpParameter )
{
  ……
  while ( true )
  {
    GetQueuedCompletionStatus ( pMyDlg->hCompletionPort, &cbTrans, &dwCompletionKey, &lpov, INFINITE ) ;
    if ( dwCompletionKey == -1 )
      break ;
    // 读取管道信息
    // 响应管道信息(写入)
  }
  return 0 ;
}

void CMyDlg::OnStart() 
{
  // 创建完成端口
  hCompletionPort = CreateIoCompletionPort ( INVALID_HANDLE_VALUE, NULL, 0, nMaxThread ) ;

  CString lpPipeName = "\\\\.\\Pipe\\NamedPipe" ;
  for ( UINT i = 0; i < nMaxPipe; i++ )
  {
    // 创建命名管道
    PipeInst[i].hPipe =  CreateNamedPipe ( lpPipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, \
          PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, nMaxPipe, 0, 0, INFINITE, NULL ) ;
    ……
    // 把命名管道与完成端口关联起来
    HANDLE hRet = CreateIoCompletionPort ( PipeInst[i].hPipe, hCompletionPort, i, nMaxThread ) ;
    ……
    // 等待连接
    ConnectNamedPipe ( PipeInst[i].hPipe, &(PipeInst[i].ov) ) ;
  }
  // 创建线程
  for ( i = 0; i < nMaxThread; i++ )
  {
    hThread[i] = AfxBeginThread ( ServerThread, NULL, THREAD_PRIORITY_NORMAL ) ;
  }
  ……
}
void CMyDlg::OnStop() 
{
  for ( UINT i = 0; i < nMaxThread; i++ )
  {
    // 用来唤醒线程的虚假I/O完成包
    PostQueuedCompletionStatus ( hCompletionPort, 0, -1, NULL ) ;
    CloseHandle ( hThread[i] ) ;
  }
  for ( i = 0; i < nMaxPipe; i++ )
  {
    DisconnectNamedPipe ( PipeInst[i].hPipe ) ;
    CloseHandle ( PipeInst[i].hPipe ) ;
  }
  ……
}

 4、心得体会
  上面这个例子是关于完成端口的简单应用。可以这样来理解完成端口,它与三种资源相关分别是管道、I/O完成包队列、线程队列,它的作用是协调这三种资 源。
【参考文献】
[1]. Windows系统编程. Johnson M.Hart著
【版权声明】必须注明来源看雪技术论坛(bbs.pediy.com) 及作者,并保持文章的完整性。

  • 大小: 9 KB
  • 大小: 9.5 KB
  • 大小: 24.4 KB
  • 大小: 9.2 KB
  • 大小: 18.4 KB
分享到:
评论

相关推荐

    Windows系统编程之异步IO和完成端口.

    在Windows系统编程中,异步I/O和完成端口是高效处理并发I/O请求的关键技术。同步I/O模式中,用户线程会在内核处理I/O操作期间被挂起,导致资源利用率低下,特别是在处理大量并发请求时。而异步I/O则允许线程在I/O...

    异步I/O处理

    它与同步I/O形成鲜明对比,同步I/O在执行时会阻塞调用线程,直到I/O操作完成,而异步I/O则允许程序在等待I/O操作完成时继续执行其他任务,从而提高系统的整体效率。 在传统的同步I/O模型中,当一个线程发起读取或...

    windows下六种socket I/O模型示例

    异步I/O和完成端口则提供了最高的并发性和效率,但需要更复杂的编程技巧。 在Windows_Socket_IO模型的压缩包中,你可能会找到以下内容: - 阻塞I/O的示例代码,展示如何创建和使用一个简单的socket进行数据传输。 -...

    服务器编程-重叠IO原理

    本篇文章将从操作系统的角度讲述完成端口的原理,介绍Windows系统编程之异步I/O和完成端口。 一、同步I/O和异步I/O 在介绍异步I/O之前,先来认识下“异步IO”。异步IO很容易联想到同步I/O,对于同一个I/O对象句柄...

    MFC IOCP模型异步IO

    IOCP(I/O Completion Port,I/O完成端口)是Windows操作系统提供的一种高效的异步I/O模型,尤其适用于处理大量并发I/O操作。在这个场景中,"MFC IOCP模型异步IO"指的是使用MFC来实现基于IOCP的异步I/O操作。 IOCP...

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

    ### Windows Socket五种I/O模型详解 在计算机网络编程中,Windows Socket...而重叠I/O模型和完成端口I/O模型则更适合处理高并发连接的场景。理解这些模型的工作原理及其差异对于编写高效的网络应用程序至关重要。

    博客文章《完成端口详解》配套代码(压力测试客户端)

    《完成端口详解》配套代码,主要关注的是网络编程中的一个重要...同时,这也是一种很好的学习实践,能够提升对异步I/O和完成端口机制的理解。在实际开发中,掌握这些技术可以帮助我们构建出高并发、高性能的网络应用。

    介绍当前Windows支持的各种Socket I/O模型

    它使用完成端口来管理异步I/O操作,提供了非常高的性能。 **特点:** - 高性能,适合大规模并发应用。 - 复杂性较高,实现较为困难。 - 可以有效利用多核处理器资源。 **代码示例:** ```delphi var sock: ...

    IOCP完成端口编程

    总的来说,IOCP完成端口编程是Windows平台下进行高性能网络编程的重要技术,它通过异步I/O和线程池管理,为大规模并发提供了强大的支撑。学习和掌握IOCP编程,对于提升Windows服务器应用的性能和稳定性至关重要。

    Socket I/O 模型的使用示例

    - 重叠I/O是Windows特有的另一种异步I/O模型,使用`WSASend`和`WSARecv`函数,允许在I/O操作进行的同时执行其他计算任务。 - 通过配合I/O完成端口(IOCP)使用,可以实现高度并发的网络服务,特别适合服务器端开发...

    C++/MFC实现winsock io异步操作

    - **原理**:重叠I/O是Windows系统提供的高级异步I/O模型,它允许I/O操作在发出后立即返回,而无需等待操作完成。 - **使用结构**:使用`OVERLAPPED`结构体记录I/O操作的状态,并通过`WSASend`和`WSARecv`等函数...

    重叠iO和完成端口模型

    重叠I/O(Overlapped I/O)和完成端口(IOCP,I/O Completion Ports)模型是Windows操作系统中用于高效处理I/O操作的技术。在现代网络编程中,特别是涉及高性能服务器开发时,这两个概念至关重要。理解并正确使用...

    完成端口例子--改进

    完成端口是Windows系统中的一种I/O模型,...总的来说,这个“完成端口例子--改进”提供了一个学习和实践异步I/O和完成端口的好机会。通过深入研究,你可以提升自己的系统编程能力,更好地应对高并发网络服务的挑战。

    手把手教你玩转网络编程模型之完成端口(CompletionPort)篇

    1. **异步I/O处理**:完成端口采用异步I/O机制,这意味着它可以同时处理多个I/O操作,而无需等待任何一个操作完成。这样一来,服务器可以高效地处理大量并发连接,避免了同步I/O模型中常见的阻塞问题。 2. **减少...

    windows socket网络编程之iocp完成端口模型的例子

    总结来说,IOCP完成端口模型是Windows平台上进行高效并发网络编程的关键技术之一,它能够优雅地处理大量并发I/O操作,确保服务的稳定性和性能。通过阅读提供的文件和代码示例,可以更深入地理解并掌握这一技术。

    windows TCP/IP 网络编程(七)5种windows网络模型(5)完成端口

    完成端口是一种多线程并发模型,它允许系统通过一个共享的I/O端口处理来自多个套接字的I/O操作。这种模型提高了系统的并行性,降低了线程上下文切换的开销,特别适合处理大量并发连接的服务器。下面我们将详细讨论...

    重叠I/O实现的大文件读写

    IOCP是Windows系统提供的一个高级I/O模型,它允许多个线程共享一个I/O端口,并处理来自不同设备的多个重叠I/O操作。当一个I/O操作完成时,系统会将结果放入队列,由IOCP通知应用程序,从而避免了线程轮询的开销。 ...

    windows编程下的完成端口标准模板,移植性很高

    在Windows编程中,完成端口(Completion Port,简称IOCP)是一种高级的多线程并发I/O模型,它提供了一种高效、可扩展的方式来进行异步I/O操作。完成端口的标准模板通常是为了提高代码的复用性和移植性,使得在不同的...

    IOCP 完成端口及示例

    IOCP(I/O Completion Port,即I/O完成端口)是Windows操作系统提供的一种高效、可扩展的异步I/O模型,它被广泛应用于网络编程,尤其是高并发服务器的实现。在本文中,我们将深入探讨IOCP的基本原理、工作流程,并...

    完成端口Console源码

    完成端口(Completion ...总的来说,完成端口是一种强大的工具,它通过异步I/O和线程池优化了服务器的性能。通过学习和实践提供的Console源码,你可以掌握如何在实际项目中应用这一技术,提升你的Windows系统编程能力。

Global site tag (gtag.js) - Google Analytics