完成端口通讯服务器设计_IOCP Socket Server

合集下载

socket通信流程及Delphi里ClientSocket和ServerSocket

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)结合的示例

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

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完成端口、线程池与高性能服务器

异步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与完成端口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 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服务器时,需要考虑到以下几个方面:网络层面、通信协议、并发处理、安全性等。

二、网络层面在网络层面,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完成端口详解(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"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,这种神奇的效果具体是如何实现的请看下文。

完成端口

完成端口

Windows socket之IO完成端口(IOCP)模型开发IO完成端口是一种内核对象。

利用完成端口,套接字应用程序能够管理数百上千个套接字。

应用程序创建完成端口对象后,通过指定一定数量的服务线程,为已经完成的重叠IO操作提供服务。

该模型可以达到最后的系统性能。

完成端口是一种真正意义上的异步模型。

在重叠IO模型中,当Windows socket应用程序在调用WSARecv函数后立即返回,线程继续运行。

另一线程在在完成端口等待操作结果,当系统接收数据完成后,会向完成端口发送通知,然后应用程序对数据进行处理。

为了将Windows打造成一个出色的服务器环境,Microsoft开发出了IO完成端口。

它需要与线程池配合使用。

服务器有两种线程模型:串行和并发模型。

串行模型:单个线程等待客户端请求。

当请求到来时,该线程被唤醒来处理请求。

但是当多个客户端同时向服务器发出请求时,这些请求必须依次被请求。

并发模型:单个线程等待请求到来。

当请求到来时,会创建新线程来处理。

但是随着更多的请求到来必须创建更多的线程。

这会导致系统内核进行上下文切换花费更多的时间。

线程无法即时响应客户请求。

伴随着不断有客户端请求、退出,系统会不断新建和销毁线程,这同样会增加系统开销。

而IO完成端口却可以很好的解决以上问题。

它的目标就是实现高效服务器程序。

与重叠IO相比较重叠IO与IO完成端口模型都是异步模型。

都可以改善程序性能。

但是它们也有以下区别:1:在重叠IO使用事件通知时,WSAWaitForMultipleEvents 只能等待WSA_MAXIMUM_WAIT_EVENTS(64)个事件。

这限制了服务器提供服务的客户端的数量。

2:事件对象、套接字和WSAOVERLAPPED结构必须一一对应关系,如果出现一点疏漏将会导致严重的后果。

完成端口模型实现包括以下步骤:1:创建完成端口2:将套接字与完成端口关联。

3:调用输入输出函数,发起重叠IO操作。

完成端口详细解析

完成端口详细解析

关于完成端口(IOCP)的文章汇总- [C/C++]版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明/logs/32007489.html首先讨论一下I/O Completion Ports试图解决什么样的问题。

写一个IO Intensive服务器程序,对每一个客户请求生成一个新的child process/worker thread来处理,每个process/thread使用同步IO,这是最经典古老的解法了。

在这之上的改进是prefork 多个process 或者使用线程池。

(使用process或thread,原理都差不多,thread的context switch花销要比process switch要小。

为了论述简单,下面只讨论线程。

)这种结构的并发性并不高,哪怕你用C++, C甚至汇编来写,效率都不会很高,究其原因,在于两点:一.同步IO,每个线程大多数时间在等IO request的结束。

IO相对于CPU,那是极极慢的。

我翻了翻手里的Computer Architecture, A Quantitative Approach第二版,1996年出的,里面对CPU Register, CPU Cache, RAM, Disk,列的access time如下:Java代码1.Registers: 2-5 nano seconds2.CPU Cache: 3-10 nano seconds3.RAM: 80-400 nano seconds4.Disk: 5000000 nano seconds (5 milli seconds)如今CPU又按照摩尔定律发展了十年后,这个硬盘还是机械式的磁头移来移去读写,尽管如今disk controller都有cache,也在发展,但和CPU相比,差距越来越大。

(谁有最新数据可以贴上来。

)二.生成数量大大超过CPU总数的线程。

这样做有两个弊端,第一是每个线程要占用内存,Windows底下每个thread自己stack的省缺大小为1M,32位程序下一个用户程序最大能利用的内存也就3G,生成3000个线程,内存就没了。

IOCP详解

IOCP详解

IOCP详解简介: IOCP(I/O Completion Port,I/O完成端⼝)是性能最好的⼀种I/O模型。

它是应⽤程序使⽤线程池处理异步I/O请求的⼀种机制。

IOCP详解IOCP(I/O Completion Port,I/O完成端⼝)是性能最好的⼀种I/O模型。

它是应⽤程序使⽤线程池处理异步I/O请求的⼀种机制。

在处理多个并发的异步I/O请求时,以往的模型都是在接收请求是创建⼀个线程来应答请求。

这样就有很多的线程并⾏地运⾏在系统中。

⽽这些线程都是可运⾏的,Windows内核花费⼤量的时间在进⾏线程的上下⽂切换,并没有多少时间花在线程运⾏上。

再加上创建新线程的开销⽐较⼤,所以造成了效率的低下。

Windows Sockets应⽤程序在调⽤WSARecv()函数后⽴即返回,线程继续运⾏。

当系统接收数据完成后,向完成端⼝发送通知包(这个过程对应⽤程序不可见)。

应⽤程序在发起接收数据操作后,在完成端⼝上等待操作结果。

当接收到I/O操作完成的通知后,应⽤程序对数据进⾏处理。

完成端⼝其实就是上⾯两项的联合使⽤基础上进⾏了⼀定的改进。

⼀个完成端⼝其实就是⼀个通知队列,由操作系统把已经完成的重叠I/O请求的通知放⼊其中。

当某项I/O操作⼀旦完成,某个可以对该操作结果进⾏处理的⼯作者线程就会收到⼀则通知。

⽽套接字在被创建后,可以在任何时候与某个完成端⼝进⾏关联。

众所皆知,完成端⼝是在WINDOWS平台下效率最⾼,扩展性最好的IO模型,特别针对于WINSOCK的海量连接时,更能显⽰出其威⼒。

其实建⽴⼀个完成端⼝的服务器也很简单,只要注意⼏个函数,了解⼀下关键的步骤也就⾏了。

分为以下⼏步来说明完成端⼝:0) 同步IO与异步IO1) 函数2) 常见问题以及解答3) 步骤4) 例程0、同步IO与异步IO同步I/O⾸先我们来看下同步I/O操作,同步I/O操作就是对于同⼀个I/O对象句柄在同⼀时刻只允许⼀个I/O操作,原理图如下:由图可知,内核开始处理I/O操作到结束的时间段是T2~T3,这个时间段中⽤户线程⼀直处于等待状态,如果这个时间段⽐较短,则不会有什么问题,但是如果时间⽐较长,那么这段时间线程会⼀直处于挂起状态,这就会很严重影响效率,所以我们可以考虑在这段时间做些事情。

