完成端口通讯服务器设计_IOCP Socket Server
socket通信流程及Delphi里ClientSocket和ServerSocket

Delphi里ClientSocket 和ServerSocket2020-07-27 20:55:44| 分类: | 标签:|字号定阅ClientSocket 和ServerSocket几个重要的属性:1.client和server都有port属性,需要一致才能相互通信2.client有Address属性,利历时填写对方(server)的IP地址几个重要的事件:client: OnRead事件,当client受到冲击消息时在OnRead事件中能够取得server 发送过来消息。
Server: OnClientRead事件,与上述client的作用相同发送信息:clien利用SocketClient1.Socket.SendBuf(char类型的数组,信息长度);server利用SocketServer1.Socket.Connection[0].SendBuf(char类型的数组,信息长度);接收信息clien利用SocketClient1.Socket.ReceiveBuf(char类型的数组,信息长度);server利用SocketServer1.Socket.Connection[0].ReceiveBuf(char类型的数组,信息长度);利用socketsSocket 控件让你成立一个利用TCP/IP和有关的协议与其他系统进行通信的应用。
利用Sockets,你能够读和写通过它连接的其他机械,而不用担忧实际的网络软件的相关细节。
Sockets提供基于TCP/IP协议的连接。
除此之外还能专门好的工作,在其他相关的协议。
Delphi 提供你写网络效劳器或客户应用程序去读和写其他的系统。
一个效劳或客户程序通常专注于一个单一的效劳如超文本传送协议(HTTP)或文件传输协议(FTP)。
利用server sockets,一个应用程序能够提供这些效劳中的一个去连接一个希望利用效劳的客户程序。
Client sockets许诺一个应用利用这些效劳中的一个去连接提供那个效劳的效劳应用。
c#AcceptEx与完成端口(IOCP)结合的示例

c#AcceptEx与完成端⼝(IOCP)结合的⽰例⽬录前⾔为什么要⽤AcceptExIocpAcceptEx外部功能说明实现步骤说明后记前⾔在windows平台下实现⾼性能⽹络服务器,iocp(完成端⼝)是唯⼀选择。
编写⽹络服务器⾯临的问题有:1 快速接收客户端的连接。
2 快速收发数据。
3 快速处理数据。
本⽂主要解决第⼀个问题。
AcceptEx函数定义BOOL AcceptEx(SOCKET sListenSocket,SOCKET sAcceptSocket,PVOID lpOutputBuffer,DWORD dwReceiveDataLength,DWORD dwLocalAddressLength,DWORD dwRemoteAddressLength,LPDWORD lpdwBytesReceived,LPOVERLAPPED lpOverlapped);为什么要⽤AcceptEx传统的accept函数能满⾜⼤部分场景的需要;但在某些极端条件下,必须使⽤acceptEx来实现。
两个函数的区别如下:1)accept是阻塞的;在⼀个端⼝监听,必须启动⼀个专⽤线程调⽤accept。
当然也可以⽤迂回的⽅式,绕过这个限制,处理起来会很⿇烦,见⽂章单线程实现同时监听多个端⼝。
acceptEx是异步的,可以同时对很多端⼝监听(监听端⼝的数量没有上限的限制)。
采⽤迂回的⽅式,使⽤accept监听,⼀个线程最多监听64个端⼝。
这⼀点可能不是AcceptEx最⼤优点,毕竟同时对多个端⼝监听的情况⾮常少见。
2)AcceptEx可以返回更多的数据。
a)AcceptEx可以返回本地和对⽅ip地址和端⼝;⽽不需要调⽤函数getsockname和getpeername获取⽹络地址了。
b)AcceptEx可以再接收到⼀段数据后,再返回。
这种做法有利有弊,⼀般不建议这样做。
3)AcceptEx是先准备套接字(socket)后接收。
C++Socket编程—socket网络模型之IOCP

