DIOCP网络通讯流程

DIOCP 运作核心探密

原文连接:

http://blog.qdac.cc/?p=2362

原作者: BB

天地弦的DIOCP早已经广为人知了,有很多的同学都用上了它,甚至各种变异、修改版本也出了不少。我最近也在学习DIOCP,打算将它用于自己的服务端,今天让我们来一起探密它(DIOCP)的运作核心吧。

DIOCP作为对Windows的IOCP完成端口封装,拥有了很高的性能,经过对ECHO示例的测试,它能轻松应对几万连接和并发。网络通讯一般分为6大阶段:请求连接、接受连接、接收数据、处理数据、回复数据、断开连接,下面我就从这6大阶段入手,来看看DIOCP是如何实现的。

一、 请求连接

实际上这第一阶段由客户端发起,指定HOST和Port请求连接我们的DIOCP服务。

二、 接受连接

在第一阶段,客户端请求连接后,我们的DIOCP服务会收到一个连接请求,这时默认会接受连接。在iocp.Sockets中,可以看到我们的服务类TIocpCustomTcpServer,它继承自TIocpCustom,就是TIocpCustomTcpServer完成了这整个网络通讯的各种请求的管理。

TIocpCustomTcpServer是一个用户能直接使用的DIOCP服务端类,在TIocpCustomTcpServer被调用Open(或Start)方法后,它先是开启IOCP任务引擎IocpEngine,初始化监听Socket,绑定监听端口,开始监听并将Socket绑定到IOCP句柄。接下来它会初始化指定数量的请求接受对象,然后再调用TiocpAcceptExRequest.PostRequest(内部调用IocpAcceptEx),像望夫石一样的守候着监听端口,等着客户端的连接。有人可能会问了,任务引擎怎么知道任务是什么,让谁来处理?好吧,我们可以看看TiocpRequest,它内部有一个Foverlapped,在Create时,Foverlapped.iocpRequest被设定为Self, TiocpAcceptExRequest是继承自TiocpRequest的。在PostRequest方法中调用AcceptEx时,有一个参数就是@FOverlapped,在任务引擎中GetQueuedCompletionStatus函数会返回Overlapped,这下明白了吧。

监听Socket:用于监听客户连接请求,开启指定的端口进入侦听状态,并调用任务引擎中的IocpCore对象Bind自己的Socket句柄到任务引擎的IOCP句柄(实际就是调用CreateIoCompletionPort函数来实现),这样监听Socket就可以在接收到IO请求时,由内核将请求加入IOCP任务队列,在IOCP引擎的工作线程中就可以通过GetQueuedCompletionStatus函数来直接取到任务进行处理了。请求响应、分配工作线程都是由任务引擎完成的。

在监听Socket收到连接请求时,会对Request进行必要的初始化(如状态设置、记录工作开始时间等),然后调用Request的FonResponse或HandleResponse。这里会优先调用FonResponse,目的是如果有指定外部的响应函数,就完全由外部接管,这样的封装增加了整体的灵活性。

在TIocpAcceptExRequest.HandleResponse中,会调用getPeerInfo函数获取远程客户端的IP地址和当前连接通讯端口,再产生一个OnAcceptedEx事件。接着调用Owner(TiocpCustomTcpServer)的DoAcceptExResponse方法,这时如果设置了OnContextAccept事件,则会产生此事件,你可以在这个事件中确定是否接受连接,默认会接受连接。接受连接后,根据KeepAlive开关判断是否设置TCP心跳。再调用IocpCore.Bind将当前连接的SocketHandle绑定到IOCP端口,如果成功会调用Context的DoConnected方法,在DoConnected里面会为当前连接分配一个标识句柄(实际上是一个计数器),设置Active状态为True,添加到在线列表,然后产生OnContextConnected事件,并调用OnConnected方法(你可以在子类中在这个地方做额外的处理),Context将状态设置为连接成功状态,并请求接收数据。如果在建立连接的过程中发生了错误,会关闭当前连接,产生OnContextError事件。