在c#使用IOCP(完成端口)的简单示例

在c#使用IOCP(完成端口)的简单示例

在c#使用IOCP(完成端口)的简单示例这次给大家演示一下利用IOCP的在线程间传递数据的例子,顺便打算讲一些细节和注意的地方。

概述:这里主要使用IOCP的三个API,CreateIoCompletionPort,PostQueuedCompletionStatus,GetQueuedCompletionStatus,第一个是用来创建一个完成端口对象,第二个是向一个端口发送数据,第三个是接受数据,基本上用着三个函数,就可以写一个使用IOCP的简单示例。

其中完成端口一个内核对象,所以创建的时候会耗费性能,CPU得切换到内核模式,而且一旦创建了内核对象,我们都要记着要不用的时候显式的释放它的句柄,释放非托管资源的最佳实践肯定是使用Dispose模式,这个博客园有人讲过N次了。

而一般要获取一个内核对象的引用,最好用SafeHandle来引用它,这个类可以帮你管理引用计数,而且用它引用内核对象,代码更健壮,如果用指针引用内核对象,在创建成功内核对象并复制给指针这个时间段,如果抛了ThreadAbortException,这个内核对象就泄漏了,而用SafeHandle去应用内核对象就不会在赋值的时候发生ThreadAbortException。