C++Socket编程—socket⽹络模型之IOCP⽹络模型—IOCP模型⼀. 什么是IOCP?什么是IOCP模型?IOCP模型有什么作⽤?1) IOCP(I/O Completion Port),常称I/O完成端⼝。
2) IOCP模型属于⼀种通讯模型,适⽤于(能控制并发执⾏的)⾼负载服务器的⼀个技术,适⽤于⼤型项⽬,处理⾼并发问题。
3) 通俗⼀点说,就是⽤于⾼效处理很多很多的客户端进⾏数据交换的⼀个模型。
4) 或者可以说,就是能异步I/O操作的模型。
⼆. IOCP ⼯作机制尽管select、WSAA、WSAE这些socket通信模型可以让我们不⽤开更多的线程来处理每⼀连接,但它们收发数据时仍然要调⽤Recv和Send,Recv和Send实际上仍然会与操作系统底层进⾏交互,仍然会进⼊内核,所以还是会有效率上的损失。
IOCP怎么解决这个问题呢?IOCP有⼀个队列,当你要发数据时,收数据和连接时,都交由IOCP队列处理,不会与操作系统底层交互。
发送数据时,先将缓冲区和长度封好,这个请求会发送到IOCP队列,IOCP内部会帮你把请求发出去。
收数据时,收数据的请求丢掉IOCP队列,IOCP会将收到的数据填⼊指定的缓冲区⾥边,当数据收好后会通知你来收数据。
建⽴连接时,IOCP帮你把连接建⽴好,告诉你新的连接已经来了。
开发者使⽤IOCP时⽆需关注数据收、发、连接,只需关注处理数据三. IOCP的存在理由(IOCP的优点)及技术相关有哪些?IOCP是⽤于⾼效处理很多很多的客户端进⾏数据交换的⼀个模型,那么,它具体的优点有些什么呢?它到底⽤到了哪些技术了呢?在Windows环境下⼜如何去使⽤这些技术来编程呢?它主要使⽤上哪些API函数呢?1) 使⽤IOCP模型编程的优点①帮助维持重复使⽤的内存池。
(与重叠I/O技术有关)②去除删除线程创建/终结负担。
③利于管理,分配线程,控制并发,最⼩化的线程上下⽂切换。
异步io、apc、io完成端口、线程池与高性能服务器

异步IO、APC、IO完成端口、线程池与高性能服务器背景:轮询PIO DMA 中断早期IO设备的速度与CPU相比,还不是太悬殊。
CPU定时轮询一遍IO设备,看看有无处理要求,有则加以处理,完成后返回继续工作。
至今,软盘驱动器还保留着这种轮询工作方式。
随着CPU性能的迅速提高,这种效率低下的工作方式浪费了大量的CPU时间。
因此,中断工作方式开始成为普遍采用的技术。
这种技术使得IO设备在需要得到服务时,能够产生一个硬件中断,迫使CPU放弃目前的处理任务,进入特定的中断服务过程,中断服务完成后,再继续原先的处理。
这样一来,IO设备和CPU可以同时进行处理,从而避免了CPU等待IO完成。
早期数据的传输方式主要是PIO(程控IO)方式。
通过对IO地址编程方式的方式来传输数据。
比如串行口,软件每次往串行口上写一个字节数据,串口设备完成传输任务后,将会产生一个中断,然后软件再次重复直到全部数据发送完成。
性能更好的硬件设备提供一个FIFO(先进先出缓冲部件),可以让软件一次传输更多的字节。
显然,使用PIO方式对于高速IO设备来说,还是占用了太多的CPU时间(因为需要通过CPU编程控制传输)。
而DMA(直接内存访问)方式能够极大地减少CPU处理时间。
CPU仅需告诉DMA控制器数据块的起始地址和大小,以后DMA控制器就可以自行在内存和设备之间传输数据,其间CPU可以处理其他任务。
数据传输完毕后将会产生一个中断。
同步文件IO和异步文件IO下面摘抄于MSDN《synchronous file I/O and asynchronous file I/O》。
有两种类型的文件IO同步:同步文件IO和异步文件IO。
异步文件IO也就是重叠IO。
在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。
而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO 操作完成了。
Java与完成端口IOCP