另外,在TiocpCustomTcpServer中内设了一个连接请求管理器IocpAcceptorMgr,它内部有一个TIocpAcceptExRequest对象池,目的是为了提升性能。IocpAcceptorMgr还能控制并发的最大请求数,超过上限时不再立即接受连接,而是等待对像池有空闲的对象时才返回。这里实际上就是一个排队效果了。

三、 接收数据

在第二阶段,连接成功后会马上调用当前连接的PostWSARecvRequest方法,请求接受数据。在TiocpCustomContext中,本身会初始化一个TIocpRecvRequest对象,它的作用就是产生一个数据接收请求,并在收到请求时,在HandleResponse方法中进行初步处理。

先来看看PostWSARecvRequest,它的实现很简单,就是直接去调用TiocpRecvRequest. PostRequest。 PostRequest函数会调用系统WSARecv函数产生一个接收数据的请求。这个请求当然也是异步的。由于TiocpRecvRequest也是继承于TiocpRequest,所以在调用WSARecv时也通过Foverlapped参数将自己与一次IO事件绑定了,在任务引擎中接收到数据时,会自动进入TiocpRecvRequest的HandleResponse方法。如果PostRequest失败,会触发OnContextError事件。

在TiocpRecvRequest. HandleResponse中,如果前面没有出错,会调用DoReceiveData,触发Context.OnRecvBuffer虚方法和IocpTcpServer的OnDataReceived事件,在这两个地方,用户可以对数据进行处理。紧接着,HandleResponse函数会再次产生一个接收请求,用于接收新的数据。(必须的哦,比如1G的文件,显然不能一个包就发送完^_^)

四、 处理数据

我们在使用TiocpCustomTcpServer时,可以通过注册一个被重载OnRecvBuffer的Context类来处理数据,也可以在OnDataReceived事件中处理。整体来说这个封装还是很自由的。

五、 回复数据

在处理数据时,我们经常需要回复一些东西给客户端。以使用OnDataReceived处理数据为例,我们先看看这个事件的声明:

1

TOnBufferReceived = procedure(Context: TIocpCustomContext; buf: Pointer; len: Cardinal; ErrorCode: Integer) ofobject;

在事件中,有当前接收的数据缓冲区地址、长度,还有一个Context。我们要回复数据或是查看远程客户端的IP端口等信息,就需要用到它。使用Context.Send()函数就可以发送我们的数据了。Send函数有几个重载版本,其中有一个里面包含BufReleaseType参数的,是指定正要被发送的数据缓冲区释放方式,默认dtNone,即不管它。如果使用dtFreeMem和dtDispose,则分别是调用FreeMem或Dispose来自动释放缓冲区内存。

Send函数同样也是异步的,会立即返回。在内部实际上是产生一个PostWSASendRequest请求。当然你也可以直接调用PostWSASendRequest请求,Send只是一个简化使用的封装。在PostWSASendRequest函数中,首先通过调用Owner(TIocpTcpServer).GetSendRequest函数从FsendRequestPool池中获取一个请求对象,将数据与这个请求对象绑定,即调用SendRequest. SetBuffer函数来初始化。在初始化完成后,将这个请求加入待发送队列中,成功后立即调用CheckNextSendRequest函数一次。

为何要调用CheckNextSendRequest? 其实CheckNextSendRequest是一个触发函数,它会从队列中Pop一个发送请求,成功后再调用TiocpSendRequest. ExecuteSend函数,在ExecuteSend里面会再次判断要发送的数据长度是否为0,然后再通过内部的InnerPostRequest来真正产生一个发送IO请求。这里面是通过WSASend来产生IO请求的,由于TiocpSendRequest也是基于TiocpRequest,所以WSASend时使用的Foverlapped参数就将对象与本次IO事件绑定了。在系统内核发送完数据或出错时,任务引擎会自动调用这个请求的HandleResponse方法。