另外SafeHandle类继承自CriticalFinalizerObject类,并实现了IDispose接口,CLR对CriticalFinalizerObject及其子类有特殊照顾,比如说在编译的时候优先编译,在调用非CriticalFinalizerObject类的Finalize方法后再调用CriticalFinalizerObject类的Finalize类的Finalize方法等。

在win32里,一般一个句柄是-1或者0的时候表示这个句柄是无效的,所以.net有一个SafeHandle的派生类SafeHandleZeroOrMinusOneIsInvalid ,但是这个类是一个抽象类,你要引用自己使用的内核对象或者非托管对象,要从这个类派生一个类并重写Relseas方法。

采用完成端口(IOCP)实现高性能网络服务器(Windowsc++版)

采用完成端口(IOCP)实现高性能网络服务器(Windowsc++版)

采⽤完成端⼝(IOCP)实现⾼性能⽹络服务器(Windowsc++版)前⾔ TCP\IP已成为业界通讯标准。

现在越来越多的程序需要联⽹。

⽹络系统分为服务端和客户端,也就是c\s模式(client \ server)。

client⼀般有⼀个或少数⼏个连接;server则需要处理⼤量连接。

⼤部分情况下,只有服务端才特别考虑性能问题。

本⽂主要介绍服务端处理⽅法,当然也可以⽤于客户端。

 我也发表过c#版⽹络库。

其实,我最早是从事c++开发,多年前就实现了对完成端⼝的封装。

最近⼜把以前的代码整理⼀下,做了测试,也和c#版⽹络库做了粗略对⽐。

总体上,还是c++性能要好⼀些。

c#⽹络库见⽂章Windows平台下处理socket通讯有多种⽅式;⼤体可以分为阻塞模式和⾮阻塞模式。

阻塞模式下send和recv都是阻塞的。

简单讲⼀下这两种模式处理思路。

阻塞模式:⽐如调⽤send时,把要发送的数据放到⽹络发送缓冲区才返回。

如果这时,⽹络发送缓冲区满了,则需要等待更久的时间。

socket的收发其实也是⼀种IO,和读写硬盘数据有些类似。

⼀般来讲,IO处理速度总是慢的,不要和内存处理并列。

对于调⽤recv,⾄少读取⼀个字节数据,函数才会返回。

所以对于recv,⼀般⽤⼀个单独的线程处理。

⾮阻塞模式:send和recv都是⾮阻塞的;⽐如调⽤send,函数会⽴马返回。

真正的发送结果,需要等待操作系统的再次通知。

阻塞模式下⼀步可以完成的处理,在⾮阻塞模式下需要两步。

就是多出的这⼀步,导致开发难度⼤⼤增加。

⾼性能⼤并发⽹络服务器必须采⽤⾮阻塞模式。

完成端⼝(IOCP)是⾮阻塞模式中性能最好的⼀种。

作者多年以前,就开始从事winsocket开发,最开始是采⽤c++、后来采⽤c#。

对⾼性能服务器设计的体会逐步加深。

⼈要在⼀定的压⼒下才能有所成就。

最开始的⼀个项⽬是移动信令分析,所处理的消息量⾮常⼤;⾼峰期,每秒要处理30万条信令,占⽤带宽500M。

WinSock IO 模型 —- 完成端口模型IOCP

WinSock IO 模型 —- 完成端口模型IOCP

引言:经过三天的努力,我终于把Winsock I/O 模型,做了一个基本了解,最终还把最为复杂高效的CompletionPort(完成端口)模型看懂了,可以说这是一场比较大的胜利。

下面来着重介绍Completion Port(完成端口)。

一、学习前提:1、会winsock 基础编程,也就是说你要明白winsock 的Tcp 和udp编程流程。

2、学习完成端口前最好是先去好好看下重叠I/O 模型,因为完成端口模型,有一部分还是重叠I/O模型的东西,要是不懂那你就歇菜了。

3、会CreateThread 创建线程最好是明白原理。

二、模型特性:在使用一个技术前如果连这个技术的特性都不了解,那你就真是一个….。

1、首先要强调的是完成端口模型是只适合Window NT 或Windows2000 等windows系统,所以在其他系统上用不了。

2、完成端口模型是一种复杂但是可以用时管理数百甚至数千的套接字的一种模型所以当你的系统要同时处理非常多的通信请求完成端口是最好的选择。