Java与完成端口IOCPJava与完成端口IOCP传统的Server/Client实现是基于Thread per request,即服务器为每个客户端请求建立一个线程处理,单独负责处理一个客户的请求。
大多数的网络游戏的服务器都会选择非阻塞select这种结构,为什么呢?因为网络游戏的服务器需要处理的连接非常之多,并且大部分会选择在Linux/Unix下运行,那么为每个用户开一个线程实际上是很不划算的,一方面因为在Linux/Unix下的线程是用进程这么一个概念模拟出来的,比较消耗系统资源,另外除了I/O之外,每个线程基本上没有什么多余的需要并行的任务,而且网络游戏是互交性非常强的,所以线程间的同步会成为很麻烦的问题。
由此一来,对于这种含有大量网络连接的单线程服务器,用阻塞显然是不现实的。
iocp,在linux下使用epoll关于线程是这样的,肯定不可能一个用户一个线程的,没见过那么做的,通常我们是这样的,我们创建几个线程分别用于发送和接收网络消息,当然数量也不是太多,通常是CPU个数的2倍,然后另外建立一个逻辑线程,所有的网络线程接收到的数据都会打入这个逻辑线程,以保证逻辑处理中的顺序处理. 不知道你是否理解. 另外一个小提示,QQGAME可不是一个进程就500个连接,通常他一个进程都会达到20000左右的连接数.NIO服务器最核心的一点就是反应器模式:当有感兴趣的事件发生的,就通知对应的事件处理器去处理这个事件,如果没有,则不处理。
所以使用一个线程做轮询就可以了。
当然这里这是个例子,如果要获得更高性能,可以使用少量的线程,一个负责接收请求,其他的负责处理请求,特别是对于多CPU时效率会更高。
JDK 7,WEB服务器 Tomcat、Jetty等,在Windows下,Java将可以使用IOCP,而不是现在nio所用的select,网络并发性能将会得到大幅度提升。
在Linux下则应该改变不多,毕竟linux现在并发最好性能的网络I/O EPOLL,JDK 6.0 nio包含5.0的后续版本的缺省实现就是epoll。
java socketioserver类的方法

Java中的Socket.IO是一个用于实现实时双向通信的库,它基于WebSocket协议,可以在客户端和服务器之间建立持久的连接。
在Java中,我们可以使用SocketIOServer类来创建和管理Socket.IO 服务器,通过该类的方法可以实现各种服务器端的功能。
本文将介绍SocketIOServer类的一些常用方法,帮助读者更好地了解和使用Socket.IO在Java中的实现。
一、创建Socket.IO服务器在使用Socket.IO之前,我们需要先创建一个Socket.IO服务器。
SocketIOServer类提供了创建服务器实例的方法,示例代码如下:```javaConfiguration config = new Configuration();config.setHostname("localhost");config.setPort(9092);SocketIOServer server = new SocketIOServer(config);```上述代码中,我们首先创建了一个Configuration实例来配置服务器的主机名和端口号,然后通过SocketIOServer类的构造函数创建了一个服务器实例。
通过这样的方式,我们就可以在Java中创建一个Socket.IO服务器,为后续的通信提供支持。
二、服务器端事件的处理在Socket.IO服务器上,我们通常需要处理一些事件,例如连接事件、断开事件、自定义事件等。
SocketIOServer类提供了一系列方法来注册和处理这些事件,示例代码如下:```javaserver.addConnectListener(client -> {System.out.println("客户端连接:" + client.getSessionId()); });server.addDisconnectListener(client -> {System.out.println("客户端断开连接:" + client.getSessionId()); });server.addEventListener("chat message", String.class, (client, data, ackRequest) -> {System.out.println("收到消息:" + data);});```上述代码中,我们使用addConnectListener方法和addDisconnectListener方法分别注册了客户端连接和断开连接的事件处理函数,使用addEventListener方法注册了一个名为"chat message"的自定义事件的处理函数。
socket服务器 方案