在TIocpSendRequest.HandleResponse中,如果数据发送失败会产生OnContextError事件,成功则调用Context的虚方法DoSendRequestCompleted。然后立即调用Conetext. PostNextSendRequest方法,处理队列中的下一个发送请求。从这里可以看出,我们在Send之后调用CheckNextSendRequest时,可能并不是当前投递的待发送请求被响应。我们Send的请求可能会在前面的请求HandleResponse后才真正发送。

在Send数据时,我们用到了队列,其目的一是保证数据的发送顺序,二是能通过设置队列的大小来增强系统的稳定性,三是我们随时能观测到服务的状态。另外我们还使用了发送请求对象池,用来提升性能。

六、断开连接

IOCP服务在与客户建立连接后,内部只在发生错误或系统退出的时候才主动断开连接。平常时候默认由客户端来断开。在处理数据时,我们也可以直接调用Context. Disconnect来断开当前连接。在调用Disconnect时,会关闭当前连接的Socket,产生OnContextDisconnected事件,调用Context. OnDisconnected虚方法,并从在线列表中删除这个连接。在删除时Context会被还回到ContextPool中。

我们可以在OnContextDisconnected事件,或重载的Context的OnDisconnected方法对断开连接作额外的处理。

需要注意的是,前面的接受连接、接收数据、回发数据等都是异步的,只有断开连接是立即的。

七、结束语

至此,本文已经差不多结束了。在上面我们分析了DIOCP整个网络通讯的运作流程和基本使用方法。通过这些分析,你会发现,DIOCP到现在的V5阶段,整体流程已经很合理高效了,至少我暂时没发现明显的沉余。至于优化我想的是,在Send拷贝时,可以将GetMem换成环形内存,降低内存碎片的产生。

另外,本文讲的DIOCP为自己修改后的版本,已经将原来diocp-v5中的diocp.sockets.pas和diocp.tcp.server、diocp.tcp.client合并,部分类名有些细小的改变。将在线列表、HashTable换成了我自己的TYXDHashMapLinkTable,它是一个将HashTable和双向链表综合起来的怪物,个人感觉还是比较好用的。

本文只是我个人的一些初步理解,必竞才学习Diocp三天(三天打鱼两天晒网 :( ),很可能存在一些错误,欢迎大家指正。

最后感谢弦弦哥,能将这么好的东西奉献给大家,真是辛苦了。

时间: 2024-10-27 07:56:21

DIOCP网络通讯流程的相关文章

C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示)

在程序设计中,涉及数据存储和数据交换的时候,不管是B/S还是C/S模式,都有这样一个概念:数据库服务器.这要求一台性能和配置都比较好的主机作为服务器,以满足数目众多的客户端进行频繁访问.但是对于一些数据交换的要求不主同,而且涉及到的通讯个体数目不多,如果还采用“一主机多客户机”的模式,便要求一台硬件配置良好而且软件上安装了相关数据服务软件,这样会造成硬件和软件上的很多不必要的成本,这时Socket在点对点的平行对象之间的网络通讯的优势就就发挥出来了. 其实对于Socket通讯来说,服务器和客户端

项目日志之基于Java socket的网络通讯

Java API网络类包中的Socket类是网络上运行的两个程序间双向通信的一端,它既可以接受请求,也可以发送请求,利用它可以较为方便的编写网络上数据的传递.我们打算通过Java中基于Socket的网络编程实现一个简单的网络通信程序.这就是我们团队项目(开发一款简单的通讯软件,其基本功能是实现一对一的网络信息通讯,并努力向一对多和多对多靠近)的主要内容. 一.Java socket通信开发原理(查阅资料): 服务器使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端

文档:网络通讯包结构(crc校验,加解密)

