开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 11718|回复: 35
收起左侧

[技术专题] 《IOCP异步TCP服务器模型讲解与例程》

[复制链接]

结帖率:100% (2/2)
发表于 2016-1-17 21:59:04 | 显示全部楼层 |阅读模式   内蒙古自治区兴安盟
本帖最后由 VC大圣 于 2016-1-17 22:20 编辑
注意:阅读本文章需要有一定的winsock开发基础,例程源码在最下面↓

在众多的服务器模型中IOCP异步模型无疑是最高效的服务器模型,是其他模型无法比拟的,一些高效稳定的服务器基本都是用IOCP模型的 例如 Apache。


什么是IOCP?IOCP我们可以将它看成一个队列  I/O是一个硬件设备详情介绍请看百度百科。

我们先用普通同步模型的服务器结构做对比。

同步模型流程:
  • WSAStartup() 加载winsock服务
  • Socket()/WSASocket() 创建一个套接字
  • Listen() 设置监听模式
  • 建立一个线程进行无限循环的Accept()
  • Accept得到连接后建立一个线程与客户通讯


这样的流程很明显并不高效,因为每个客户都要创建一个线程与之通讯,当接受到大量的客户进入的时候就挂了。那么IOCP模型是怎样的呢?

IOCP模型流程:
  • WSAStartup() 加载winsock服务
  • Socket()/WSASocket() 创建一个套接字
  • CreateIoCompletionPort() 创建一个完成端口
  • Listen() 设置监听模式
  • CreateIoCompletionPort() 将sock与IOCP绑定
  • 建立Worker线程(具体这个线程中是干什么的看下面)
  • 建立Accept线程(为了更好的讲解暂时使用Accept,下面将介绍扩展的 AcceptEx)

这样看起来两个模型并没有什么区别,但重点在下面:

同步模型的Accept线程在得到一个客户连接后将会建立与其通讯的线程;
与同步模型的Accept线程不同的是IOCP模型的在得到客户连接后将会把客户的Socket绑定到IOCP然后投递一个WSARecv操作


IOCP的重点来了,同步模型的Recv操作是同步的,需要等待接到消息或者超时后才返回,但IOCP模型的是投递一个Recv操作  注意是投递!

那么为什么叫投递呢?  我们来对比 上面说了同步模型的Recv需要等待接到消息或者超时后才返回,而IOCP模型的则是投递一个WSARecv操作,这个WSARecv操作将会由系统来帮你完成;当系统接收到消息后将会把消息放到IOCP里面,我们只需要调用 GetQueuedCompletionStatus()来获取消息。

那么我们来看看Worker线程都干了些什么?

很简单 Worker线程只不过是在不断的调用GetQueuedCompletionStatus() 获取来自IOCP的消息,然后根据消息做出不同操作。这里我们暂时不提,因为我们还不知道他是如何获取消息的。

下面讲解 WSARecv() 和 GetQueuedCompletionStatus() 以及IOCP模型最重要的重叠结构!



WSARecv()  用于接收套接口的消息

参数1:s 客户的套接字
参数2:pBuffers  一个WSABUF结构 WSABUF 有2个成员 成员1:len 表明缓冲区长度 成员2:一个指针地址 指向接收数据的缓冲区  这个参数可以是一个数组 因为他可以一次接收多个消息。或者你也可以不传入数组到那下面的参数将要设置为1
参数3:wBufferCount WSABUF数组的数量
参数4:lpFlags  和用来控制套接字的行为 通常设置为0
参数5:lpOverlapped 重叠结构指针地址 重要! 下面讲解
参数6:lpCompletionRoutine 这个我们之间设置0 不管他
GetQueuedCompletionStatus() 用于接收IOCP的消息参数1:CompletionPort  IOCP
参数2:NumberOfBytesTransferred  整数型传址 用于接收IOCP得到的消息的长度
参数3:CompletionKey  这类似一个线程中的参数 是由 PostQueuedCompletionStatus()传递过来的
参数4:lpOverlapped   重叠结构指针地址
参数5:dwMilliseconds 超时时间 -1表示无限等待

重叠结构:
.数据类型 OVERLAPPED
.成员 Internal, 整数型
.成员 InternalHigh, 整数型
.成员 offset, 整数型
.成员 OffsetHigh, 整数型
.成员 hEvent, 整数型


这是基本的重叠结构 这些我们都不需要管,重叠结构之所以最重要是因为我们传递很多东西都要靠它了,例如接收的数据是 WSARecv操作还是AcceptEx操作或者WSASend操作,还有接收数据的缓冲区指针地址啊之类的,很多东西我们可以靠它来传送。