socket服务器方案一、概述Socket服务器是一种基于Socket通信机制实现的服务器,它可以接收客户端的连接请求并进行相应的处理。
Socket服务器有着广泛的应用场景,如聊天室服务器、文件服务器、游戏服务器等。
在设计和实现Socket服务器时,需要考虑到以下几个方面:网络层面、通信协议、并发处理、安全性等。
二、网络层面在网络层面,Socket服务器需要考虑到以下几个方面。
1. IP地址和端口号:Socket服务器需要绑定一个IP地址和一个端口号,以便客户端能够与服务器建立连接。
在选择IP地址时,可以考虑使用本机的IP地址或者使用0.0.0.0来监听所有网络接口。
在选择端口号时,需要确保该端口号没有被其他程序占用。
2. 连接管理:Socket服务器需要管理客户端的连接,包括接受新的连接请求、关闭已有的连接等。
一般来说,服务器会监听一个固定的端口,并采用多线程、多进程或异步IO等方式来处理客户端的连接请求。
三、通信协议通信协议是Socket服务器与客户端之间进行通信所遵循的规则。
常见的通信协议有TCP、UDP等。
1. TCP协议:TCP(Transmission Control Protocol)是一种面向连接的协议,它提供可靠的、面向字节流的通信服务。
在TCP协议下,服务器与客户端之间通过建立连接来进行通信,通过TCP的流式传输方式,确保数据不丢失、不失序。
2. UDP协议:UDP(User Datagram Protocol)是一种无连接的协议,它提供了一种不可靠的、基于数据报的通信方式。
在UDP协议下,服务器与客户端之间不需要建立连接,直接发送数据报进行通信。
由于UDP协议不保证数据的可靠性,因此在实现Socket服务器时需要考虑到数据的丢失和重复等问题。
四、并发处理在Socket服务器中,需要处理多个客户端的同时连接和通信。
为了能够高效地处理并发连接,可以采用以下几种方式。
1. 多线程:可以为每个客户端的连接创建一个线程来处理。
IOCP完成端口详解(10年吐血大总结)