3、一般我们使用完成端口的时候都是按照系统CPU数量来创建IO服务线程的,所以如果你的系统cpu数量越多那么性能当然会越好。

三、模型原理:这个模型原理其实是我个人的一个理解和概括,所以不一定完全正确各位看官见谅不要说我坑你了。

其实所谓的完成端口模型就是先创建一个完成端口(注意我们在创建完成端口的时候指定了这个完成端口同时执行的IO服务线程数量,一般为cpu数量),并创建若干(说若干是因为我们一般是根据cpu数量来创建线程的,这样做是因为每个cpu执行一个线程就避免的线程的切换影响效率,但是实际我们还是会创建大于cpu数量一定值的线程,虽然前面指定了完成端口同时执行的线程只有cpu个数,但是因为有的线程会出现挂起的情况,多创建一点就可以减少因为挂起而使cpu闲置)的服务线程(将完成端口作为参数传递给线程)然后将完成端口和我们关心的套接字关联在一起,并且在关联的时候我们还可以传递一个自定义的结构体。

理解I O C P(完成端口)

理解I O C P(完成端口)

理解I/O Completion Port(完成端口)欢迎阅读此篇IOCP教程。

我将先给出IOCP的定义然后给出它的实现方法,最后剖析一个Echo 程序来为您拨开IOCP的谜云,除去你心中对IOCP的烦恼。

OK,但我不能保证你明白IOCP 的一切,但我会尽我最大的努力。

以下是我会在这篇文章中提到的相关技术:I/O端口同步/异步堵塞/非堵塞服务端/客户端多线程程序设计Winsock API 2.0在这之前,我曾经开发过一个项目,其中一块需要网络支持,当时还考虑到了代码的可移植性,只要使用select,connect,accept,listen,send还有recv,再加上几个#ifdef的封装以用来处理Winsock和BSD套接字[socket]中间的不兼容性,一个网络子系统只用了几个小时很少的代码就写出来了,至今还让我很回味。

那以后很长时间也就没再碰了。

前些日子,我们策划做一个网络游戏,我主动承担下网络这一块,想想这还不是小case,心里偷着乐啊。

网络游戏好啊,网络游戏为成百上千的玩家提供了乐趣和令人着秘的游戏体验,他们在线上互相战斗或是加入队伍去战胜共同的敌人。

我信心满满的准备开写我的网络,于是乎,发现过去的阻塞同步模式模式根本不能拿到一个巨量多玩家[MMP]的架构中去,直接被否定掉了。

于是乎,就有了IOCP,如果能过很轻易而举的搞掂IOCP,也就不会有这篇教程了。

下面请诸位跟随我进入正题。

什么是IOCP?先让我们看看对IOCP的评价I/O完成端口可能是Win32提供的最复杂的内核对象。

[Advanced Windows 3rd] Jeffrey Richter这是[IOCP]实现高容量网络服务器的最佳方法。

[Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports]Microsoft Corporation完成端口模型提供了最好的伸缩性。

IOCP完成端口详解

IOCP完成端口详解

IOCP完成端口详解通常要开发网络应用程序并不是一件轻松的事情,不过,实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字,尝试进行连接,然后收发数据。

真正难的是要写出一个可以接纳少则一个,多则数千个连接的网络应用程序。

本文将讨论如何通过Winsock2在Windows NT 和Windows 2000上开发高扩展能力的Winsock应用程序。

文章主要的焦点在客户机/服务器模型的服务器这一方,当然,其中的许多要点对模型的双方都适用。

API与响应规模通过Win32的重叠I/O机制,应用程序可以提请一项I/O操作,重叠的操作请求在后台完成,而同一时间提请操作的线程去做其他的事情。

等重叠操作完成后线程收到有关的通知。

这种机制对那些耗时的操作而言特别有用。

不过,像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用,但是它们不能满足响应规模的需要。

而完成端口机制是针对操作系统内部进行了优化,在Windows NT 和Windows 2000上,使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。

完成端口一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。

当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到一则通知。

而套接字在被创建后,可以在任何时候与某个完成端口进行关联。

通常情况下,我们会在应用程序中创建一定数量的工作者线程来处理这些通知。