首先我们看 WSARecv() 他的参数5是一个重叠结构的指针地址,而我们使用的GetQueuedCompletionStatus()参数4
就是用来接收这个指针地址的,那么我们这样想 只要这个内存区没被释放,我们就可以操作他,那么我们可以在基本的重叠结构后面加上一些我们自己的数据,这样我们在使用GetQueuedCompletionStatus()得到重叠结构地址的时候那么我们的数据不都一样全都传过来了!  这是个非常好的东西。那么我们怎么用呢?


首先我们使用HeapAlloc来为程序分配一块内存,这块内存的长度我们根据要传入的数据来做决定 这里我们示例分配48字节的内存。

其结构:
OVERLAPPED 结构 20字节 这是固定的必须的 留给操作系统 我们不管
MsgType    消息类型,用于我们区分是AcceptEx投递的操作还是WSARecv等投递的操作 4字节 整数型
C_Socket   存放客户套接字 4字节 整数型
PeerAddr   远端客户的地址信息 16字节
Buf_Ptr    缓冲区的内存指针地址  4字节 整数型

好了,我们该投递WSARecv操作了。

WSARecv (C_Sock, WSABuf, 1, NumberOfBytesRecvd, Flags, Overlapped, 0)


我们这样的调用WSARecv来投递一个Recv操作,WSABUF结构中的 缓冲区指针地址成员我们可以使用HeapAlloc来分配一块内存 这里我们给他分配4096字节(即4KB)

我们再创建一个48字节的内存块 并写入 以上介绍的数据结构中的所需数据 再将这个内存块的地址放入WSARecv的参数lpOverlapped 这样我们就投递了一个Recv操作,接下来他将由系统完成。

在Worker线程中我们使用GetQueuedCompletionStatus()来获取IOCP中的消息,它得到的重叠结构的指针地址将与我们创建的一样 所以我们可以从这块内存中取出 我们事先写入的数据 我们先取出 PeerAddr 和C_Socket 得到这个客户的地址信息和sock,接下来我们取出 Buf_Ptr 我们之前在这里写入了一块内存的指针地址 并作为WSABUF中的成员传入了WSARecv的参数 这时 WSARecv所接收到的数据将写入这块内存。
然后我们需要再次投递一个WSARecv操作以便接收下个来自此客户的数据。


关于Worker线程的数量 一般认为最合适的是 CPU核心数×2 因为CPU只有那么些核心太多了Worker线程也没用,乘以2是因为如果某个Worker线程处在Sleep状态那么另一个便可以代替他,从而更好的利用CPU。

AcceptEx的使用:

Accept()与AcceptEx()最大的区别在于AcceptEx可以像WSARecv一样投递给IOCP 并且还能更好的利用Socket资源减少写创建Socket所浪费的时间。


AcceptEx()
参数1:sListenSocket  服务监听的套接字
参数2:sAcceptSocket  一个将用于新客户的套接字
参数3:lpOutputBuffer 一个内存缓冲区 这个内存缓冲区将用于接收客户的地址信息 和接收客户发来的第一条消息 (如果指定了的话) 这个参数必须指定 否则将返回错误
参数4:dwReceiveDataLength  指定欲接收来自新客户的第一条消息的缓冲区长度 若此值是零 则windows不会等待新客户的第一条消息 而是立即建立连接
参数5:dwLocalAddressLength  为本地地址信息保留的字节数。此值必须比所用传输协yi的最大地址大小长16个字节。
参数6:dwRemoteAddressLength为远程地址的信息保留的字节数。此值必须比所用传输协yi的最大地址大小长16个字节。 该值不能为0。
参数7:dwBytesReceived 此参数只在同步模式下起作用,所以我们直接给个0
参数8:lpOverlapped    重叠结构指针地址



使用了AcceptEx的话,我们就不必再去创建Accept的线程 而是直接投递一个Accept操作。当有客户连接的时候Windows会将消息写入IOCP。我们便可以从调用AcceptEx时指定的缓冲区使用GetAcceptExSockaddrs()取出客户的地址信息。在从重叠结构冲取出我们所需的信息即可。

当Worker接收到AcceptEx消息时,我们需要再次使用同样的方法投递一个Accept操作。
那么为什么说AcceptEx()能更好的利用Socket资源呢?
注意看AcceptEx的第二的参数 与Accept不同的是 Accept接受到连接系统会自动的为客户创建一个Socket 而AcceptEx则是由自己传入一个已创建的Socket 那么我们就可以使用Socket池技术 以减少Socket创建所浪费的时间
可惜的是本文章中的例程并未用到此技术。