IOCP完成端口超级详解目录:1.完成端口的优点2.完成端口程序的运行演示3.完成端口的相关概念4.完成端口的基本流程5.完成端口的使用详解6.实际应用中应该要注意的地方一.完成端口的优点1. 我想只要是写过或者想要写C/S模式网络服务器端的朋友,都应该或多或少的听过完成端口的大名吧,完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S 通信模式中性能最好的网络通信模型,没有之一;甚至连和它性能接近的通信模型都没有。
2. 完成端口和其他网络通信方式最大的区别在哪里呢?(1) 首先,如果使用“同步”的方式来通信的话,这里说的同步的方式就是说所有的操作都在一个线程内顺序执行完成,这么做缺点是很明显的:因为同步的通信操作会阻塞住来自同一个线程的任何其他操作,只有这个操作完成了之后,后续的操作才可以完成;一个最明显的例子就是咱们在MFC的界面代码中,直接使用阻塞Socket调用的代码,整个界面都会因此而阻塞住没有响应!所以我们不得不为每一个通信的Socket都要建立一个线程,多麻烦?这不坑爹呢么?所以要写高性能的服务器程序,要求通信一定要是异步的。
(2) 各位读者肯定知道,可以使用使用“同步通信(阻塞通信)+多线程”的方式来改善(1)的情况,那么好,想一下,我们好不容易实现了让服务器端在每一个客户端连入之后,都要启动一个新的Thread和客户端进行通信,有多少个客户端,就需要启动多少个线程,对吧;但是由于这些线程都是处于运行状态,所以系统不得不在所有可运行的线程之间进行上下文的切换,我们自己是没啥感觉,但是CPU却痛苦不堪了,因为线程切换是相当浪费CPU时间的,如果客户端的连入线程过多,这就会弄得CPU都忙着去切换线程了,根本没有多少时间去执行线程体了,所以效率是非常低下的,承认坑爹了不?(3) 而微软提出完成端口模型的初衷,就是为了解决这种"one-thread-per-client"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,这种神奇的效果具体是如何实现的请看下文。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
完成端口通讯服务器设计 (IOCP Socket Server)第一章:是谁神化了IOCPWindows系统下的socket模型有多种,其中完成例程的效率也是相当高的,其它的也不差(相关模型知识这里不多做介绍,读者可以自己搜索或查阅有关资料)。
但是不知道为什么,一提起IOCP就会有很多人质疑:IOCP真的有这么神话吗?尽管质疑,依然有很多人还是在茫茫网络中苦苦寻找一个完整的IOCP源码,希望能够对了解IOCP起到事半功倍的作用,不过得到的大多也只是残缺不全的。
什么是IOCP?IOCP的机制是什么?IOCP有怎样的性能?当一个人深入了解IOCP以后,才解开了它神话之谜:其实它没有什么神话。
很多人之所以质疑IOCP,说出上面那句话的时候,其实是他正在神化IOCP,主要是因为对IOCP不了解,甚至不知道。
所以,是谁神化了IOCP呢?是那些不了解IOCP 但又想了解却没有进展的人。
IOCP主要针对数据吞吐量和连接并发量而设计。
有些人使用IOCP,做的却是堵塞模式的事情:对每个连接自己建立一个发送队列,每次才投递一个发送请求给IOCP,等该请求已决后才又出列一个再投递给IOCP。
任何一个服务器,能达到怎样的性能,对设计者的要求也是苛刻。
根据服务器对性能要求,合理利用通讯模型,才是设计者的关键。
如果在一个只有100个终端且每个终端每10秒才发送一个数据包的服务器系统里,用什么Socket模型都一样,甚至用Win98系统做都可以。
对于一个服务器而言,需要设计者对内存管理,对网络状况,对操作系统等等都要有深入了解,并具有深厚的技术功底。
否则,还会产生更多神化IOCP的人。
服务器性能,系统支持是基础,设计者水平是关键。
而这个水平条件,没有一个衡量的最终标准,它是永无止境的,会随着时间和经验的积累不断提高。
第二章:内存管理(AWE)有牛人曾经说过,服务器玩的就是内存。
仔细想想,确实是如此。
服务器对内存的需求是巨大的,对内存的要求也是苛刻的。
如何在内存管理上下功夫使服务器性能达到一个质的飞跃,是服务器设计中的首要解决的问题。
说到内存,我想刚开始设计服务器的人会说,不就申请释放吗,有什么难呢。
从操作步骤来说,确实就这么两个,没有再多了的工作了。
当我们采用虚拟内存分配或堆分配从操作系统获取内存的时候,总以为我们获得了足够的内存就可以让服务器安心工作了。
但事情并未就这么简单,操作系统在一定条件下,还可以征用已经分配给你的物理内存,它会将你的物理内存数据复制到页交换文件中,然后把本来给你的物理内存再分配给别的进程,当你的进程访问你所获得的虚拟地址集的数据时,它会再找个空(或许也是从别的进程征用)的物理内存,再从页交换文件里面调出你原来的数据放回到新的物理内存里面,并将这个物理内存映射到你申请的虚拟内存地址集内(有关这项内容请参考操作系统的内存管理)。
这个过程是相当耗费CPU资源且十分缓慢的,尤其是对硬盘虚拟内存文件的读写。
其它大道理本文不多说,关于操作系统内存管理的原理可以从《Windows核心编程》、《Windows操作系统》、《操作系统》等书籍上了解。
我们可以使用lookaside lists技术来重新使用已经分配的内存的,或者使用SetWorkingSetSize来设置标志告知操作系统不要交换我的内存,但不外乎多一次操作而已。
这个操作到底消耗多少的CPU资源,本人也没有考究过,但从性能要求的角度来说,多一事不如少一事。
本文讨论的内存管理,将采用AWE(地址窗口化扩展)的技术,将申请到的物理内存保留为非分页内存,这部分的内存不会被页交换文件所交换,关于AWE请参阅以上提到的书籍。
(下面提到的“内存管理”,将仅针对应用程序自己的内存管理功能模块(下文称之为内存管理器)而言,已非上面提到的操作系统的内存管理。
)衡量内存管理器性能的有两个,一个是内存分配时的效率(分配效率),另一个内存交还时的效率(释放效率),亦即二者操作的时间性,这个时间越短那么可以认为它的效率越高。
下面的讨论,假定内存管理器是以页为最小分配单位,至于页的大小是多少才合适,稍后再说。
先谈分配效率(下面提到方法仅为本人归纳后的方法,不是学术上的算法):1、单链表型也就是将所有空闲内存块(即空闲内存碎片,下称空闲碎片)组成一个空闲碎片链表。
当提出内存分配申请的时候,从这个链表头遍历查找合乎要求的内存或从大的碎片里面分割出来。
这个方式简单,但如果空闲碎片多而且小于申请要求的时候,就需要做众多的循环操作。
单链表型排列方式可分为:a按地址高低排列 nb安碎片由小到大排列c不排列(先进先出)2、多链表型事实上,多链表型就是将上述按b方式排列的单链表,根据一定的大小档次截断而组成的多个链表集(相关算法请参阅上文提到的书籍)。
多链表我将之分为a、b型。
a型如下表:从上表我们看到,0~<4K的ListA有8个碎片节点;4K~<8K的ListB有4个碎片节点,分别是:4K、6K、7K、7K;8K~<16K的ListC为空;16K~<24K的ListD有4个碎片节点,分别是:16K、19K、22K、23K。
假如要分配一个5K的内存,就可以直接从ListB查找(如果ListB为空,则继续向更大空间的链表查找)直到底部的ListN。
这样就可以避免遍历ListA,假如ListA碎片极多的情况下那么就可以节省更多的时间。
b型如下表右边的情况不多说,基本和a型的一致。
那么左边的映射表是怎么回事呢?映射表始终没有空的,如果ListA不为空,那么a就指向ListA,如果ListA为空,那么它再指向ListB,以此类推直到底部的ListN。
如果要分配9K 内存,直接从c取链表头,实际上它指向的ListN,这样当N =1000+的时候,节省的时间就和a型相比就非常可观了。
再看看释放效率:1针对内存分配方案的第1种类型释放步骤为:a找到比释放内存块的地址低的并是相连接的空闲碎片然后与之合并再重新排列;b不管a成立与否,再找比释放内存块的地址高的并是相连接的空闲碎片然后与之合并再重新排列;c在a和b不成立的情况,则按排列规则插入空闲碎片链表。
上述步骤中,我们发现空闲碎片是一个巨量级的时候效率及其低下。
2针对内存分配方案第2种类型a从ListA到ListN找到比释放内存块的地址低的并是相连接的空闲碎片然后与之合并再重新排列;b不管a成立与否,再从ListA到ListN找比释放内存块的地址高的并是相连接的空闲碎片然后与之合并再重新排列;c在a和b不成立的情况,根据释放内存的大小找到归档链表,按排列规则插入该空闲碎片链表。
在这个情况中,工作量比起1更加大的多。
3内存块链表法这个链表不是指上面所说的空闲碎片链表,而是所有的内存不管空闲的或是使用的,按地址由低到高排列的双向内存块链表。
当然,我们不能在释放的时候再去排列所有的内存块,这样的话效率也是相当低的。
如何排列这个链表,可以从分配的时候下功夫:pBlock为空闲的碎片块……if(pBlock->dwSize > dwSize){//如果空闲块的大小大于要分配的//从大的里面切出一块来使用,该块容量减少pBlock->dwSize -= dwSize;//返回分配的地址Result = (PGMEM_BLOCK)pBlock->pAddr;//该空闲块向后指向新的空闲地址pBlock->pAddr = (char*)Result + dwSize;//获取一个新节点PGMEM_BLOCK pTmp;pTmp = pmbGMemNodePool;pmbGMemNodePool = pmbGMemNodePool->pmbNext;//将新分配出去的块插在该空闲块前面pTmp->pAddr = Result; //分配出去的内存块地址pTmp->dwSize = dwSize; //分配出去的内存块大小pTmp->pmbNext = pBlock; //下个指针指向被分割的空闲块pTmp->pmbPrior = pBlock->pmbPrior;if(pTmp->pmbPrior)pTmp->pmbPrior->pmbNext = pTmp;pBlock->pmbPrior = pTmp;……}根据上述代码,在分配的时候只需很少的代码量就可以完成了排列要求,这个小小的开支,就可以在释放的时候起到非常高的效率:从上图看到,内存块链表顺序是:ABCDEFG,空闲块链表顺序是:EBG。
通过要释放的块的地址经过计算(FreeAddr –AddrHead)/ PageSize得出该块在页标志的位置,就可以找到要释放的内存块在内存块链表中的位置是F,通过上下指针就可以知道与F相邻的两个块是EG,根据标志判断是否为空闲块,然后通过双向链表操作来合并这个三个块,这样几乎不要任何遍历操作就可以轻松完成了释放合并的操作。
当然如果相邻两个块都没有空闲的,则按排列规则插入空闲块队列。
估计没有比这个更好的释放合并内存的算法了。
经过了上面的方法介绍,这个时候我们应该清楚我们该做什么了吧。
如果采用内存分配第2种“多链表b型”的方案和内存释放第3种“内存块链表法”的方案,那么这个内存管理器一定是优越的吧。
于是开始噼里啪啦的编码……发现一个头痛的问题,即使把内存释放的工作让独立线程来处理,空闲内存块的排列依然消耗很多时间。
这个时候不禁问:我们到底在干什么?用这么强大的内存管理器来做操作系统吗?不,我们不是做操作系统,那还有什么方法让内存管理更为简单呢?来看下表,一个应用服务器对内存动态需求量的统计表:看了上面这个表终于明白,原来在内存需求中类型为F和G的最多,似乎豁然开朗,假定内存足够的情况下将内存管理器的页大小设置为512B,那么即使空闲内存块是不排列的,分配内存的效率也是极高的。
但对类型D这个使用频率最高的内存需求怎么解决呢?任何只想使用一个内存管理器就能做好服务器的想法是错误的。
针对很多种内存需求,必须加以分类。
针对这个4K内存需求,可以使用一个固定大小的内存池来解决,只要是这个内存类型需求的都要在这个内存池操作,比如Socket I/O操作的缓冲区等(将在以后介绍)。
通过以上讨论,采用内存分配方案的第1种类型c方式排列和内存释放方案第3种类型,来满足不固定大小的动态内存分配需求,即可一定程度上达到对性能要求的目的。
当然,使用内存分配方案的第2种b类型的,也未必不可,多消耗些CPU资源而已。
内存管理没有绝对的方法,所以上述内容仅仅是讨论如何根据内存需求设计内存管理器。
例程截图:源码说明:例程源码下载地址:/source/1607811GMem.cpp和GMem.h是内存管理单元的源码文件,欢迎指正。