线程数量取决于应用程序的特定需要。

理想的情况是,线程数量等于处理器的数量,不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作,以免线程阻塞。

每个线程都将分到一定的CPU时间,在此期间该线程可以运行,然后另一个线程将分到一个时间片并开始执行。

如果某个线程执行了阻塞型的操作,操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。

第5章_完成端口

第5章_完成端口
}
(2)完成端口模型
解决的问题: 当需要管理众多套接字时,应用程序要为每一个套接字开一个
线程,当线程较多时系统效率降低且线程很难管理。
完成端口本质: 事先开辟N个线程,当若干套接字有I/O处理时,利用N个线程对
重叠I/O进行处理。
完成端口 GetQueuedCompletionStatus
N个线程 进行处理
利用完成端口创建程序的步骤: 创建完成端口 确定所需创建的 工作线程数 创建套接字 准备参数
在新连接上开始 I/O,投递一个或 多个请求
接收连接
将新连接关联到 完成端口
完成端口 案例
案例:实现一个Echo服务器
{
OVERLAPPED Overlapped; WSABUF DataBuf; CHAR Buffer[DATA_BUFSIZE]; DWORD BytesSEND; // 发送字节数 DWORD BytesRECV; } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
完成端口的创建一般需要两次,第一次创建一个没有指定参数的完 成端口对象,第二次再将参数传递。
Comport= CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0) CreateIoCompletionPort((HANDLE) Accept[num_players], Comport, (DWORD) PerHandleData, 0)
投递 绑定 CreateIoCompletionPort
I/O(OS)
发送
Socket
WSASend和WSARecv
HANDLE CreateIoCompletionPort (

windows下socket编程实现client和server双向通信

windows下socket编程实现client和server双向通信

windows下socket编程实现client和server双向通信服务端代码server.c// server.cpp : Defines the entry point for the console application.//#include <stdio.h>#include <Winsock2.h> //Socket的函数调⽤ #include <windows.h>#define BUF_SIZE 6400 // 缓冲区⼤⼩#pragma comment (lib, "ws2_32") // 使⽤WINSOCK2.H时,则需要库⽂件WS2_32.LIBDWORD WINAPI Rcv( LPVOID lpParam ){SOCKET sClient = *(SOCKET*)lpParam;int retVal;char bufRecv[BUF_SIZE];memset( bufRecv, 0, sizeof( bufRecv ) );while(1){retVal = recv( sClient, bufRecv, BUF_SIZE, 0 );if ( retVal == SOCKET_ERROR ) {printf( "recive faild!\n" );break;} else {printf( "收到客户端消息:%s\n", bufRecv );}}return 0;}DWORD WINAPI Snd( LPVOID lpParam ){SOCKET sClient = *(SOCKET*)lpParam;int retVal;char bufSend[BUF_SIZE];memset( bufSend, 0, sizeof( bufSend ) );while(1){gets( bufSend );retVal = send( sClient, bufSend, strlen(bufSend)+sizeof(char), 0 );if ( retVal == SOCKET_ERROR ) {printf( "send faild!\n" );break;}}return 0;}int main(int argc, char* argv[]){// 初始化套接字动态库WSADATA wsaData;if ( WSAStartup(MAKEWORD(2, 2), &wsaData) != 0 ) {printf( "winsock load faild!\n" );return 1;}// 创建服务段套接字SOCKET sServer = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );if ( sServer == INVALID_SOCKET ) {printf( "socket faild!\n" );WSACleanup();return -1;}// 服务端地址sockaddr_in addrServ;addrServ.sin_family = AF_INET;addrServ.sin_port = htons( 9999 );addrServ.sin_addr.s_addr = htonl( INADDR_ANY );// 绑定套接字if ( bind( sServer, ( const struct sockaddr* )&addrServ, sizeof(addrServ) ) == SOCKET_ERROR ) {printf( "bind faild!\n" );closesocket( sServer );WSACleanup();return -1;}printf("Server is On IP:[%s],port:[%d]\n",inet_ntoa(addrServ.sin_addr),ntohs(addrServ.sin_port));// 监听套接字数字表⽰最多能监听客户个数if ( listen( sServer, 5 ) == SOCKET_ERROR ) {printf( "listen faild!\n" );closesocket( sServer );WSACleanup();return -1;}SOCKET sClient; // 客户端套接字sockaddr_in addrClient;int addrClientLen = sizeof( addrClient );sClient = accept( sServer, ( sockaddr FAR* )&addrClient, &addrClientLen );if ( sClient == INVALID_SOCKET ) {printf( "accept faild!\n" );closesocket( sServer );WSACleanup();return -1;}printf("accepted client IP:[%s],port:[%d]\n",inet_ntoa(addrClient.sin_addr),ntohs(addrClient.sin_port)); HANDLE hThread1, hThread2;DWORD dwThreadId1, dwThreadId2;hThread1 = ::CreateThread(NULL, NULL, Snd, (LPVOID*)&sClient, 0, &dwThreadId1);hThread2 = ::CreateThread(NULL, NULL, Rcv, (LPVOID*)&sClient, 0, &dwThreadId2);::WaitForSingleObject(hThread1, INFINITE);::WaitForSingleObject(hThread2, INFINITE);::CloseHandle(hThread1);::CloseHandle(hThread2);closesocket( sClient );WSACleanup(); // 资源释放return 0;}客户端代码client.c// client.cpp : Defines the entry point for the console application.//#include <stdio.h>#include <Winsock2.h> //Socket的函数调⽤ #include <windows.h>#define BUF_SIZE 6400#pragma comment (lib, "ws2_32") // 使⽤WINSOCK2.H时,则需要库⽂件WS2_32.LIBDWORD WINAPI Rcv( LPVOID lpParam ){SOCKET sHost = *(SOCKET*)lpParam;int retVal;char bufRecv[BUF_SIZE];memset( bufRecv, 0, sizeof( bufRecv ) );while(1){retVal = recv( sHost, bufRecv, BUF_SIZE, 0 );if ( retVal == SOCKET_ERROR ) {printf( "recive faild!\n" );break;} else {printf( "收到服务器消息:%s\n", bufRecv );}}return 0;}DWORD WINAPI Snd( LPVOID lpParam ){SOCKET sHost = *(SOCKET*)lpParam;int retVal;char bufSend[BUF_SIZE];memset( bufSend, 0, sizeof( bufSend ) );while(1){gets( bufSend );retVal = send( sHost, bufSend, strlen(bufSend)+sizeof(char), 0 );if ( retVal == SOCKET_ERROR ) {printf( "send faild!\n" );break;}}return 0;}int main(int argc, char* argv[]){WSADATA wsaData;if ( WSAStartup( MAKEWORD(2,2), &wsaData ) != 0 ) {printf( "Winsock load faild!\n" );return 1;}// 服务器套接字SOCKET sHost = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );if ( sHost == INVALID_SOCKET ) {printf( "socket faild!\n" );WSACleanup();return -1;}SOCKADDR_IN servAddr;servAddr.sin_family = AF_INET;// 注意当把客户端程序发到别⼈的电脑时此处IP需改为服务器所在电脑的IPservAddr.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1" );servAddr.sin_port = htons( 9999 );// 连接服务器if ( connect( sHost, (LPSOCKADDR)&servAddr, sizeof( servAddr ) ) == SOCKET_ERROR ) {printf( "connect faild!\n" );closesocket(sHost);WSACleanup();return -1;}printf("连接到服务器 IP:[%s],port:[%d]\n",inet_ntoa(servAddr.sin_addr),ntohs(servAddr.sin_port)); HANDLE hThread1, hThread2;DWORD dwThreadId1, dwThreadId2;hThread1 = ::CreateThread( NULL, NULL, Snd, (LPVOID)&sHost, 0, &dwThreadId1 );hThread2 = ::CreateThread( NULL, NULL, Rcv, (LPVOID)&sHost, 0, &dwThreadId2 );::WaitForSingleObject( hThread1, INFINITE );::WaitForSingleObject( hThread2, INFINITE );::CloseHandle(hThread1);::CloseHandle(hThread2);closesocket(sHost);WSACleanup();return 0;}截图如下:编译好后⾸先是启动服务端(来监听),然后再启动客户端。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 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是内存管理单元的源码文件,欢迎指正。

相关文档
最新文档