包结构: 包 对(datacrc+protoID+dataSize)组成的byte[] 进行crc计算而得到 对(数据内容)进行crc计算而得到 协议号 数据内容的字节长度 数据内容 字段 headcrc datacrc protoID dataSize data 类型 uint uint ushort ushort byte[] 字节数 4 4 2 2 dataSize crc校验 问:TCP协议中,底层做了校验,那通信时我们还有必要再进行有CRC或者其他校验吗? 答:tcp 是可靠的, 就是

QT5 网络通讯

QT5 TCP网络通讯 服务器与客户端建立连接listen() - connectToHost();  触发newPendingConnect信号 实时数据通讯write(); read();  触发readyRead信号 通讯主要使用的类: QTcpServer  Class QTcpServer类提供了一个基于TCP的服务器.这个类可以接受传入的TCP连接.您可以指定端口或让QTcpServer自动选择一个端口.您可以收听特定地址或所有机器的地址.调用listen()让服务器侦听传入的连接.

Android网络通讯简介

网络通信应该包含三部分的内容:发送方.接收方.协议栈.发送方和接收方是参与通信的主体,协议栈是发送方和接收方进行通信的契约.按照服务类型,网络通信可分为面向连接和无连接的方式.面向连接是在通信前建立通信链路,而通信结束后释放该链路.无连接的方式则不需要在通信前建立通信连接,这种方式不保证传输的质量. Android提供了多种网络通信的方式,如Java中提供的网络编程,在Android中都提供了支持.Android中常用的网络编程方式如下: 针对TCP/IP协议的Socket和ServerSock

[转] C#.Net Socket网络通讯编程总结

1.理解socket1).Socket接口是TCP/IP网络的应用程序接口(API).Socket接口定义了许多函数和例程,程序员可以用它们来开发TCP/IP网络应用程序.Socket可以看成是网络通信上的一个端点,也就是说,网络通信包括两台主机或两个进程,通过网络传递它们之间的数据.为了进行网络通信,程序在网络对话的每一端都需要一个Socket. 2).TCP/IP传输层使用协议端口将数据传送给一台主机的特定应用程序,从网络的观点看,协议端口是一个应用程序的进程地址.当传输层模块的网络软件模块

《连载 | 物联网框架ServerSuperIO教程》-4.如开发一套设备驱动,同时支持串口和网络通讯。附:将来支持Windows 10 IOT

感谢唯笑志在分享 原博主原地址:http://www.cnblogs.com/lsjwq/ 注:ServerSuperIO有可能被移植到Windows 10 IOT上,那么将来有可能开发一套设备驱动,可以支行在服务端.嵌入式设备中,将形成完整的解决方案.       现在已经调试通过部分代码,还得需要一段时间,一般都是晚上干,时间也有限.如下图: 目       录 4.如开发一套设备驱动,同时支持串口和网络通讯... 2 4.1           概述... 2 4.2          

网络--三种网络通讯方式及Android的网络通讯机制

Android平台有三种网络接口可以使用,他们分别是:java.net.*(标准Java接口).Org.apache接口和Android.net.*(Android网络接口).下面分别介绍这些接口的功能和作用. 1.标准Java接口 java.net.*提供与联网有关的类,包括流.数据包套接字(socket).Internet协议.常见Http处理等.比如:创建URL,以及URLConnection/HttpURLConnection对象.设置链接参数.链接到服务器.向服务器写数据.从服务器读取

个人第一个开源分布式项目distributeTemplate的实现三 网络通讯netty传输大文件

今天 我将讲讲网络通讯,这里我初始版本 由于采用的事Netty框架  所以 这里讲网络Netty在我们这里是怎么使用的,下周开始添加rpc lucene内容了 实现之后的0.2 0.3版本,后面将会去掉netty依赖 采用原生的NIO2 (aio) 异步非阻塞方式 实现自己网络通讯,也就是说 这部分可能会实现一个简单的但是比netty精简高效的网络框架,后期做出来 可能会单独开一个分支开源出来,netty说白了 就是 事件驱动 以及 NIO 加一些协议 以及 异常 处理,废话不多说了. 我最近