PostQueuedCompletionStatus()的使用

PostQueuedCompletionStatus()可以向IOCP发送一条消息以便Worker线程做出相应的处理,例如我们要退出程序了而Worker线程还是处在无限循环状态,我们就可以通过这个函数发送消息给Worker让他退出。

PostQueuedCompletionStatus()
参数1:CompletionPort IOCP
参数2:dwNumberOfBytesTransferred 指定发送的消息的字节数
参数3:dwCompletionKey   这类似于线程的参数,将会传递到GetQueuedCompletionStatus()中的参数CompletionKey
参数4:lpOverlapped  重叠结构指针地址

同样的是Send操作也可以投递给系统去完成 函数声明如下
WSASend()
参数
s:标识一个已连接套接口的描述字。
lpBuffers:一个指向WSABUF结构数组的指针。每个WSABUF结构包含缓冲区的指针和缓冲区的大小。
dwBufferCount:lpBuffers数组中WSABUF结构的数目。
lpNumberOfBytesSent:如果发送操作立即完成,则为一个指向所发送数据字节数的指针。
dwFlags:标志位。
lpOverlapped:重叠结构指针
lpCompletionRoutine:一个指向发送操作完成后调用的完成例程的指针。(对于非重叠套接口则忽略)。


现在我们再回观全文 你是否发现了IOCP模型与同步模型的差距? 同步模型需要为每个客户都建立一个线程,这将极大的消耗系统资源! 而IOCP模型仅仅只需要一个或几个线程就可以完美的解决!

不懂的骚年可以提问,我尽量给予答复。作者:@小白熊  


附件: IOCP异步模型.rar (111.22 KB, 下载次数: 725)

点评

很是用心,我希望你能整个视频,那就更直观了!   湖南省怀化市  发表于 2017-9-2 03:22

评分

参与人数 5好评 +5 精币 +10 收起 理由
赵曰天 + 1 请不要进行人身攻J,请不要恶意灌水
默念、 + 1 + 4 猴哥就是吊
外星人群控 + 1 + 2 辛苦了
一听软件 + 1 + 2 感谢发布原创作品,精易因你更精彩!
oldlee + 1 + 2 不错,技术贴现在不多了

查看全部评分


本帖被以下淘专辑推荐:

结帖率:86% (6/7)
发表于 2016-1-17 22:57:54 | 显示全部楼层   天津市天津市
来支持下 这个好 研究下

评分

参与人数 1精币 +2 收起 理由
VC大圣 + 2 好评没了,给你个精吧

查看全部评分

回复 支持 反对

使用道具 举报

结帖率:0% (0/4)
发表于 2022-5-16 10:41:12 | 显示全部楼层   江苏省盐城市
这个好。值得学习。
回复 支持 反对

使用道具 举报

结帖率:0% (0/4)
发表于 2022-5-16 10:40:46 | 显示全部楼层   江苏省盐城市
666666666666666666
回复 支持 反对

使用道具 举报

结帖率:33% (1/3)

签到天数: 11 天

发表于 2019-8-20 23:28:16 | 显示全部楼层   四川省成都市
大佬你的这个模型服务端怎么把返回的数据传送回客户Duan
回复 支持 反对

使用道具 举报

结帖率:25% (1/4)
发表于 2019-5-30 22:08:08 | 显示全部楼层   河南省信阳市
猴哥就是吊
回复 支持 反对

使用道具 举报

结帖率:67% (10/15)

签到天数: 1 天

发表于 2019-5-18 21:07:43 | 显示全部楼层   广东省惠州市
大神啊,能不能写完啊,主题上的图片有服务器压力测试 ,为什么下载的源码服务端没有发送请求的代码啊,跪求
回复 支持 反对

使用道具 举报

结帖率:100% (4/4)

签到天数: 21 天

发表于 2019-2-16 17:24:59 | 显示全部楼层   江苏省徐州市
下载看看 希望能用上
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)
发表于 2018-12-4 07:26:52 | 显示全部楼层   广东省惠州市
客户Duan用什么都行嘛??
回复 支持 反对

使用道具 举报

结帖率:50% (1/2)

签到天数: 1 天

发表于 2018-8-21 16:14:32 | 显示全部楼层   四川省成都市
好用吗?远程????
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)
发表于 2018-5-23 17:53:16 | 显示全部楼层   广东省韶关市
学习下
回复 支持 反对

使用道具 举报

发表于 2018-3-19 19:19:00 | 显示全部楼层   湖南省长沙市
学习学习大神的杰作
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 诚聘英才| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 800073686,邮箱:800073686@b.qq.com
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表