李超

cc编程笔记本。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  218 随笔 :: 5 文章 :: 51 评论 :: 4 引用

导言

写一个Windows平台下的应用程序大多时候都是离不开读写文件,网络通信的。
比如一个服务应用程序来说,它可能从网络适配器接受用户的请求,对请求进行处理计算,最终将用户端所需的数据返回,中间可能还涉及到对磁盘的读写,这些都是I/O操作,所以,要设计一个稳健的,高效的,伸缩性好的应用程序,就必须将Windows的I/O机制搞清楚。

一、 两种 读/写 机制

输入Input / 输出Output,有两种机制,他们是:

1 同步I/O: 线程执行一个输入输出函数时,输入输出工作执行完毕后,函数返回继续执行以后的代码。
2 异步I/O: 线程执行一个输入输出函数时,函数不等待读/写操作完成便立即返回,线程可先去执行下文,执行下文时可查询刚刚发起的读/写操作是否完成。

解读:

可以看出,异步I/O是更加高效的,同步I/O将会阻塞线程的执行,不应该被提倡。

有时候我们会认为,我们为每次执行I/O操作的时候,新开一个线程,让他去执行同步I/O函数,然后我们的原线程去执行工作,这样做是可以的,但是其实这样就是我们自己模拟实现了异步I/O,其实我们调用异步I/O函数的时候,操作系统内部也是维护了一个新线程去为我们进行I/O操作的。

但是,新建线程是一件很耗费CPU资源的事情,而且I/O操作时,CPU需要计算,需要从磁盘或网络适配器的缓冲区,将数据读取到内存中,或者将内存中的数据写到磁盘或网络适配器的缓冲区里,所以,I/O操作的线程不是睡眠的,而是忙碌的,所以我们得出了一个结论,如果我们的计算机上只有两个CPU,同时执行的I/O线程超过两个以上时,CPU会因为I/O线程不是睡眠的而分配给他们时间片,于是线程间的频繁切换,也会降低我们的应用程序的性能。

那么,我们得出结论:同时执行的I/O线程的数量不应该大于CPU数量,客户发起的I/O请求需要一个队列,每次并发处理的I/O操作应该等于CPU数量,如果并发处理的I/O请求数量太多,CPU切换过于频繁,会将CPU资源浪费在线程切换上,从而严重降低程序性能。

二、读/写 前后的工作:创建与释放要读/写的设备的内核对象

要对设备进行读/写,必须先知道创建一个用于该设备的读写的内核对象,在读/写之后,释放该内核对象。

1. 创建I/O内核对象的函数: CreateFile(...)  与其他内核对象一样,我们获取的仅仅是一个句柄。
2. 关闭I/O内核对象的函数: CloseHandle(...)

解读:

HANDLE CreateFile(
LPCTSTR lpFileName,    
//要读写的设备
DWORD dwDesiredAccess,  //访问模式:0(不读写,如改变设备配置)  GENERIC_READ,GENERIC_WRITE(可异或)
DWORD dwShareMode,      //共享模式:FILE_SHARE_DELETE FILE_SHARE_READ FILE_SHARE_WRITE
LPSECURITY_ATTRIBUTES lpSecurityAttributes,  //安全属性  里面可设置内核对象的继承性
DWORD dwCreationDistribution,  //创建方式  CREATE_NEW  CREATE_ALWAYS  OPEN_EXISTING  OPEN_ALWAYS  TRUNCATE_EXISTING
DWORD dwFlagsAndAttributes,    //文件属性和标志
HANDLE hTemplateFile    //文件属性模板,如指定了这个参数,则忽略上个参数中的文件属性
);


/*
参数 dwFlagsAndAttributes
1)文件属性:
  (FILE_ATTRIBUTE_)ARCHIVE COMPRESSED HIDDEN NORMAL OFFLINE READONLY SYSTEM TEMPORARY
2)标志:
  (FILE_FLAG_)WRITE_THROUGH OVERLAPPED NO_BUFFERING RANDOM_ACCESS SEQUENTIAL_SCAN DELETE_ON_CLOSE BACKUP_SEMANTICS POSIX_SEMANTICS
*/

这个CreateFile函数返回了一个文件内核对象句柄,这里说的文件不是狭义上的磁盘文件,也包括一些设备,比如串口,并口,邮件槽,命名管道和匿名管道,另外,有些设备句柄不是通过CreateFile创建的,下面是一个创建设备内核对象的函数的说明表。


设备            创建设备内核对象的函数

文件            CreateFile(pszName为路径名或UNC路径名)。

逻辑磁盘驱动器       CreateFile(pszName为\\.\x:)。x是盘符,打开驱动器可以格式化或者检测该驱动器的大小。

物理磁盘驱动器       CreateFile(pszName为\\.\PHYSICALDRIVEx)。x是物理驱动器序号,比如PHYSICALDRIVE0

串口            CreateFile(pszName为"COMx")

并口            CreateFIle(pszName为"LPTx")

邮件槽服务器        CreateMailslot(pszName为\\.\mailslot\mailslotname)

邮件槽客户端        CreateFile(pszName为\\servername\mailslot\mailslotname)

命名管道服务器       CreateNamedPipe(pszName为\\.\pipe\pipename)

命名管道客户端       CreateFile(pszName为\\servername\pipe\pipename)

匿名管道          CreatePipe

套接字           Socket,accept或AcceptEx

控制台           CreateConsoleScreenBuffer或GetStdHandle



以上这些函数,会创建一个I/O内核对象,并取得该对象的句柄值,然后我们可以调用与具体设备相关的函数,并传入得到的设备内核对象句柄,来和设备进行通信。

和其他内核对象一样,可以调用函数CloseHandle来关闭CreateFile创建的I/O内核对象。

BOOL CloseHandle(HANDLE hObject);


但是如果设备是套接字,就必须调用closesocket。

int closesocket(SOCKET s);

如果有一个设备句柄,可以通过GetFileType来查处具体的设备类型。
DWORD GetFileType(HANDLE hDevice);
返回值为:
描述
FILE_TYPE_UNKNOWN 未知类型
FILE_TYPE_DISK 磁盘文件
FILE_TYPE_CHAR 字符文件,一般是并口设备或者控制台设备
FIEL_TYPE_PIPE 指定的文件时一个命名管道或匿名管道




 
posted on 2009-10-10 20:27  coderlee  阅读(2360)  评论(0编辑  收藏