﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-浪迹天涯-文章分类-技术</title><link>http://www.cppblog.com/qiujian5628/category/6088.html</link><description>唯有努力...
&lt;br&gt;努力....再努力...</description><language>zh-cn</language><lastBuildDate>Mon, 13 Jul 2009 11:34:16 GMT</lastBuildDate><pubDate>Mon, 13 Jul 2009 11:34:16 GMT</pubDate><ttl>60</ttl><item><title>简历</title><link>http://www.cppblog.com/qiujian5628/articles/86512.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Tue, 02 Jun 2009 01:56:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/86512.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/86512.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/86512.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/86512.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/86512.html</trackback:ping><description><![CDATA[.........<br> <img src ="http://www.cppblog.com/qiujian5628/aggbug/86512.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2009-06-02 09:56 <a href="http://www.cppblog.com/qiujian5628/articles/86512.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Web 服务最佳实践</title><link>http://www.cppblog.com/qiujian5628/articles/61970.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Tue, 16 Sep 2008 05:50:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/61970.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/61970.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/61970.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/61970.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/61970.html</trackback:ping><description><![CDATA[Web 服务最佳实践<br>作者：未知&nbsp;&nbsp;&nbsp; 阅读人次：743&nbsp;&nbsp;&nbsp; 文章来源：CSDN Bolg&nbsp;&nbsp;&nbsp; 发布时间：2007-9-1&nbsp;&nbsp;&nbsp; 网友评论(0)条<br>&nbsp;<br><br>概要<br><br>Web服务是作为一种沟通技术而被很好地制订出来，它为Internet提供最佳的互通能力。它们的标准化进程正高速地进行着，这必将引起它们会被更广泛的接受。尽管如此，从许多邮件列表、用户组和各种讨论来判断，在不同Web服务设计（Web Service Design）方法中仍然存在相当多的混乱情形。&#8220;Document/Literal&#8221; 意味着什么，而&#8220;RPC-style&#8221;又是什么，怎样使SOAP&#8220;message-style&#8221; 适合这呢？<br><br>这篇文章将会阐明和详细解释那些由Web服务标准化组定义的不同的Web服务设计方法学，阐明各种术语，着重解释它们的不同之处。在这里，我们将把精力放在如下的几个Web服务设计方法学，评估它们的优点和缺点，并在设计一种互通性Web服务中探究每一种类型得到多大程度的支持。<br><br>&nbsp;&nbsp; 1. RPC/Encoded 样式<br>&nbsp;&nbsp; 2. RPC/Literal 样式<br>&nbsp;&nbsp; 3. Document/Literal 样式<br>&nbsp;&nbsp; 4. Document /Literal 外覆样式<br><br>介绍<br><br>在市场上Web服务在相对存在较短的时间内，得到了巨大的认同和广泛的应用。其中一个主要原因当然是它们的那些非常早期的开放式标准，而这些标准就是市场上所有主要的Web服务推崇者所推动的；另一方面，在Web服务看起来应该像什么和他们应该如何通信这些方面，这些推崇者各有自己的偏爱。这已经导致今天的标准支持各种不同的关于web服务消息能怎样格式化和它们如何通信的方法，事实上，那些不同的通信类型是需要的。<br><br>这些描述和使用Web服务的相关标准是WSDL(Web服务描述语言)，一种标准的类似XML　Schema的语言，用它详细说明Web服务和简单对象访问协议（SOAP），Web服务使用的实际的沟通协议就是简单对象访问协议（SOAP）。在了解真正的设计方法学之前，让我们先阐述下在web服务领域中频繁使用到的各种术语。<br><br>沟通模式<br><br>让我们从沟通模式开始，在使用Web服务时我们应能本质地区别三种沟通方法：<br><br>&nbsp;&nbsp;&nbsp; * 远程进程调用：客户端给服务提供者发送一个SOAP请求并等待一个SOAP响应（同步沟通）。<br>&nbsp;&nbsp;&nbsp; * 发送消息： 客户端发送一个SOAP请求但不期望有SOAP响应返回（单向沟通）。<br>&nbsp;&nbsp;&nbsp; * 异步回调： 一个客户端用上述方法中的一种调用服务。然后，双方为回叫调用交换角色。这种模式能建立在前面两种模式之上。<br><br>SOAP 协议格式化规则<br><br>现在我们转到一个Web服务的SOAP的消息（本质上是消息的 元素）能如何格式化，WSDL1.1 区分两种不同绑定形式（参考soap的绑定形式）：RPC和Document（文档）。（译者注：RPC（消息包含参数并返回值）Document（消息包含文档））<br><br>&nbsp;&nbsp;&nbsp; * RPC 样式<br><br>RPC样式指定 元素包含一个将被调用的web方法的名称的元素（wrapper element（封装元素））。这个元素依次为该方法的每个参数还有返回值作了记录。<br><br>&nbsp;&nbsp;&nbsp; * Document　样式<br><br>如果是document 样式，就没有像在RPC样式中的wrapper元素。转而代之的是消息片断直接出现在&lt; SPAN&gt;&gt; 元素之下。没有任何SOAP格式化规则规定元素下能包含什么；它包含的是一个发送者和接收者都达成一致的XML文档。<br><br>第二种格式规则就是&#8216;Use&#8217; 属性。这与各种类型如何在XML中显示有关，它指定使用某种编码规则对消息片段进行编码，还是使用消息的具体架构来定义片段。如下就是提供的两种选择：<br><br>&nbsp;&nbsp;&nbsp; * 编码 <br><br>如果use的值是&#8221;encoded&#8221;, 则每个消息片段将使用类型属性来引用抽象类型。通过应用由 encodingStyle 属性所指定的编码样式，可使用这些抽象类型生成具体的消息。最常用到的SOAP编码样式是在SOAP1.1中定义的一组序列化规则，它说明了对象、结构、数组和图形对象应该如何序列化。通常，在应用程序中使用SOAP编码着重于远程进程调用和以后适合使用RPC消息样式。<br><br>&nbsp;&nbsp;&nbsp; * Literal <br><br>如果use 的值是&#8221;Literal&#8221;， 则每个片段使用 element 属性（对于简单片段）或 type 属性（对于复合片段）来引用具体架构，例如，数据根据指定的架构来序列化，这架构通常使用W3C XML架构来表述。<br><br>表1总结了各种不同的web服务参数的可选项。一个重要的事实就是，每个web服务开发人员要做三个独立的决定，使用什么&#8220;沟通模式&#8221;？什么SOAP格式化&#8220;样式&#8221;？最后用到什么SOAP消息编码类型？<br><br>&nbsp;<br><br>WSDL 参数<br>&nbsp;&nbsp;&nbsp; <br><br>可用选项<br><br>Communication Patterns<br>&nbsp;&nbsp;&nbsp; <br><br>远程进程调用或单方消息发送<br><br>Style<br>&nbsp;&nbsp;&nbsp; <br><br>Document 或 RPC<br><br>Use<br>&nbsp;&nbsp;&nbsp; <br><br>Encoded 或 Literal<br><br>表1.Web服务参数。<br><br>虽然，理论上说，这些选项的任何一种组合都是可以的，但在实践中会明确偏爱某种组合而不是其它的，同时，各种标准和Web服务互用性组织（WS-I）也有某种明确的偏爱。<br><br>因此在实践中，仅Document/Literal 和 RPC/Encoded 得到了广泛的应用，同时也被大部分平台直接支持，这些平台显示在表2中。这个表也显示了对不同的style/use组合的WS-I 一致性测试结果。<br><br>Style/Use 组合<br>&nbsp;&nbsp;&nbsp; <br><br>支持的 Soap 工具<br>&nbsp;&nbsp;&nbsp; <br><br>WS-I 一致性<br><br>RPC/Encoded<br>&nbsp;&nbsp;&nbsp; <br><br>Microsoft, Axis 1.1<br>&nbsp;&nbsp;&nbsp; <br><br>Failed<br><br>RPC/Literal<br>&nbsp;&nbsp;&nbsp; <br><br>Axis 1.1<br>&nbsp;&nbsp;&nbsp; <br><br>Failed<br><br>Document/Literal<br>&nbsp;&nbsp;&nbsp; <br><br>Microsoft , Axis1.1<br>&nbsp;&nbsp;&nbsp; <br><br>Passed<br><br>表2. Web服务格式支持<br><br>由于Document/Encoded这种组合不支持现有使用的平台，所以没有测试。事实上Document/Encoded组合还没有真实的应用环境。<br><br>一个简单Web服务例子<br><br>现在我们来更详细的了解被使用和支持最多的RPC/Encoded与Document/Literal的style/use组合。我们将利用一个叫做 &#8220;SendTemperature&#8221; 的web方法举例来说明每一种的style/use组合，那个web方法使用的参数是一个用户自定义的复杂对象，返回一个void类型，这些在清单1中有描述。<br><br>我们选择了这么一个使用复杂数据类型例子，就是为了让各种样式之间的不同更加明显。<br><br>public void SendTemperature (Temperature[] TempCollection){}<br><br>public class Temperature <br><br>{<br><br>&nbsp;&nbsp; /// <br><br>&nbsp;&nbsp; public int Id;<br><br>&nbsp; /// <br><br>&nbsp; public string Name;<br><br>&nbsp; /// <br><br>&nbsp; public System.Double Temperature;<br><br>}<br><br>清单<br>1. 使用C#实现的web方法SendTemperature<br><br>针对这个web方法，我们将展示各种web服务样式就它们各自的SOAP请求形式怎样在WSDL中中实现，重点讲述它们的不同之处。我们利用Microsoft VS.NET和Axis SOAP 工具箱来实现。<br><br>注意，为了简单，在这篇文章中我们忽略了WSDL文件的名称空间、前缀和服务部分。清单2列出了常用的名称空间和前缀。<br><br>xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" <br><br>xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" <br><br>xmlns:s="http://www.w3.org/2001/XMLSchema" <br><br>xmlns:s0="http://interop.webservices.fhso.ch/{service name}" <br><br>xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" <br><br>xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" <br><br>xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" <br><br>xmlns="http://schemas.xmlsoap.org/wsdl/" <br><br>targetNamespace="http://interop.webservices.fhso.ch/{service name}/ &#8221;<br><br>清单<br>2. 名称空间和使用到的前缀<br><br>&nbsp;&nbsp; 1. RPC/Encoded 样式<br><br>实质上RPC/Encoded 是一种典型的遵循&#8220;远程进程调用&#8221;模式的样式，在这种模式中客户端发送一个同步请求给服务器来执行一次操作。SOAP请求包含了要执行的方法的名称和它携带的参数。运行web服务的的服务器把该请求转化成适当的对象，然后执行操作并向客户端做出响应反馈一个SOAP消息。在客户端，该响应被用来合成一个适当的对象并返回给客户端所需要的的信息。在RPC样式的web服务中，整个方法在WSDL文件和SOAP体中被指定，包含方法的发送参数和返回值。因此我们用这种样式会有相当紧密的耦合关系。<br><br>清单3显示在RPC/Encoded样式中SendTemperature 的WSDL定义。<br><br>&lt;types&gt;<br><br>&lt;s:schema targetNamespace="http://interop.webservices.fhso.ch/rpcencoded"&gt;<br><br>&lt;s:complexType name="ArrayOfTemperature"&gt;<br><br>&lt;s:complexContent mixed="false"&gt;<br><br>&lt;s:restriction base="soapenc:Array"&gt;<br><br>&lt;s:attribute d7p1:arrayType="s0:Temperature[]" ref="soapenc:arrayType" <br><br>xmlns:d7p1="http://schemas.xmlsoap.org/wsdl/"/&gt;<br><br>s:restriction&gt;<br><br>s:complexContent&gt;<br><br>s:complexType&gt;<br><br>&lt;s:complexType name="Temperature"&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="1" maxOccurs="1" name="Id" type="s:int"/&gt;<br><br>&lt;s:element minOccurs="1" maxOccurs="1" name="Name" type="s:string"/&gt;<br><br>&lt;s:element minOccurs="1" maxOccurs="1" name="value" type="s:double"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>s:schema&gt;<br><br>types&gt;<br><br>&lt;message name="SendTemperatureSoapIn"&gt;<br><br>&lt;part name="Collection" type="s0:ArrayOfTemperature"/&gt;<br><br>message&gt;<br><br>&lt;message name="SendTemperatureSoapOut"/&gt;<br><br>&lt;portType name="TemperatureRpcEncodedSoap"&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;input message="s0:SendTemperatureSoapIn"/&gt;<br><br>&lt;output message="s0:SendTemperatureSoapOut"/&gt;<br><br>operation&gt;<br><br>portType&gt;<br><br>&lt;binding name="TemperatureRpcEncodedSoap" type="s0:TemperatureRpcEncodedSoap"&gt;<br><br>&lt;soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;soap:operation soapAction="http://interop.fhso.ch/soapformat/SendTemperature"/&gt;<br><br>&lt;input&gt;<br><br>&lt;soap:body use="encoded" <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/&gt;<br><br>input&gt;<br><br>&lt;output&gt;<br><br>&lt;soap:body use="encoded" <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/&gt;<br><br>output&gt;<br><br>operation&gt;<br><br>binding&gt;<br>&nbsp;<br><br>清单3. SendTemperature在RPC/Encoded 样式中的SOAP定义<br><br>注意绑定的style的值被设为&#8216;rpc&#8217; 而use的值是&#8216;encoded&#8217;. 在 这节中，可以有任意数目的 元素，该元素包含一个type 属性，对&#8216;rpc&#8217; 样式来说，该属性的值是惟一的。现在我们看看调用这个SendTemperature web方法会发生什么，传送的参数是包含两个元素的数组。清单4显示了SOAP消息形式的结果。<br><br>&nbsp;<br><br>&lt;soap:Envelopexmlns:<br><br>&nbsp;&nbsp;&nbsp;&nbsp; soap="http://schemas.xmlsoap.org/soap/envelope/" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; xmlns:tns="http://interop.webservices.fhso.ch/rpcencoded" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; xmlns:types="http://interop.webservices.fhso.ch/rpcencoded/encodedTypes" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;<br><br>&lt;soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"&gt;<br><br>&lt;SendTemperature&gt;<br><br>&lt;Collection href="#id1"/&gt;<br><br>SendTemperature&gt;<br><br>&lt;soapenc:Array id="id1" soapenc:arrayType="tns:Temperature[2]"&gt;<br><br>&lt;Item href="#id2"/&gt;<br><br>&lt;Item href="#id3"/&gt;<br><br>soapenc:Array&gt;<br><br>&lt;tns:Temperature id="id2" xsi:type="tns:Temperature"&gt;<br><br>&lt;Id xsi:type="xsd:int"&gt;3Id&gt;<br><br>&lt;Name xsi:type="xsd:string"&gt;Station1Name&gt;<br><br>&lt;value xsi:type="xsd:double"&gt;34.3value&gt;<br><br>tns:Temperature&gt;<br><br>&lt;tns:Temperature id="id3" xsi:type="tns:Temperature"&gt;<br><br>&lt;Id xsi:type="xsd:int"&gt;56Id&gt;<br><br>&lt;Name xsi:type="xsd:string"&gt;Station3Name&gt;<br><br>&lt;value xsi:type="xsd:double"&gt;23.6value&gt;<br><br>tns:Temperature&gt;<br><br>soap:Body&gt;<br><br>soap:Envelope&gt;<br><br>清单４. SendTemperature在RPC/Encoded 样式中的SOAP消息<br><br>在SOAP消息中每个参数都会被类型编码（译者注：例如xsi:type="xsd:int"），注意，在SOAP消息中的&#8216;href&#8217; tags 标签，实质上也是RPC/Encoded样式的一部分，它被用来查询数组中的元素。在任何literal 样式中，这个&#8216;href&#8217; 标签是不可用的。让我们来分析下WSDL定义和它的SOAP消息形式。<br><br>优点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * WSDL文件的定义遵循直观和众所周知的远程进程调用的沟通模式。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 操作名显示在消息中，因此接收者很容易就把消息分派给它的实现。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 如果你正在你的服务中使用数据图形或者多态，在本文描述的样式中这是惟一能使用的样式。 <br><br>缺点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * SOAP消息包含的类型编码信息就如xsi:type="xsd:int", xsi:type="xsd:string", xsi:type="xsd:double" ，这些就是一种开销。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 通常验证SOAP消息是很困难的。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * RPC样式引起了一种在服务提供者和客户之间的紧密耦合，任何对接口的更改都会导致服务和客户间联系的中断。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 依赖那也许也许要同步处理的信息，内存约束也许使得RPC消息传输不能实现，因为在内存中发生消息聚集。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 不被WSI一致性标准所支持。<br><br>现在我们来看同样的web方法用RPC/Literal样式实现并看看它是否能消除RPC/Encode样式中的一些缺陷。<br><br>&nbsp;&nbsp; 2. RPC/Literal 样式<br><br>这里的WSDL定义看起来和先前的非常相似，惟一可预见的改变就是 元素中&#8216;use&#8217; 的值由&#8216;encoded&#8217; 变为 &#8216;literal&#8217;，这显示在清单5中。就如上面所描述的，这就意味着数据类型定义并是由引用的XML Schema所提供，而不是RPC编码。<br><br>&nbsp;<br>&lt;types&gt;<br><br>&lt;s:schema elementFormDefault="qualified" <br><br>targetNamespace="http://interop.webservices.fhso.ch/rpcliteral"&gt;<br><br><br><br>&lt;s:complexType name="ArrayOfTemperature"&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; nillable="true" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; type="s0:Temperature"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>&lt;s:complexType name="Temperature"&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>s:schema&gt;<br><br>types&gt;<br><br>&lt;message name="SendTemperatureSoapIn"&gt;<br><br>&lt;part name="Collection" type="s0:ArrayOfTemperature"/&gt;<br><br>message&gt;<br><br>&lt;message name="SendTemperatureSoapOut"/&gt;<br><br>&lt;portType name="TemperatureRpcLiteralSoap"&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;input message="s0:SendTemperatureSoapIn"/&gt;<br><br>&lt;output message="s0:SendTemperatureSoapOut"/&gt;<br><br>operation&gt;<br><br>portType&gt;<br><br>&lt;binding name="TemperatureRpcLiteralSoap" <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type="s0:TemperatureRpcLiteralSoap"&gt;<br><br>&lt;soap:binding style="rpc" <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; transport="http://schemas.xmlsoap.org/soap/http"/&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;soap:operation <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; soapAction="http://interop.fhso.ch/soapformat/SendTemperature"/&gt;<br><br>&lt;input&gt;<br><br>&lt;soap:body use="literal" <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; namespace="http://interop.fhso.ch/soapformat/SendTemperature"/&gt;<br><br>input&gt;<br><br>&lt;output&gt;<br><br>&lt;soap:body use="literal" /&gt;<br><br>output&gt;<br><br>operation&gt;<br><br>binding&gt;<br><br>清单 5. SendTemperature在RPC/Literal 样式中的SOAP定义<br><br>然而，使用这种样式仍然存在一个主要的缺陷。独立的XML Schema 不会告诉你消息体中的信息集合包含了些什么，你也必需知道RPC规则。因此，该schema描述的一种RPC/Literal消息但并不足以验证那种消息。<br><br>在清单6中让我们看看针对RPC/Literal样式的SOAP消息形式。注意，类型编码被完全移除掉了。<br><br>&nbsp;<br>&lt;soapenv:Envelope<br><br>xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" <br><br>xmlns:xsd="http://www.w3.org/2001/XMLSchema" <br><br>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;<br><br>&lt;soapenv:Body&gt;<br><br>&lt; ! &#8211; SendTemperature is the name of the procedure being invoked. <br><br>Collection is a parameter of that procedure.<br><br>Note that Collection is not namespace qualified. The two Temperature <br><br>elements are contents of the Collection parameter. This Collection can <br><br>be thought of as an array of Temperature items. Note that the Temperature <br><br>is namespace qualified but is in a different namespace than SendTemperature. <br><br>These namespace rules are unique to RPC-style messages -- &gt; <br><br>&lt;SendTemperature xmlns="http://interop.fhso.ch/soapformat/SendTemperature"&gt;<br><br>&lt;Collection xmlns=""&gt;<br><br>&lt;ns1:Temperature xmlns:ns1="http://interop.webservices.fhso.ch/rpcliteral"&gt;<br><br>&lt;ns1:Id&gt;2ns1:Id&gt;<br><br>&lt;ns1:Name&gt; Station1ns1:Name&gt;<br><br>&lt;ns1:value&gt;34.2ns1:value&gt;<br><br>ns1:Temperature&gt;<br><br>&lt;ns2:Temperature xmlns:ns2="http://interop.webservices.fhso.ch/rpcliteral"&gt;<br><br>&lt;ns2:Id&gt;56ns2:Id&gt;<br><br>&lt;ns2:Name&gt; Station 3ns2:Name&gt;<br><br>&lt;ns2:value&gt;23.6ns2:value&gt;<br><br>ns2:Temperature&gt;<br><br>Collection&gt;<br><br>SendTemperature&gt;<br><br>soapenv:Body&gt;<br><br>soapenv:Envelope&gt;<br><br>清单 6. SendTemperature在RPC/Literal 样式中的SOAP消息<br><br>优点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * WSDL定义仍然像RPC/Encoded样式一样简单直接。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 操作名仍然出现在SOAP消息中。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 把类型编码从消息中排除了，因此提升了吞吐性能。 <br><br>缺点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 服务和客户之间仍然有紧密耦合。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 仍然难以用SOAP消息来验证传输的数据。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 它也不被WSI一致性标准所支持。<br><br>现在，我们来看看Document/Literal样式，来看一下该样式是否能减少现有的缺陷。<br><br>&nbsp;&nbsp; 3. Document/Literal样式<br><br>Document样式和上面的RPC样式最主要的不同就是，前者中客户在一个规范的XML文档中向服务器发送服务参数，而代替了后者中的一组离散的方法的参数值。这使得Document样式比RPC样式有更加松散的耦合关系。<br><br>Web服务提供者处理规范的XML文档，执行操作并向客户端作出响应，返回的也是一个规范的XML文档。在服务器对象（参数，方法调用等）和XML数据值之间并没有一种直接的映射关系。应用程序负责映射XML数据值。Document样式中SOAP消息在它的SOAP体中包含了一个或者更多的XML文档。协议并没有约束文档需要如何组织构成；这完全是在程序级处理的。另外，Document样式web服务遵循异步处理范例。<br><br>就像在清单7中显示的那样，相比RPC样式，该样式的WSDL定义有了很大的改变。<br><br>&lt;types&gt;<br><br>&lt;s:schema elementFormDefault="qualified" <br><br>&nbsp;&nbsp;&nbsp;&nbsp; targetNamespace="http://interop.webservices.fhso.ch/docLit"&gt;<br><br>&lt; ! - - this element declaration describes the entire contents <br><br>of the soap:Body in the request message.<br><br>This is a feature of document/Literal that RPC/Literal lacks - -&gt; <br><br>&lt;s:element name="Collection"&gt;<br><br>&lt;s:complexType&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" <br><br>&nbsp;&nbsp; nillable="true" <br><br>&nbsp;&nbsp; type="s0:Temperature"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>s:element&gt;<br><br>&lt;s:complexType name="Temperature"&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>&lt; ! &#8211; Similarly this element declaration describes <br><br>the contents of the soap body in the response <br><br>message. In this case the response is empty -- &gt; <br><br>&lt;s:element name="SendTemperatureResponse"&gt;<br><br>&lt;s:complexType/&gt;<br><br>s:element&gt;<br><br>s:schema&gt;<br><br>types&gt;<br><br>&lt;message name="SendTemperatureSoapIn"&gt;<br><br>&lt;part name="parameters" element="s0:Collection"/&gt;<br><br>message&gt;<br><br>&lt;message name="SendTemperatureSoapOut"&gt;<br><br>&lt;part name="parameters" element="s0:SendTemperatureResponse"/&gt;<br><br>message&gt;<br><br>&lt;portType name="TemperatureDocLitSoap"&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;input message="s0:SendTemperatureSoapIn"/&gt;<br><br>&lt;output message="s0:SendTemperatureSoapOut"/&gt;<br><br>operation&gt;<br><br>portType&gt;<br><br>&lt;binding name="TemperatureDocLitSoap" type="s0:TemperatureDocLitSoap"&gt;<br><br>&lt;soap:binding style="document" <br><br>&nbsp; transport="http://schemas.xmlsoap.org/soap/http"/&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;soap:operation soapAction=<br><br>&nbsp;&nbsp; "http://interop.webservices.fhso.ch/documentliteral/SendTemperature" <br><br>style="document"/&gt;<br><br>&lt;input&gt;<br><br>&lt;soap:body use="literal" /&gt;<br><br>input&gt;<br><br>&lt;output&gt;<br><br>&lt;soap:body use="literal" /&gt;<br><br>output&gt;<br><br>operation&gt;<br><br>binding&gt;<br><br>&nbsp;<br><br>清单 7. SendTemperature 在Document/Literal样式中 WSDL 定义.<br><br>注意，绑定style 的值是&#8216;document&#8217; 以及 use 的值是 &#8216;literal&#8217;. 在&#8216;message&#8217; 这一节中，仅可能有一个 元素，该元素包含了一个叫做element的属性。<br><br>这片断指出一个描述了soap体的全部内容的schema元素申明。注意，现在集合被定义为一个元素而不上一种类型。Document/Literal样式的主要特点，相比RPC/Literal样式的关键有益之处就是schema 元素申明完全描述了的内容。这样就意味着你只通过查看schema而不要任何附加规则就能说出消息体信息集中包含了什么。因此你就能接收schema描述一个Document/Literal消息并用它验证消息，这可是用RPC/Literal样式无法完成的。<br><br>现在让我们来看看在清单8中该样式相应的SOAP消息形式。注意，没有指定类型编码数据，还有操作名也消失了。<br><br>&nbsp;<br>&lt;soap:Envelope <br><br>xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" <br><br>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br><br>xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;<br><br>&lt;soap:Body&gt;<br><br>&lt; -- note that the Operation name is missing in the message -- &gt;<br><br>&lt;Collection xmlns="http://interop.webservices.fhso.ch/docLit"&gt;<br><br>&lt;Temperature&gt;<br><br>&lt;Id&gt;2Id&gt;<br><br>&lt;Name&gt;Station 1Name&gt;<br><br>&lt;value&gt;34.2value&gt;<br><br>Temperature&gt;<br><br>&lt;Temperature&gt;<br><br>&lt;Id&gt;56Id&gt;<br><br>&lt;Name&gt;Station 3Name&gt;<br><br>&lt;value&gt;23.5value&gt;<br><br>Temperature&gt;<br><br>Collection&gt;<br><br>soap:Body&gt;<br><br>soap:Envelope&gt;<br><br>清单 8. SendTemperature　在Document/Literal 样式中SOAP 消息<br><br>下面就是对这种样式的优点和缺点的总结。<br><br>优点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 在SOAP消息中没有类型编码信息。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 你总能用任何XML验证器来验证消息，在soap体中任何东西都在schema中有定义。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 使用document样式，规则不是那么严格，还有对XML Schema进行增强和更改时不会破坏接口。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 如果使用某特殊序列进行多进程调用，Document 样式可以保持应用程序的状态。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * Document样式更加适合异步处理。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 许多document-messaging服务能够选择文档的DOM和SAX 两种处理方式的其中一种，结果就是能最小化在内存中的处理。<br><br>缺点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * WSDL定义变得更加复杂。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 在SOAP消息中的操作名没有了，没有了名称，把消息分派给它的实现方法就变得困难或不可能了。<br><br>我们已经看到Document/Literal 样式消除了许多RPC/Literal样式中的缺点，另一方面，它也引入了一个新的缺点：在SOAP消息中丢失了操作名。<br><br>由于第四种样式选项，Document/Encoding样式，没有实际使用，将不对它进行描述了。而是我们将看看Document/Literal样式的扩展，即Document/Literal外覆样式。<br><br>&nbsp;&nbsp; 4. Document/Literal 外覆样式<br><br>该样式被微软推荐来消除因标准的Document/Literal 样式引起的缺点。虽然在WSDL1.1标准中没有对该样式进行说明，但现在许多SOAP工具都支持它。<br><br>让我们看清单９中的WSDL定义，以及清单１０中相应的SOAP消息。<br><br>&nbsp;<br>&lt;types&gt;<br><br>&lt;s:schema elementFormDefault="qualified" <br><br>targetNamespace="http://interop.webservices.fhso.ch/docLitWrapped"&gt;<br><br>&lt;s:element name="SendTemperature"&gt;<br><br>&lt;s:complexType&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Collection" <br><br>type="s0:ArrayOfTemperature"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>s:element&gt;<br><br>&lt;s:complexType name="ArrayOfTemperature"&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" <br><br>&nbsp; nillable="true" <br><br>&nbsp; type="s0:Temperature"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>&lt;s:complexType name="Temperature"&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>&lt;s:element name="SendTemperatureResponse"&gt;<br><br>&lt;s:complexType/&gt;<br><br>s:element&gt;<br><br>s:schema&gt;<br><br>types&gt;<br><br>&lt;message name="SendTemperatureSoapIn"&gt;<br><br>&lt;part name="parameters" element="s0:SendTemperature"/&gt;<br><br>message&gt;<br><br>&lt;message name="SendTemperatureSoapOut"&gt;<br><br>&lt;part name="parameters" element="s0:SendTemperatureResponse"/&gt;<br><br>message&gt;<br><br>&lt;portType name="TemperatureDocLitWrappedSoap"&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;input message="s0:SendTemperatureSoapIn"/&gt;<br><br>&lt;output message="s0:SendTemperatureSoapOut"/&gt;<br><br>operation&gt;<br><br>portType&gt;<br><br>&lt;binding name="TemperatureDocLitWrappedSoap" <br><br>&nbsp;&nbsp; type="s0:TemperatureDocLitWrappedSoap"&gt;<br><br>&lt;soap:binding style="document" <br><br>&nbsp;&nbsp; transport="http://schemas.xmlsoap.org/soap/http"/&gt;<br><br>&lt;operation name="SendTemperature"&gt;<br><br>&lt;soap:operation soapAction=<br><br>&nbsp; "http://interop.webservices.fhso.ch/docLitWrapped/SendTemperature" <br><br>&nbsp; style="document"/&gt;<br><br>&lt;input&gt;<br><br>&lt;soap:body use="literal"/&gt;<br><br>input&gt;<br><br>&lt;output&gt;<br><br>&lt;soap:body use="literal"/&gt;<br><br>output&gt;<br><br>operation&gt;<br><br>binding&gt;<br><br>清单 9. SendTemperature在Document/Literal 外覆样式中的WSDL定义.<br><br>首先，注意在清单９中操作名被重新引入到WSDL文件的第一个&#8216;element&#8217; 标签中，也注意到，在清单１０中SOAP消息看起来跟RPC/Literal样式的SOAP消息类似，但是有一个细微的差别，在RPC/Literal样式SOAP消息中 元素的子元素 是操作名，而在Document/Literal外覆样式的SOAP消息中， 是元素名，单一输入消息片断引用该元素，该样式用一种狡猾的方法把操作名重新引入到SOAP消息中。<br><br>这些就是document/Literal外覆样式的主要特征:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 输入消息有一单一片断<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 该片断是一个元素<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 该元素同操作有相同的名字<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 该元素的复杂类型没有属性<br><br>&nbsp;<br>&lt;soap:Envelope <br><br>xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" <br><br>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br><br>xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;<br><br>&lt;soap:Body&gt;<br><br>&nbsp;&#8211; the following is an xml document described in the service&#8217;s <br><br>contract using XML schema. In this case SendTemperature may or may not <br><br>be the name of the remote procedure being invoked by this message. <br><br>Also, Collection may or may not be the name of the parameter. We know the <br><br>structure of the xml document but We don&#8217;t know how the service <br><br>is going to process it -- &gt;<br><br>&lt;SendTemperature xmlns="http://interop.webservices.fhso.ch/docLitWrapped"&gt;<br><br>&lt;Collection&gt;<br><br>&lt;Temperature&gt;<br><br>&lt;Id&gt;2Id&gt;<br><br>&lt;Name&gt;Station 1Name&gt;<br><br>&lt;value&gt;34.2value&gt;<br><br>Temperature&gt;<br><br>&lt;Temperature&gt;<br><br>&lt;Id&gt;56Id&gt;<br><br>&lt;Name&gt;Station 3Name&gt;<br><br>&lt;value&gt;23.6value&gt;<br><br>Temperature&gt;<br><br>Collection&gt;<br><br>SendTemperature&gt;<br><br>soap:Body&gt;<br><br>soap:Envelope&gt;<br><br>清单 10. SendTemperature在Document/Literal 外覆样式中的WSDL消息.<br><br>下面就是该方法缺点与优点的总结：<br><br>优点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 包含了所有Document/Literal样式的优点。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 操作名出现在SOAP消息中。<br><br>缺点：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 即使WSDL定义变得更加复杂，但仍然有不少缺点。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 如果你在web服务中重载了操作，你就不能使用该样式。<br><br>到现在为止，我们看到Document/Literal 和Document/Literal外覆样式相比于任何其它样式，带给我们许多的弹性和优点，但是仍存在一些有待解决的问题。<br><br>Document/Literal 和 Document/Literal 外覆样式的局限性<br><br>假设，我们如清单11中显示的那样重载了操作。<br><br>public void SendTemperature (Temperature[] TempCollection){}<br><br>public void SendTemperature (Temperature[] TempCollection, int week){}<br><br>清单11. 重载 SendTemperature 方法.<br><br>在这种情况，就不可能使用Document/Literal外覆样式，即使WSDL规范允许有重载的方法。原因就是，当我们在一个WSDL文档中增加一个外覆样式时，你将需要一个跟操作相同名字的元素（请看清单９）。当我们想在XML Schema有两个同名称的元素时问题就出现了。因此，我们为了重载的操作的来个二选一，要么寻找非Ddocument/Literal外覆样式，要么一种RPC样式。<br><br>让我们看看我们怎么在Document/Literal样式中实现它，这儿在WSDL定义的schema 节为上述的两个web方法有了修改。<br><br>&nbsp;<br>&lt;types&gt;<br><br>&lt;s:schema elementFormDefault="qualified" <br><br>&nbsp;&nbsp; targetNamespace="http://interop.webservices.fhso.ch/docLit"&gt;<br><br>&lt;s:element name="Collection"&gt;<br><br>&lt;s:complexType&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" <br><br>&nbsp;&nbsp; nillable="true" type="s0:Temperature"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="week" type="s:int"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>s:element&gt;<br><br>&lt;s:complexType name="Temperature"&gt;<br><br>&lt;s:sequence&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/&gt;<br><br>&lt;s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/&gt;<br><br>s:sequence&gt;<br><br>s:complexType&gt;<br><br>&lt;s:element name="SendTemperatureResponse"&gt;<br><br>&lt;s:complexType/&gt;<br><br>s:element&gt;<br><br>s:schema&gt;<br><br>types&gt;<br><br>清单12. SendTemperature在WSDL定义的Schema 节作了更改<br><br>我们仅向集合中添加了另一个元素，所有其它的跟清单7中保持相似。有趣的是让我们看看用VS.NET wsdl.exe 实用工具生成的客户端代理。<br><br>&nbsp;<br>Public void SendTemperature([System.Xml.Serialization.XmlElementAttribute("Protocol", <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IsNullable=true)] Temperature[] Temperature, int week, <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [System.Xml.Serialization.XmlIgnoreAttribute()] bool weekSpecified) {<br><br>this.Invoke("SendTemperature", new object[] {<br><br>&nbsp;&nbsp; Temperature,<br><br>&nbsp;&nbsp; week,<br><br>&nbsp;&nbsp; weekSpecified});<br><br>}<br><br>清单13. 为重载操作SendTemperature生成的C#代理代码<br><br>注意到有另外一个名称为&#8220;weekSpecified&#8221;的Boolean型参数被自动创建，现在客户端就能用两种方法调用这个重载操作的web方法，如果一个客户端调用的方法有week参数以及 weekSpecified 的值设为 &#8220;false&#8221;时，我们调用的是清单11中的第一个方法，还有SOAP请求也跟清单10中的一种相同。<br><br>&nbsp;<br>SendTemperature(Temperatue_Object,7,false);<br><br>&nbsp;<br><br>另一方面客户端调用的方法weekSpecified 的值被设为 &#8220;true&#8221;时，那么它引用在清单11中的重载方法，以及现在的SOAP请求看起来就像清单14的那样，以一个XML标签来传递&#8220;week&#8221; 参数。<br><br>&nbsp;<br>SendTemperature(Temperature_Object,7,true);<br><br>&nbsp;<br><br>&lt;soap:Envelope <br><br>xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" <br><br>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br><br>xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;<br><br>&lt;soap:Body&gt;<br><br>&lt;Collection xmlns="http://interop.webservices.fhso.ch/docLit"&gt;<br><br>&lt;Temperatue&gt;<br><br>&lt;Id&gt;2Id&gt;<br><br>&lt;Name&gt;Station 1Name&gt;<br><br>&lt;value&gt;34.2value&gt;<br><br>Temperature&gt;<br><br>&lt;Temperature&gt;<br><br>&lt;Id&gt;56Id&gt;<br><br>&lt;Name&gt;Station 3Name&gt;<br><br>&lt;value&gt;23.5value&gt;<br><br>Temperature&gt;<br><br>&lt;week&gt;7week&gt;<br><br>Collection&gt;<br><br>soap:Body&gt;<br><br>soap:Envelope&gt;<br><br>清单14. 第二个重载操作的SOAP消息形式<br><br>现在我们已经看到因Document/Literal 外覆样式引起的问题怎样通过使用标准的Document/Literal样式而巧妙地解决了。<br><br>结论<br><br>Document-centric和RPC 样式的Web服务提供了非常不同的应用环境，Document样式的web服务更加适合于企业到企业在Internet上的交互，开发一个Document样式的web服务也许需要比RPC样式付出更多的努力。我们看到了Document/Literal 样式和Document/Literal 外覆样式相比于其它设计样式带给我们更多弹性和优点。任何时候，你不要对已经存在的RPC样式进行接口连接，document样式的益处通常价值大于对服务进行接口连接。如果你设计ｗｅｂ服务的主要需求是互用性，协调工作，那么RPC/Encoded样式就十分气馁了。<br><br>在WS-I Basic profile 1.0不提倡使用RPC/Encoded方法，推荐使用Document/Literal方法，还有RPC/Literal是惟一允许的RPC 样式的style/use组合。许多人都认为在将来的WSI-profile版本中会屏弃RPC/Literal样式 。<br><br>参考<br><br>&nbsp;&nbsp;&nbsp; * [WSI] WS-I Final Specification " BasicProfile Version 1.0a", 8 August 2003.<br>&nbsp;&nbsp;&nbsp; * [WSDL] WSDL Specification.<br>&nbsp;&nbsp;&nbsp; * [SOAP] SOAP Specification.<br>&nbsp;&nbsp;&nbsp; * [IBMWS] IBM Developer works Web Services article suit.<br>&nbsp;&nbsp;&nbsp; * [XMETH] Publicly available web services in both Doc/RPC styles at Xmethods.<br>&nbsp;&nbsp;&nbsp; * [MSDN] Microsoft&#8217;s MSDN Web Services article library.<br><img src ="http://www.cppblog.com/qiujian5628/aggbug/61970.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-09-16 13:50 <a href="http://www.cppblog.com/qiujian5628/articles/61970.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>B 树、 B- 树、 B+ 树、 B* 树都是什么</title><link>http://www.cppblog.com/qiujian5628/articles/42190.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Wed, 30 Jan 2008 03:10:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/42190.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/42190.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/42190.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/42190.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/42190.html</trackback:ping><description><![CDATA[<p><strong><font size=3><span><font face="Times New Roman">B</font></span><span>树</span></font></strong></p>
<p><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>即二叉搜索树：</span></font></p>
<p><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1.</font></span><span>所有非叶子结点至多拥有两个儿子（</span><span><font face="Times New Roman">Left</font></span><span>和</span><span><font face="Times New Roman">Right</font></span><span>）；</span></font></p>
<p><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2.</font></span><span>所有结点存储一个关键字；</span></font></p>
<p><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>3.</font></span><span>非叶子结点的左指针指向小于其关键字的子树，右指针指向大于其关键字的子树；</span></font></p>
<p><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>如：</span></font></p>
<div align=center src_cetemp="/images/cppblog_com/qiujian5628/1.jpg"><img height=252 alt="" src="http://www.cppblog.com/images/cppblog_com/qiujian5628/1.jpg" width=247 border=0></div>
B<span>树的搜索，从根结点开始，如果查询的关键字与结点的关键字相等，那么就命中；否则，如果查询关键字比结点关键字小，就进入左儿子；如果比结点关键字大，就进入右儿子；如果左儿子或右儿子的指针为空，则报告找不到相应的关键字；</span>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>如果</span><span><font face="Times New Roman">B</font></span><span>树的所有非叶子结点的左右子树的结点数目均保持差不多（平衡），那么</span><span><font face="Times New Roman">B</font></span><span>树的搜索性能逼近二分查找；但它比连续内存空间的二分查找的优点是，改变</span><span><font face="Times New Roman">B</font></span><span>树结构（插入与删除结点）不需要移动大段的内存数据，甚至通常是常数开销；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>如：</span></font></p>
<div align=center src_cetemp="/images/cppblog_com/qiujian5628/2.jpg"><img height=252 alt="" src="http://www.cppblog.com/images/cppblog_com/qiujian5628/2.jpg" width=523 border=0></div>
但<span><font face="Times New Roman">B</font></span><span>树在经过多次插入与删除后，有可能导致不同的结构：<br></span>
<div align=center src_cetemp="/images/cppblog_com/qiujian5628/31.jpg"><img height=248 alt="" src="http://www.cppblog.com/images/cppblog_com/qiujian5628/31.jpg" width=485 border=0></div>
右边也是一个<span><font face="Times New Roman">B</font></span><span>树，但它的搜索性能已经是线性的了；同样的关键字集合有可能导致不同的树结构索引；所以，使用</span><span><font face="Times New Roman">B</font></span><span>树还要考虑尽可能让</span><span><font face="Times New Roman">B</font></span><span>树保持左图的结构，和避免右图的结构，也就是所谓的&#8220;平衡&#8221;问题；</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<p style="MARGIN: 0cm 0cm 0pt" align=left><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>实际使用的</span><span><font face="Times New Roman">B</font></span><span>树都是在原</span><span><font face="Times New Roman">B</font></span><span>树的基础上加上平衡算法，即&#8220;平衡二叉树&#8221;；如何保持</span><span><font face="Times New Roman">B</font></span><span>树结点分布均匀的平衡算法是平衡二叉树的关键；平衡算法是一种在</span><span><font face="Times New Roman">B</font></span><span>树中插入和删除结点的策略；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"></p>
<p style="MARGIN: 0cm 0cm 0pt"></p>
<p style="MARGIN: 0cm 0cm 0pt"><strong><font size=3><span><font face="Times New Roman">B-</font></span><span>树</span></font></strong></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>是一种多路搜索树（并不是二叉的）：</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1.</font></span><span>定义任意非叶子结点最多只有</span><span><font face="Times New Roman">M</font></span><span>个儿子；且</span><span><font face="Times New Roman">M&gt;2</font></span><span>；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2.</font></span><span>根结点的儿子数为</span><span><font face="Times New Roman">[2, M]</font></span><span>；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>3.</font></span><span>除根结点以外的非叶子结点的儿子数为</span><span><font face="Times New Roman">[M/2, M]</font></span><span>；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>4.</font></span><span>每个结点存放至少</span><span><font face="Times New Roman">M/2-1</font></span><span>（取上整）和至多</span><span><font face="Times New Roman">M-1</font></span><span>个关键字；（至少</span><span><font face="Times New Roman">2</font></span><span>个关键字）</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>5.</font></span><span>非叶子结点的关键字个数</span><span><font face="Times New Roman">=</font></span><span>指向儿子的指针个数</span><span><font face="Times New Roman">-1</font></span><span>；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>6.</font></span><span>非叶子结点的关键字：</span><span><font face="Times New Roman">K[1], K[2], &#8230;, K[M-1]</font></span><span>；且</span><span><font face="Times New Roman">K[i] &lt; K[i+1]</font></span><span>；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>7.</font></span><span>非叶子结点的指针：</span><span><font face="Times New Roman">P[1], P[2], &#8230;, P[M]</font></span><span>；其中</span><span><font face="Times New Roman">P[1]</font></span><span>指向关键字小于</span><span><font face="Times New Roman">K[1]</font></span><span>的子树，</span><span><font face="Times New Roman">P[M]</font></span><span>指向关键字大于</span><span><font face="Times New Roman">K[M-1]</font></span><span>的子树，其它</span><span><font face="Times New Roman">P[i]</font></span><span>指向关键字属于</span><span><font face="Times New Roman">(K[i-1], K[i])</font></span><span>的子树；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>8.</font></span><span>所有叶子结点位于同一层；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>如：（</span><span><font face="Times New Roman">M=3</font></span><span>）</span></font></p>
<div align=center src_cetemp="/images/cppblog_com/qiujian5628/4.jpg"><img height=268 alt="" src="http://www.cppblog.com/images/cppblog_com/qiujian5628/4.jpg" width=624 border=0></div>
<br>B-<span>树的搜索，从根结点开始，对结点内的关键字（有序）序列进行二分查找，如果命中则结束，否则进入查询关键字所属范围的儿子结点；重复，直到所对应的儿子指针为空，或已经是叶子结点；</span>
<p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt"><font size=3><span><font face="Times New Roman">B-</font></span><span>树的特性：</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1.</font></span><span>关键字集合分布在整颗树中；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2.</font></span><span>任何一个关键字出现且只出现在一个结点中；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>3.</font></span><span>搜索有可能在非叶子结点结束；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>4.</font></span><span>其搜索性能等价于在关键字全集内做一次二分查找；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>5.</font></span><span>自动层次控制；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>由于限制了除根结点以外的非叶子结点，至少含有</span><span><font face="Times New Roman">M/2</font></span><span>个儿子，确保了结点的至少利用率，其最底搜索性能为：<br>
<div align=center src_cetemp="/images/cppblog_com/qiujian5628/41.jpg"><img height=256 alt="" src="http://www.cppblog.com/images/cppblog_com/qiujian5628/41.jpg" width=328 border=0></div>
</span></font>
<p>&#160;</p>
<br><span>其中，</span><span><font face="Times New Roman">M</font></span><span>为设定的非叶子结点最多子树个数，</span><span><font face="Times New Roman">N</font></span><span>为关键字总数；</span>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>所以</span><span><font face="Times New Roman">B-</font></span><span>树的性能总是等价于二分查找（与</span><span><font face="Times New Roman">M</font></span><span>值无关），也就没有</span><span><font face="Times New Roman">B</font></span><span>树平衡的问题；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>由于</span><span><font face="Times New Roman">M/2</font></span><span>的限制，在插入结点时，如果结点已满，需要将结点分裂为两个各占</span><span><font face="Times New Roman">M/2</font></span><span>的结点；删除结点时，需将两个不足</span><span><font face="Times New Roman">M/2</font></span><span>的兄弟结点合并；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"></p>
<p style="MARGIN: 0cm 0cm 0pt"></p>
<p style="MARGIN: 0cm 0cm 0pt"><strong><font size=3><span><font face="Times New Roman">B+</font></span><span>树</span></font></strong></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B+</font></span><span>树是</span><span><font face="Times New Roman">B-</font></span><span>树的变体，也是一种多路搜索树：</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1.</font></span><span>其定义基本与</span><span><font face="Times New Roman">B-</font></span><span>树同，除了：</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2.</font></span><span>非叶子结点的子树指针与关键字个数相同；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>3.</font></span><span>非叶子结点的子树指针</span><span><font face="Times New Roman">P[i]</font></span><span>，指向关键字值属于</span><span><font face="Times New Roman">[K[i], K[i+1])</font></span><span>的子树（</span><span><font face="Times New Roman">B-</font></span><span>树是开区间）；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>5.</font></span><span>为所有叶子结点增加一个链指针；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>6.</font></span><span>所有关键字都在叶子结点出现；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>如：（</span><span><font face="Times New Roman">M=3</font></span><span>）</span></font></p>
<div align=center src_cetemp="/images/cppblog_com/qiujian5628/5.jpg"><img height=345 alt="" src="http://www.cppblog.com/images/cppblog_com/qiujian5628/5.jpg" width=569 border=0></div>
<span><font face="Times New Roman">B+</font></span><span>的搜索与</span><span><font face="Times New Roman">B-</font></span><span>树也基本相同，区别是</span><span><font face="Times New Roman">B+</font></span><span>树只有达到叶子结点才命中（</span><span><font face="Times New Roman">B-</font></span><span>树可以在非叶子结点命中），其性能也等价于在关键字全集做一次二分查找；</span>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B+</font></span><span>的特性：</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1.</font></span><span>所有关键字都出现在叶子结点的链表中（稠密索引），且链表中的关键字恰好是有序的；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2.</font></span><span>不可能在非叶子结点命中；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>3.</font></span><span>非叶子结点相当于是叶子结点的索引（稀疏索引），叶子结点相当于是存储（关键字）数据的数据层；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>4.</font></span><span>更适合文件索引系统；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"></p>
<p style="MARGIN: 0cm 0cm 0pt"><strong><font size=3><span><font face="Times New Roman">B*</font></span><span>树</span></font></strong></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>是</span><span><font face="Times New Roman">B+</font></span><span>树的变体，在</span><span><font face="Times New Roman">B+</font></span><span>树的非根和非叶子结点再增加指向兄弟的指针；</span></font></p>
<div align=center src_cetemp="/images/cppblog_com/qiujian5628/6.jpg"><img height=345 alt="" src="http://www.cppblog.com/images/cppblog_com/qiujian5628/6.jpg" width=569 border=0></div>
<span><font face="Times New Roman">B*</font></span><span>树定义了非叶子结点关键字个数至少为</span><span><font face="Times New Roman">(2/3)*M</font></span><span>，即块的最低使用率为</span><span><font face="Times New Roman">2/3</font></span><span>（代替</span><span><font face="Times New Roman">B+</font></span><span>树的</span><span><font face="Times New Roman">1/2</font></span><span>）；</span>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B+</font></span><span>树的分裂：当一个结点满时，分配一个新的结点，并将原结点中</span><span><font face="Times New Roman">1/2</font></span><span>的数据复制到新结点，最后在父结点中增加新结点的指针；</span><span><font face="Times New Roman">B+</font></span><span>树的分裂只影响原结点和父结点，而不会影响兄弟结点，所以它不需要指向兄弟的指针；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B*</font></span><span>树的分裂：当一个结点满时，如果它的下一个兄弟结点未满，那么将一部分数据移到兄弟结点中，再在原结点插入关键字，最后修改父结点中兄弟结点的关键字（因为兄弟结点的关键字范围改变了）；如果兄弟也满了，则在原结点与兄弟结点之间增加新结点，并各复制</span><span><font face="Times New Roman">1/3</font></span><span>的数据到新结点，最后在父结点增加新结点的指针；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>所以，</span><span><font face="Times New Roman">B*</font></span><span>树分配新结点的概率比</span><span><font face="Times New Roman">B+</font></span><span>树要低，空间使用率更高；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"></p>
<p style="MARGIN: 0cm 0cm 0pt"><span><strong><font size=3>小结</font></strong></span></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B</font></span><span>树：二叉树，每个结点只存储一个关键字，等于则命中，小于走左结点，大于走右结点；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B-</font></span><span>树：多路搜索树，每个结点存储</span><span><font face="Times New Roman">M/2</font></span><span>到</span><span><font face="Times New Roman">M</font></span><span>个关键字，非叶子结点存储指向关键字范围的子结点；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span style="mso-tab-count: 1"><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>所有关键字在整颗树中出现，且只出现一次，非叶子结点可以命中；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B+</font></span><span>树：在</span><span><font face="Times New Roman">B-</font></span><span>树基础上，为叶子结点增加链表指针，所有关键字都在叶子结点中出现，非叶子结点作为叶子结点的索引；</span><span><font face="Times New Roman">B+</font></span><span>树总是到叶子结点才命中；</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>B*</font></span><span>树：在</span><span><font face="Times New Roman">B+</font></span><span>树基础上，为非叶子结点也增加链表指针，将结点的最低利用率从</span><span><font face="Times New Roman">1/2</font></span><span>提高到</span><span><font face="Times New Roman">2/3</font></span><span>；</span></font></p>
<br>
<img src ="http://www.cppblog.com/qiujian5628/aggbug/42190.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-01-30 11:10 <a href="http://www.cppblog.com/qiujian5628/articles/42190.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux网络编程一步一步学-epoll同时处理海量连接的代码</title><link>http://www.cppblog.com/qiujian5628/articles/42168.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Wed, 30 Jan 2008 01:15:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/42168.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/42168.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/42168.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/42168.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/42168.html</trackback:ping><description><![CDATA[<table style="WIDTH: 100%" cellSpacing=1 cellPadding=1 align=baseline border=1>
    <tbody>
        <tr>
            <td><stdio.h><stdlib.h><errno.h><string.h><sys><netinet><sys><sys><unistd.h><arpa><openssl><openssl><fcntl.h><sys><sys><sys></sys></sys></sys></fcntl.h></openssl></openssl></arpa></unistd.h></sys></sys></netinet></sys></string.h></errno.h></stdlib.h></stdio.h>#include &lt;stdio.h&gt;<br>#include &lt;stdlib.h&gt;<br>#include &lt;errno.h&gt;<br>#include &lt;string.h&gt;<br>#include &lt;sys/types.h&gt;<br>#include &lt;netinet/in.h&gt;<br>#include &lt;sys/socket.h&gt;<br>#include &lt;sys/wait.h&gt;<br>#include &lt;unistd.h&gt;<br>#include &lt;arpa/inet.h&gt;<br>#include &lt;openssl/ssl.h&gt;<br>#include &lt;openssl/err.h&gt;<br>#include &lt;fcntl.h&gt;<br>#include &lt;sys/epoll.h&gt;<br>#include &lt;sys/time.h&gt;<br>#include &lt;sys/resource.h&gt;<br><br><br>#define MAXBUF 1024<br>#define MAXEPOLLSIZE 10000<br><br>/*<br>setnonblocking - 设置句柄为非阻塞方式<br>*/<br>int setnonblocking(int sockfd)<br>{<br>&nbsp;&nbsp;&nbsp; if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br><br>/*<br>handle_message - 处理每个 socket 上的消息收发<br>*/<br>int handle_message(int new_fd)<br>{<br>&nbsp;&nbsp;&nbsp; char buf[MAXBUF + 1];<br>&nbsp;&nbsp;&nbsp; int len;<br>&nbsp;&nbsp;&nbsp; /* 开始处理每个新连接上的数据收发 */<br>&nbsp;&nbsp;&nbsp; bzero(buf, MAXBUF + 1);<br>&nbsp;&nbsp;&nbsp; /* 接收客户端的消息 */<br>&nbsp;&nbsp;&nbsp; len = recv(new_fd, buf, MAXBUF, 0);<br>&nbsp;&nbsp;&nbsp; if (len &gt; 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ("%d接收消息成功:'%s'，共%d个字节的数据\n",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new_fd, buf, len);<br>&nbsp;&nbsp;&nbsp; else {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (len &lt; 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ("消息接收失败！错误代码是%d，错误信息是'%s'\n",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; errno, strerror(errno));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(new_fd);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; /* 处理每个新连接上的数据收发结束 */<br>&nbsp;&nbsp;&nbsp; return len;<br>}<br>/************关于本文档********************************************<br>*filename: epoll-server.c<br>*purpose: 演示epoll处理海量socket连接的方法<br>*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)<br>Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言<br>*date time:2007-01-31 21:00<br>*Note: 任何人可以任意复制代码并运用这些文档，当然包括你的商业用途<br>* 但请遵循GPL<br>*Thanks to:Google<br>*Hope:希望越来越多的人贡献自己的力量，为科学技术发展出力<br>* 科技站在巨人的肩膀上进步更快！感谢有开源前辈的贡献！<br>*********************************************************************/<br>int main(int argc, char **argv)<br>{<br>&nbsp;&nbsp;&nbsp; int listener, new_fd, kdpfd, nfds, n, ret, curfds;<br>&nbsp;&nbsp;&nbsp; socklen_t len;<br>&nbsp;&nbsp;&nbsp; struct sockaddr_in my_addr, their_addr;<br>&nbsp;&nbsp;&nbsp; unsigned int myport, lisnum;<br>&nbsp;&nbsp;&nbsp; struct epoll_event ev;<br>&nbsp;&nbsp;&nbsp; struct epoll_event events[MAXEPOLLSIZE];<br>&nbsp;&nbsp;&nbsp; struct rlimit rt;<br><br>&nbsp;&nbsp;&nbsp; if (argv[1])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; myport = atoi(argv[1]);<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; myport = 7838;<br><br>&nbsp;&nbsp;&nbsp; if (argv[2])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lisnum = atoi(argv[2]);<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lisnum = 2;<br><br>&nbsp;&nbsp;&nbsp; /* 设置每个进程允许打开的最大文件数 */<br>&nbsp;&nbsp;&nbsp; rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;<br>&nbsp;&nbsp;&nbsp; if (setrlimit(RLIMIT_NOFILE, &amp;rt) == -1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("setrlimit");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(1);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; else printf("设置系统资源参数成功！\n");<br><br>&nbsp;&nbsp;&nbsp; /* 开启 socket 监听 */<br>&nbsp;&nbsp;&nbsp; if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("socket");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(1);<br>&nbsp;&nbsp;&nbsp; } else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("socket 创建成功！\n");<br><br>&nbsp;&nbsp;&nbsp; setnonblocking(listener);<br><br>&nbsp;&nbsp;&nbsp; bzero(&amp;my_addr, sizeof(my_addr));<br>&nbsp;&nbsp;&nbsp; my_addr.sin_family = PF_INET;<br>&nbsp;&nbsp;&nbsp; my_addr.sin_port = htons(myport);<br>&nbsp;&nbsp;&nbsp; if (argv[3])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my_addr.sin_addr.s_addr = inet_addr(argv[3]);<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my_addr.sin_addr.s_addr = INADDR_ANY;<br><br>&nbsp;&nbsp;&nbsp; if (bind<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (listener, (struct sockaddr *) &amp;my_addr, sizeof(struct sockaddr))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; == -1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("bind");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(1);<br>&nbsp;&nbsp;&nbsp; } else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("IP 地址和端口绑定成功\n");<br><br>&nbsp;&nbsp;&nbsp; if (listen(listener, lisnum) == -1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("listen");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(1);<br>&nbsp;&nbsp;&nbsp; } else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("开启服务成功！\n");<br><br>&nbsp;&nbsp;&nbsp; /* 创建 epoll 句柄，把监听 socket 加入到 epoll 集合里 */<br>&nbsp;&nbsp;&nbsp; kdpfd = epoll_create(MAXEPOLLSIZE);<br>&nbsp;&nbsp;&nbsp; len = sizeof(struct sockaddr_in);<br>&nbsp;&nbsp;&nbsp; ev.events = EPOLLIN | EPOLLET;<br>&nbsp;&nbsp;&nbsp; ev.data.fd = listener;<br>&nbsp;&nbsp;&nbsp; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &amp;ev) &lt; 0) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; } else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("监听 socket 加入 epoll 成功！\n");<br>&nbsp;&nbsp;&nbsp; curfds = 1;<br>&nbsp;&nbsp;&nbsp; while (1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* 等待有事件发生 */<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nfds = epoll_wait(kdpfd, events, curfds, -1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (nfds == -1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("epoll_wait");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* 处理所有事件 */<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (n = 0; n &lt; nfds; ++n) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (events[n].data.fd == listener) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new_fd = accept(listener, (struct sockaddr *) &amp;their_addr,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;len);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (new_fd &lt; 0) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("accept");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("有连接来自于： %d:%d， 分配的 socket 为:%d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setnonblocking(new_fd);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ev.events = EPOLLIN | EPOLLET;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ev.data.fd = new_fd;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &amp;ev) &lt; 0) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, "把 socket '%d' 加入 epoll 失败！%s\n",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new_fd, strerror(errno));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; curfds++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret = handle_message(events[n].data.fd);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (ret &lt; 1 &amp;&amp; errno != 11) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;ev);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; curfds--;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; close(listener);<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br></td>
        </tr>
    </tbody>
</table>
<br>编译此程序用命令：<br>gcc -Wall epoll-server.c -o server<br><br>运行此程序需要具有管理员权限！<br><br>sudo ./server 7838 1<br><br>通过测试这一个服务器可能同时处理10000 -3 = 9997 个连接！<br><br>如果这是一个在线服务系统，那么它可以支持9997人同时在线，比如游戏、聊天等。
<img src ="http://www.cppblog.com/qiujian5628/aggbug/42168.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-01-30 09:15 <a href="http://www.cppblog.com/qiujian5628/articles/42168.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>给echo服务器增加读线程池</title><link>http://www.cppblog.com/qiujian5628/articles/42167.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Wed, 30 Jan 2008 01:14:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/42167.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/42167.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/42167.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/42167.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/42167.html</trackback:ping><description><![CDATA[上篇文章使用linux内核2.6提供的epoll机制实现了一个反应式echo服务器，使用反应式服务器的最大好处就是可以按cpu的数量来配置线程池内线程的线程数而不是根据客户端的并发量配置线程池。我是第一次使用pthread库来写线程池，使用的是工作队列方式的线程池。我感觉作队列方式的线程池可以当成一种设计模式来用的，在很多平台上都是可以按这种方式来实现线程池，从win32 ,unix到jvm都是适用的<br><br><STYLE><!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;}@font-face {font-family:新宋体; panose-1:2 1 6 9 3 1 1 1 1 1;}@font-face {font-family:"\@新宋体"; panose-1:2 1 6 9 3 1 1 1 1 1;}@font-face {font-family:"\@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;}div.Section1 {page:Section1;}--></STYLE>
<div>
<p align=left>&nbsp;</p>
<p align=left><span>#include</span><span> &lt;iostream&gt;</span></p>
<p align=left><span>#include</span><span> &lt;sys/socket.h&gt;</span></p>
<p align=left><span>#include</span><span> &lt;sys/epoll.h&gt;</span></p>
<p align=left><span>#include</span><span> &lt;netinet/in.h&gt;</span></p>
<p align=left><span>#include</span><span> &lt;arpa/inet.h&gt;</span></p>
<p align=left><span>#include</span><span> &lt;fcntl.h&gt;</span></p>
<p align=left><span>#include</span><span> &lt;unistd.h&gt;</span></p>
<p align=left><span>#include</span><span> &lt;stdio.h&gt;</span></p>
<p align=left><span>#include</span><span> &lt;pthread.h&gt;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>#define</span><span> MAXLINE 10</span></p>
<p align=left><span>#define</span><span> OPEN_MAX 100</span></p>
<p align=left><span>#define</span><span> LISTENQ 20</span></p>
<p align=left><span>#define</span><span> SERV_PORT 5555</span></p>
<p align=left><span>#define</span><span> INFTIM 1000</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>//</span><span>线程池任务队列结构体</span></p>
<p align=left><span>struct</span><span> task{</span></p>
<p align=left><span>&nbsp;&nbsp;<span>int</span> fd; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>//</span></span><span>需要读写的文件描述符</span></p>
<p align=left><span>&nbsp;&nbsp;<span>struct</span> task *next; <span>//</span></span><span>下一个任务</span></p>
<p align=left><span>};</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>//</span><span>用于读写两个的两个方面传递参数</span></p>
<p align=left><span>struct</span><span> user_data{</span></p>
<p align=left><span>&nbsp;&nbsp;<span>int</span> fd;</span></p>
<p align=left><span>&nbsp;&nbsp;<span>unsigned</span> <span>int</span> n_size;</span></p>
<p align=left><span>&nbsp;&nbsp;<span>char</span> line[MAXLINE];</span></p>
<p align=left><span>};</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>//</span><span>线程的任务函数</span></p>
<p align=left><span>void</span><span> * readtask(<span>void</span> *args);</span></p>
<p align=left><span>void</span><span> * writetask(<span>void</span> *args);</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left><span>//</span><span>声明<span>epoll_event</span>结构体的变量<span>,ev</span>用于注册事件<span>,</span>数组用于回传要处理的事件</span></p>
<p align=left><span>struct</span><span> epoll_event ev,events[20];</span></p>
<p align=left><span>int</span><span> epfd;</span></p>
<p align=left><span>pthread_mutex_t mutex;</span></p>
<p align=left><span>pthread_cond_t cond1;</span></p>
<p align=left><span>struct</span><span> task *readhead=NULL,*readtail=NULL,*writehead=NULL;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>void</span><span> setnonblocking(<span>int</span> sock)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>int</span> opts;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; opts=fcntl(sock,F_GETFL);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span>(opts&lt;0)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; perror("fcntl(sock,GETFL)");</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; exit(1);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;opts = opts|O_NONBLOCK;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span>(fcntl(sock,F_SETFL,opts)&lt;0)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; perror("fcntl(sock,SETFL,opts)");</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; exit(1);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>int</span><span> main()</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>int</span> i, maxi, listenfd, connfd, sockfd,nfds;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; pthread_t tid1,tid2;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>struct</span> task *new_task=NULL;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>struct</span> user_data *rdata=NULL;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; socklen_t clilen;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; pthread_mutex_init(&amp;mutex,NULL);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; pthread_cond_init(&amp;cond1,NULL);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>初始化用于读线程池的线程</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; pthread_create(&amp;tid1,NULL,readtask,NULL);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; pthread_create(&amp;tid2,NULL,readtask,NULL);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>生成用于处理<span>accept</span>的<span>epoll</span>专用的文件描述符<span>&nbsp;&nbsp;&nbsp; </span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; epfd=epoll_create(256);</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>struct</span> sockaddr_in clientaddr;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>struct</span> sockaddr_in serveraddr;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; listenfd = socket(AF_INET, SOCK_STREAM, 0);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>把<span>socket</span>设置为非阻塞方式</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; setnonblocking(listenfd);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>设置与要处理的事件相关的文件描述符</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; ev.data.fd=listenfd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>设置要处理的事件类型</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; ev.events=EPOLLIN|EPOLLET;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>注册<span>epoll</span>事件</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&amp;ev);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; bzero(&amp;serveraddr, <span>sizeof</span>(serveraddr));</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; serveraddr.sin_family = AF_INET;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>char</span> *local_addr="200.200.200.222";</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; inet_aton(local_addr,&amp;(serveraddr.sin_addr));<span>//htons(SERV_PORT);</span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; serveraddr.sin_port=htons(SERV_PORT);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; bind(listenfd,(sockaddr *)&amp;serveraddr, <span>sizeof</span>(serveraddr));</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; listen(listenfd, LISTENQ);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; maxi = 0; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>for</span> ( ; ; ) {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>等待<span>epoll</span>事件的发生</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; nfds=epoll_wait(epfd,events,20,500);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>处理所发生的所有事件<span> &nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>for</span>(i=0;i&lt;nfds;++i)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>if</span>(events[i].data.fd==listenfd)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; connfd = accept(listenfd,(sockaddr *)&amp;clientaddr, &amp;clilen);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>if</span>(connfd&lt;0){</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;perror("connfd&lt;0");</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;exit(1);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;}</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; setnonblocking(connfd);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>char</span> *str = inet_ntoa(clientaddr.sin_addr);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;std::cout&lt;&lt;"connec_ from &gt;&gt;"&lt;&lt;str&lt;&lt;std::endl;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>设置用于读操作的文件描述符</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; ev.data.fd=connfd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>设置用于注测的读操作事件</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ev.events=EPOLLIN|EPOLLET;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>注册<span>ev</span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&amp;ev);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>else</span> <span>if</span>(events[i].events&amp;EPOLLIN)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; printf("reading!\n");&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> ( (sockfd = events[i].data.fd) &lt; 0) <span>continue</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; new_task=<span>new</span> task();</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; new_task-&gt;fd=sockfd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; new_task-&gt;next=NULL;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>//</span></span><span>添加新的读任务</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; pthread_mutex_lock(&amp;mutex);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>if</span>(readhead==NULL)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;readhead=new_task;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;readtail=new_task;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>else</span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readtail-&gt;next=new_task;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;readtail=new_task;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;<span>//</span></span><span>唤醒所有等待<span>cond1</span>条件的线程</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; pthread_cond_broadcast(&amp;cond1);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; pthread_mutex_unlock(&amp;mutex); &nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;}</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; <span>else</span> <span>if</span>(events[i].events&amp;EPOLLOUT)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rdata=(<span>struct</span> user_data *)events[i].data.ptr;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;sockfd = rdata-&gt;fd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;write(sockfd, rdata-&gt;line, rdata-&gt;n_size);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;<span>delete</span> rdata;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;<span>//</span></span><span>设置用于读操作的文件描述符</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;ev.data.fd=sockfd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;<span>//</span></span><span>设置用于注测的读操作事件</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ev.events=EPOLLIN|EPOLLET;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;<span>//</span></span><span>修改<span>sockfd</span>上要处理的事件为<span>EPOLIN</span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&amp;ev);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>}</span></p>
<p align=left><span>void</span><span> * readtask(<span>void</span> *args)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;<span>int</span> fd=-1;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;<span>unsigned</span> <span>int</span> n;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;<span>//</span></span><span>用于把读出来的数据传递出去</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;<span>struct</span> user_data *data = NULL;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;<span>while</span>(1){</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pthread_mutex_lock(&amp;mutex);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>//</span></span><span>等待到任务队列不为空</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>while</span>(readhead==NULL)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pthread_cond_wait(&amp;cond1,&amp;mutex);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;fd=readhead-&gt;fd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>//</span></span><span>从任务队列取出一个读任务</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>struct</span> task *tmp=readhead;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;readhead = readhead-&gt;next;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>delete</span> tmp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pthread_mutex_unlock(&amp;mutex);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;data = <span>new</span> user_data();</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;data-&gt;fd=fd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>if</span> ( (n = read(fd, data-&gt;line, MAXLINE)) &lt; 0) {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<span>if</span> (errno == ECONNRESET) {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;close(fd);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <span>else</span> </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;std::cout&lt;&lt;"readline error"&lt;&lt;std::endl;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<span>if</span>(data!=NULL)<span>delete</span> data;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;} <span>else</span> <span>if</span> (n == 0) {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;close(fd);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;printf("Client close connect!\n");</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<span>if</span>(data!=NULL)<span>delete</span> data;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;} <span>else</span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;data-&gt;n_size=n;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>//</span></span><span>设置需要传递出去的数据</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ev.data.ptr=data;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>//</span></span><span>设置用于注测的写操作事件</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ev.events=EPOLLOUT|EPOLLET;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<span>//</span></span><span>修改<span>sockfd</span>上要处理的事件为<span>EPOLLOUT</span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&amp;ev);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;}</span></p>
<p align=left><span>}</span></p>
<p>&nbsp;</p>
</div>
<img src ="http://www.cppblog.com/qiujian5628/aggbug/42167.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-01-30 09:14 <a href="http://www.cppblog.com/qiujian5628/articles/42167.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下Epoll的简单介绍</title><link>http://www.cppblog.com/qiujian5628/articles/42166.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Wed, 30 Jan 2008 01:12:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/42166.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/42166.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/42166.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/42166.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/42166.html</trackback:ping><description><![CDATA[<div>Q：网络服务器的瓶颈在哪？ <br>A：IO效率。 </div>
<div>在大家苦苦的为在线人数的增长而导致的系统资源吃紧上的问题正在发愁的时候，Linux 2.6内核中提供的System Epoll为我们提供了一套完美的解决方案。传统的select以及poll</div>
<div>的效率会因为在线人数的线形递增而导致呈二次乃至三次方的下降，这些直接导致了网络服务器可以支持的人数有了个比较明显的限制。 </div>
<div>自从Linux提供了/dev/epoll的设备以及后来2.6内核中对/dev/epoll设备的访问的封装（System Epoll）之后，这种现象得到了大大的缓解，如果说几个月前，大家还对epoll不熟悉，那么现在来说的话，epoll的应用已经得到了大范围的普及。 </div>
<div>那么究竟如何来使用epoll呢？其实非常简单。 <br>通过在包含一个头文件#include 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。 </div>
<div>&nbsp;</div>
<div>首先通过create_epoll(int maxfds)来创建一个epoll的句柄，</div>
<div>其中maxfds为你epoll所支持的最大句柄数。</div>
<div>这个函数会返回一个新的epoll句柄，之后的所有操作将通过这个句柄来进行操作。</div>
<div>在用完之后，记得用close()来关闭这个创建出来的epoll句柄。 </div>
<div>之后在你的网络主循环里面，</div>
<div>每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口，看哪一个可以读，哪一个可以写了。</div>
<div>基本的语法为： nfds = epoll_wait(kdpfd, events, maxevents, -1); </div>
<div><br>其中kdpfd为用epoll_create创建之后的句柄，events是一个epoll_event*的指针，当epoll_wait这个函数操作成功之后，epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是epoll_wait的超时，为0的时候表示马上返回，为-1的时候表示一直等下去，直到有事件范围，为任意正整数的时候表示等这么长的时间，如果一直没有事件，则范围。一般如果网络主循环是单独的线程的话，可以用-1来等，这样可以保证一些效率，如果是和主逻辑在同一个线程的话，则可以用0来保证主循环的效率。 </div>
<div>epoll_wait范围之后应该是一个循环，遍利所有的事件：
<table style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="95%" bgColor=#f1f1f1 border=1>
    <tbody>
        <tr>
            <td>
            <p style="MARGIN: 5px; LINE-HEIGHT: 150%"><code><span style="COLOR: #000000"><font face=新宋体><span style="COLOR: #0000ff">for</span><span style="COLOR: #0000cc">(</span>n <span style="COLOR: #0000cc">=</span> 0<span style="COLOR: #0000cc">;</span> n <span style="COLOR: #0000cc">&lt;</span> nfds<span style="COLOR: #0000cc">;</span> <span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span>n<span style="COLOR: #0000cc">)</span> <br><span style="COLOR: #0000cc">{</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>events<span style="COLOR: #0000cc">[</span>n<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">.</span>data<span style="COLOR: #0000cc">.</span>fd <span style="COLOR: #0000cc">=</span><span style="COLOR: #0000cc">=</span> listener<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> </font><font face=新宋体><span style="COLOR: #ff9900">//如果是主socket的事件的话，则表示有新连接进入了，进行新连接的处理。 <br></span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;client <span style="COLOR: #0000cc">=</span> <span style="COLOR: #ff0000">accept</span><span style="COLOR: #0000cc">(</span>listener<span style="COLOR: #0000cc">,</span> <span style="COLOR: #0000cc">(</span><span style="COLOR: #0000ff">struct</span> <span style="COLOR: #ff0000">sockaddr</span> <span style="COLOR: #0000cc">*</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">&amp;</span>local<span style="COLOR: #0000cc">,</span> <span style="COLOR: #0000cc">&amp;</span>addrlen<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>client <span style="COLOR: #0000cc">&lt;</span> 0<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">{</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">perror</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff00ff">"accept"</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">continue</span><span style="COLOR: #0000cc">;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setnonblocking<span style="COLOR: #0000cc">(</span>client<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> </font><font face=新宋体><span style="COLOR: #ff9900">// 将新连接置于非阻塞模式&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ev<span style="COLOR: #0000cc">.</span>events <span style="COLOR: #0000cc">=</span> EPOLLIN <span style="COLOR: #0000cc">|</span> EPOLLET<span style="COLOR: #0000cc">;</span> </font><font face=新宋体><span style="COLOR: #ff9900">// 并且将新连接也加入EPOLL的监听队列。 注意，这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听，如果有写操</span>作的话，这个时候epoll是不会返回事件的，如果要对写操作也监听的话，应该是EOLLIN <span style="COLOR: #0000cc">|</span> EPOLLOUT <span style="COLOR: #0000cc">|</span> EPOLLET <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ev<span style="COLOR: #0000cc">.</span>data<span style="COLOR: #0000cc">.</span>fd <span style="COLOR: #0000cc">=</span> client<span style="COLOR: #0000cc">;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span> <span style="COLOR: #0000cc">(</span>epoll_ctl<span style="COLOR: #0000cc">(</span>kdpfd<span style="COLOR: #0000cc">,</span> EPOLL_CTL_ADD<span style="COLOR: #0000cc">,</span> client<span style="COLOR: #0000cc">,</span> <span style="COLOR: #0000cc">&amp;</span>ev<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">&lt;</span> 0<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">/* 设置好event之后，将这个新的event通过epoll_ctl加入到epoll的监听队列里面，这里用EPOLL_CTL_ADD来加一个新的epoll事件，通过POLL_CTL_DEL来减少一个epoll事件，通过EPOLL_CTL_MOD来改变一个事件的监听方式。 */</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000"><font color=#000000>fprintf(stderr, "epoll set insertion error: fd=%d0, <br>&nbsp;&nbsp;&nbsp;&nbsp;client); <br>&nbsp;&nbsp;&nbsp;return -1; <br>&nbsp;&nbsp;} <br>&nbsp;} <br>&nbsp;else // 如果不是主socket的事件的话，则代表是一个用户socket的事件，则来处理这个用户socket的事情，比如说read(fd,xxx)之类的，或者一些其他的处理。 <br>&nbsp;&nbsp;do_use_fd(events[n].data.fd); <br>} </font></span></font></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<br></div>
<div>对，epoll的操作就这么简单，总共不过4个API：epoll_create, epoll_ctl, epoll_wait和close。 <br></div>
<img src ="http://www.cppblog.com/qiujian5628/aggbug/42166.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-01-30 09:12 <a href="http://www.cppblog.com/qiujian5628/articles/42166.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows完成端口与Linux epoll技术简介</title><link>http://www.cppblog.com/qiujian5628/articles/42165.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Wed, 30 Jan 2008 01:10:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/42165.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/42165.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/42165.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/42165.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/42165.html</trackback:ping><description><![CDATA[<p><strong>WINDOWS完成端口编程<br></strong>1、基本概念<br>2、WINDOWS完成端口的特点<br>3、完成端口（Completion Ports ）相关数据结构和创建<br>4、完成端口线程的工作原理<br>5、Windows完成端口的实例代码<br><strong>Linux的EPoll模型<br></strong>1、为什么select落后<br>2、内核中提高I/O性能的新方法epoll<br>3、epoll的优点<br>4、epoll的工作模式 <br>5、epoll的使用方法<br>6、Linux下EPOll编程实例<br><strong>总结</strong></p>
<p><strong>WINDOWS完成端口编程<br></strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 摘要：开发网络程序从来都不是一件容易的事情，尽管只需要遵守很少的一些规则;创建socket,发起连接，接受连接，发送和接受数据。真正的困难在于：让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windows平台完成端口进行重叠I/O的技术和Linux在2.6版本的内核中引入的EPOll技术，可以很方便在在在Windows和Linux平台上开发出支持大量连接的网络服务程序。本文介绍在Windows和Linux平台上使用的完成端口和EPoll模型开发的基本原理，同时给出实际的例子。本文主要关注C/S结构的服务器端程序，因为一般来说，开发一个大容量，具可扩展性的winsock程序一般就是指服务程序。<br><strong><br>1、基本概念<br></strong>&nbsp;&nbsp;&nbsp; 设备---windows操作系统上允许通信的任何东西，比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile等。所以我们不能看到**File函数就只想到文件设备。与设备通信有两种方式，同步方式和异步方式。同步方式下，当调用ReadFile函数时，函数会等待系统执行完所要求的工作，然后才返回；异步方式下，ReadFile这类函数会直接返回，系统自己去完成对设备的操作，然后以某种方式通知完成操作。<br>重叠I/O----顾名思义，当你调用了某个函数（比如ReadFile）就立刻返回做自己的其他动作的时候，同时系统也在对I/0设备进行你要求的操作，在这段时间内你的程序和系统的内部动作是重叠的，因此有更好的性能。所以，重叠I/O是用于异步方式下使用I/O设备的。 重叠I/O需要使用的一个非常重要的数据结构OVERLAPPED。<br><strong><br>2、WINDOWS完成端口的特点<br></strong>&nbsp;&nbsp; Win32重叠I/O(Overlapped I/O)机制允许发起一个操作，然后在操作完成之后接受到信息。对于那种需要很长时间才能完成的操作来说，重叠IO机制尤其有用，因为发起重叠操作的线程在重叠请求发出后就可以自由的做别的事情了。在WinNT和Win2000上，提供的真正的可扩展的I/O模型就是使用完成端口（Completion Port）的重叠I/O.完成端口---是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下，当然重叠I/O不一定非使用完成端口不可，还有设备内核对象、事件对象、告警I/0等。但是完成端口内部提供了线程池的管理，可以避免反复创建线程的开销，同时可以根据CPU的个数灵活的决定线程个数，而且可以让减少线程调度的次数从而提高性能其实类似于WSAAsyncSelect和select函数的机制更容易兼容Unix，但是难以实现我们想要的&#8220;扩展性&#8221;。而且windows的完成端口机制在操作系统内部已经作了优化，提供了更高的效率。所以，我们选择完成端口开始我们的服务器程序的开发。<br>1、发起操作不一定完成，系统会在完成的时候通知你，通过用户在完成端口上的等待，处理操作的结果。所以要有检查完成端口，取操作结果的线程。在完成端口上守候的线程系统有优化，除非在执行的线程阻塞，不会有新的线程被激活，以此来减少线程切换造成的性能代价。所以如果程序中没有太多的阻塞操作，没有必要启动太多的线程，CPU数量的两倍，一般这样来启动线程。<br>2、操作与相关数据的绑定方式：在提交数据的时候用户对数据打相应的标记，记录操作的类型，在用户处理操作结果的时候，通过检查自己打的标记和系统的操作结果进行相应的处理。 <br>3、操作返回的方式:一般操作完成后要通知程序进行后续处理。但写操作可以不通知用户，此时如果用户写操作不能马上完成，写操作的相关数据会被暂存到到非交换缓冲区中，在操作完成的时候，系统会自动释放缓冲区。此时发起完写操作，使用的内存就可以释放了。此时如果占用非交换缓冲太多会使系统停止响应。<br><strong><br>3、完成端口（Completion Ports ）相关数据结构和创建<br></strong>&nbsp;&nbsp;&nbsp; 其实可以把完成端口看成系统维护的一个队列，操作系统把重叠IO操作完成的事件通知放到该队列里，由于是暴露 &#8220;操作完成&#8221;的事件通知，所以命名为&#8220;完成端口&#8221;（COmpletion Ports）。一个socket被创建后，可以在任何时刻和一个完成端口联系起来。<br>完成端口相关最重要的是OVERLAPPED数据结构<br>typedef struct _OVERLAPPED { <br>&nbsp;&nbsp;&nbsp; ULONG_PTR Internal;//被系统内部赋值，用来表示系统状态 <br>&nbsp;&nbsp;&nbsp; ULONG_PTR InternalHigh;// 被系统内部赋值，传输的字节数 <br>&nbsp;&nbsp;&nbsp; union { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD Offset;//和OffsetHigh合成一个64位的整数，用来表示从文件头部的多少字节开始 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD OffsetHigh;//操作，如果不是对文件I/O来操作，则必须设定为0 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PVOID Pointer; <br>&nbsp;&nbsp;&nbsp; }; <br>&nbsp;&nbsp;&nbsp; HANDLE hEvent;//如果不使用，就务必设为0,否则请赋一个有效的Event句柄 <br>} OVERLAPPED, *LPOVERLAPPED; <br><br>下面是异步方式使用ReadFile的一个例子 <br>OVERLAPPED Overlapped; <br>Overlapped.Offset=345; <br>Overlapped.OffsetHigh=0; <br>Overlapped.hEvent=0; <br>//假定其他参数都已经被初始化 <br>ReadFile(hFile,buffer,sizeof(buffer),&amp;dwNumBytesRead,&amp;Overlapped); <br>这样就完成了异步方式读文件的操作，然后ReadFile函数返回，由操作系统做自己的事情，下面介绍几个与OVERLAPPED结构相关的函数 <br>等待重叠I/0操作完成的函数 <br>BOOL GetOverlappedResult (<br>HANDLE hFile,<br>LPOVERLAPPED lpOverlapped,//接受返回的重叠I/0结构<br>LPDWORD lpcbTransfer,//成功传输了多少字节数<br>BOOL fWait //TRUE只有当操作完成才返回，FALSE直接返回，如果操作没有完成，通过调//用GetLastError ( )函数会返回ERROR_IO_INCOMPLETE <br>);<br>宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成，该宏对OVERLAPPED结构的Internal成员进行了测试，查看是否等于STATUS_PENDING值。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一般来说，一个应用程序可以创建多个工作线程来处理完成端口上的通知事件。工作线程的数量依赖于程序的具体需要。但是在理想的情况下，应该对应一个CPU创建一个线程。因为在完成端口理想模型中，每个线程都可以从系统获得一个&#8220;原子&#8221;性的时间片，轮番运行并检查完成端口，线程的切换是额外的开销。在实际开发的时候，还要考虑这些线程是否牵涉到其他堵塞操作的情况。如果某线程进行堵塞操作，系统则将其挂起，让别的线程获得运行时间。因此，如果有这样的情况，可以多创建几个线程来尽量利用时间。<br>应用完成端口：<br>&nbsp;&nbsp;&nbsp; 创建完成端口：完成端口是一个内核对象，使用时他总是要和至少一个有效的设备句柄进行关联，完成端口是一个复杂的内核对象，创建它的函数是：<br>HANDLE CreateIoCompletionPort( <br>&nbsp;&nbsp;&nbsp; IN HANDLE FileHandle, <br>&nbsp;&nbsp;&nbsp; IN HANDLE ExistingCompletionPort, <br>&nbsp;&nbsp;&nbsp; IN ULONG_PTR CompletionKey, <br>&nbsp;&nbsp;&nbsp; IN DWORD NumberOfConcurrentThreads <br>&nbsp;&nbsp;&nbsp; ); <br><br>通常创建工作分两步：<br>第一步，创建一个新的完成端口内核对象，可以使用下面的函数：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads) <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads); <br>};<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>第二步，将刚创建的完成端口和一个有效的设备句柄关联起来，可以使用下面的函数：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey) <br>{ <br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0); <br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return h==hCompPort; <br>}; <br>说明 <br>1） CreateIoCompletionPort函数也可以一次性的既创建完成端口对象，又关联到一个有效的设备句柄 <br>2） CompletionKey是一个可以自己定义的参数，我们可以把一个结构的地址赋给它，然后在合适的时候取出来使用，最好要保证结构里面的内存不是分配在栈上，除非你有十分的把握内存会保留到你要使用的那一刻。<br>3） NumberOfConcurrentThreads通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0，这样系统会根据CPU的个数来自动确定。创建和关联的动作完成后，系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口，就会有多个对应的设备列表。如果设备句柄被关闭，则表中自动删除该纪录。<br><strong><br>4、完成端口线程的工作原理</strong><br>完成端口可以帮助我们管理线程池，但是线程池中的线程需要我们使用_beginthreadex来创建，凭什么通知完成端口管理我们的新线程呢？答案在函数GetQueuedCompletionStatus。该函数原型： <br>BOOL GetQueuedCompletionStatus( <br>&nbsp;&nbsp;&nbsp; IN HANDLE CompletionPort, <br>&nbsp;&nbsp;&nbsp; OUT LPDWORD lpNumberOfBytesTransferred, <br>&nbsp;&nbsp;&nbsp; OUT PULONG_PTR lpCompletionKey, <br>&nbsp;&nbsp;&nbsp; OUT LPOVERLAPPED *lpOverlapped, <br>&nbsp;&nbsp;&nbsp; IN DWORD dwMilliseconds <br>); <br>这个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候，完成队列中才有纪录。凡是调用这个函数的线程将被放入到完成端口的等待线程队列中，因此完成端口就可以在自己的线程池中帮助我们维护这个线程。完成端口的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录，该纪录拥有四个字段，前三项就对应GetQueuedCompletionStatus函数的2、3、4参数，最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。 <br>当I/0完成队列中出现了纪录，完成端口将会检查等待线程队列，该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单，只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中，同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。所以我们的线程要想成为完成端口管理的线程，就必须要调用GetQueuedCompletionStatus函数。出于性能的优化，实际上完成端口还维护了一个暂停线程列表，具体细节可以参考《Windows高级编程指南》，我们现在知道的知识，已经足够了。 完成端口线程间数据传递线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数，或者使用全局变量。但是完成端口还有自己的传递数据的方法，答案就在于CompletionKey和OVERLAPPED参数。<br>CompletionKey被保存在完成端口的设备表中，是和设备句柄一一对应的，我们可以将与设备句柄相关的数据保存到CompletionKey中，或者将CompletionKey表示为结构指针，这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做，因此不能在以后动态改变。<br>OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到，如果我们不是对文件设备做操作，该结构的成员变量就对我们几乎毫无作用。我们需要附加信息，可以创建自己的结构，然后将OVERLAPPED结构变量作为我们结构变量的第一个成员，然后传递第一个成员变量的地址给ReadFile函数。因为类型匹配，当然可以通过编译。当GetQueuedCompletionStatus函数返回时，我们可以获取到第一个成员变量的地址，然后一个简单的强制转换，我们就可以把它当作完整的自定义结构的指针使用，这样就可以传递很多附加的数据了。太好了！只有一点要注意，如果跨线程传递，请注意将数据分配到堆上，并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中，这样当GetQueuedCompletionStatus被返回时，我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。CompletionKey和OVERLAPPED参数，都可以通过GetQueuedCompletionStatus函数获得。<br>线程的安全退出<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 很多线程为了不止一次的执行异步数据处理，需要使用如下语句<br>while (true)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ......<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetQueuedCompletionStatus(...); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ......<br>}<br>那么如何退出呢，答案就在于上面曾提到的PostQueudCompletionStatus函数，我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址，里面包含一个状态变量，当状态变量为退出标志时，线程就执行清除动作然后退出。<br><strong><br>5、Windows完成端口的实例代码：<br></strong>DWORD WINAPI WorkerThread(LPVOID lpParam)<br>{ <br>ULONG_PTR *PerHandleKey;<br>OVERLAPPED *Overlap;<br>OVERLAPPEDPLUS *OverlapPlus,<br>*newolp;<br>DWORD dwBytesXfered;<br>while (1)<br>{<br>ret = GetQueuedCompletionStatus(<br>hIocp,<br>&amp;dwBytesXfered,<br>(PULONG_PTR)&amp;PerHandleKey,<br>&amp;Overlap,<br>INFINITE);<br>if (ret == 0)<br>{<br>// Operation failed<br>continue;<br>}<br>OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);<br>switch (OverlapPlus-&gt;OpCode)<br>{<br>case OP_ACCEPT:<br>// Client socket is contained in OverlapPlus.sclient<br>// Add client to completion port<br>CreateIoCompletionPort(<br>(HANDLE)OverlapPlus-&gt;sclient,<br>hIocp,<br>(ULONG_PTR)0,<br>0);<br>// Need a new OVERLAPPEDPLUS structure<br>// for the newly accepted socket. Perhaps<br>// keep a look aside list of free structures.<br>newolp = AllocateOverlappedPlus();<br>if (!newolp)<br>{<br>// Error<br>}<br>newolp-&gt;s = OverlapPlus-&gt;sclient;<br>newolp-&gt;OpCode = OP_READ;<br>// This function divpares the data to be sent<br>PrepareSendBuffer(&amp;newolp-&gt;wbuf);<br>ret = WSASend(<br>newolp-&gt;s,<br>&amp;newolp-&gt;wbuf,<br>1,<br>&amp;newolp-&gt;dwBytes,<br>0,<br>&amp;newolp.ol,<br>NULL);<br>if (ret == SOCKET_ERROR)<br>{<br>if (WSAGetLastError() != WSA_IO_PENDING)<br>{<br>// Error<br>}<br>}<br>// Put structure in look aside list for later use<br>FreeOverlappedPlus(OverlapPlus);<br>// Signal accept thread to issue another AcceptEx<br>SetEvent(hAcceptThread);<br>break;<br>case OP_READ:<br>// Process the data read <br>// Repost the read if necessary, reusing the same<br>// receive buffer as before<br>memset(&amp;OverlapPlus-&gt;ol, 0, sizeof(OVERLAPPED));<br>ret = WSARecv(<br>OverlapPlus-&gt;s,<br>&amp;OverlapPlus-&gt;wbuf,<br>1,<br>&amp;OverlapPlus-&gt;dwBytes,<br>&amp;OverlapPlus-&gt;dwFlags,<br>&amp;OverlapPlus-&gt;ol,<br>NULL);<br>if (ret == SOCKET_ERROR)<br>{<br>if (WSAGetLastError() != WSA_IO_PENDING)<br>{<br>// Error<br>}<br>}<br>break;<br>case OP_WRITE:<br>// Process the data sent, etc.<br>break;<br>} // switch<br>} // while<br>} // WorkerThread<br>　</p>
<p>查看以上代码，注意如果Overlapped操作立刻失败（比如，返回SOCKET_ERROR或其他非WSA_IO_PENDING的错误），则没有任何完成通知时间会被放到完成端口队列里。反之，则一定有相应的通知时间被放到完成端口队列。更完善的关于Winsock的完成端口机制，可以参考MSDN的Microsoft PlatFormSDK，那里有完成端口的例子。访问<a href="http://msdn.microsoft.com/library/techart/msdn_servrapp.htm"><u><font color=#0000ff>http://msdn.microsoft.com/library/techart/msdn_servrapp.htm</font></u></a>可以获得更多信息。</p>
<p><strong>Linux的EPoll模型<br></strong>Linux 2.6内核中提高网络I/O性能的新方法-epoll I/O多路复用技术在比较多的TCP网络服务器中有使用，即比较多的用到select函数。<br><br><strong>1、为什么select落后<br></strong>首先，在Linux内核中，select所用到的FD_SET是有限的，即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数，在我用的2.6.15-25-386内核中，该值是1024，搜索内核源代码得到：<br>include/linux/posix_types.h:#define __FD_SETSIZE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1024<br>也就是说，如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。其次，内核中实现select是用轮询方法，即每次检测都会遍历所有FD_SET中的句柄，显然，select函数执行时间与FD_SET中的句柄个数有一个比例关系，即select要检测的句柄数越多就会越费时。当然，在前文中我并没有提及poll方法，事实上用select的朋友一定也试过poll，我个人觉得select和poll大同小异，个人偏好于用select而已。</p>
<p><strong>2、内核中提高I/O性能的新方法epoll</strong><br>epoll是什么？按照man手册的说法：是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用：epoll_create(2)， epoll_ctl(2)， epoll_wait(2)。<br>当然，这不是2.6内核才有的，它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)</p>
<p>Linux2.6内核epoll介绍<br>先介绍2本书《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》，以2.4内核讲解Linux TCP/IP实现，相当不错.作为一个现实世界中的实现，很多时候你必须作很多权衡，这时候参考一个久经考验的系统更有实际意义。举个例子,linux内核中sk_buff结构为了追求速度和安全，牺牲了部分内存，所以在发送TCP包的时候，无论应用层数据多大,sk_buff最小也有272的字节.其实对于socket应用层程序来说，另外一本书《UNIX Network Programming Volume 1》意义更大一点.2003年的时候，这本书出了最新的第3版本，不过主要还是修订第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens给出了网络IO的基本模型。在这里最重要的莫过于select模型和Asynchronous I/O模型.从理论上说，AIO似乎是最高效的，你的IO操作可以立即返回，然后等待os告诉你IO操作完成。但是一直以来，如何实现就没有一个完美的方案。最著名的windows完成端口实现的AIO,实际上也是内部用线程池实现的罢了，最后的结果是IO有个线程池，你应用也需要一个线程池...... 很多文档其实已经指出了这带来的线程context-switch带来的代价。在linux 平台上，关于网络AIO一直是改动最多的地方，2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布，网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法，在使用了NPTL的linux上面其实和windows的完成端口基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘，对于流服务器在内存平稳性上有相当帮助)。<br>所以，剩下的select模型基本上就是我们在linux上面的唯一选择，其实，如果加上no-block socket的配置，可以完成一个"伪"AIO的实现，只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点，所以改进一直是2.4-2.5开发版本内核的任务，包括/dev/poll，realtime signal等等。最终，Davide Libenzi开发的epoll进入2.6内核成为正式的解决方案<br><strong><br>3、epoll的优点</strong><br>&lt;1&gt;支持一个进程打开大数目的socket描述符(FD)<br>select 最不能忍受的是一个进程所打开的FD是有一定限制的，由FD_SETSIZE设置，默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核，不过资料也同时指出这样会带来网络效率的下降，二是可以选择多进程的解决方案(传统的Apache方案)，不过虽然linux上面创建进程的代价比较小，但仍旧是不可忽视的，加上进程间数据同步远比不上线程间同步的高效，所以也不是一种完美的方案。不过 epoll则没有这个限制，它所支持的FD上限是最大可以打开文件的数目，这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右，具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。<br>&lt;2&gt;IO效率不随FD数目增加而线性下降<br>传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合，不过由于网络延时，任一时间只有部分的socket是"活跃"的，但是select/poll每次调用都会线性扫描全部的集合，导致效率呈现线性下降。但是epoll不存在这个问题，它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么，只有"活跃"的socket才会主动的去调用 callback函数，其他idle状态socket则不会，在这点上，epoll实现了一个"伪"AIO，因为这时候推动力在os内核。在一些 benchmark中，如果所有的socket基本上都是活跃的---比如一个高速LAN环境，epoll并不比select/poll有什么效率，相反，如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。<br>&lt;3&gt;使用mmap加速内核与用户空间的消息传递。<br>这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间，如何避免不必要的内存拷贝就很重要，在这点上，epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话，一定不会忘记手工 mmap这一步的。<br>&lt;4&gt;内核微调<br>这一点其实不算epoll的优点了，而是整个linux平台的优点。也许你可以怀疑linux平台，但是你无法回避linux平台赋予你微调内核的能力。比如，内核TCP/IP协议栈使用内存池管理sk_buff结构，那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX&gt;/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度)，也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。<br>4、epoll的工作模式<br>令人高兴的是，2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多，所以，大部分情况下，强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。<br>LT(level triggered)是缺省的工作方式，并且同时支持block和no-block socket.在这种做法中，内核告诉你一个文件描述符是否就绪了，然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作，内核还是会继续通知你的，所以，这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表．<br>ET (edge-triggered)是高速工作方式，只支持no-block socket。在这种模式下，当描述符从未就绪变为就绪时，内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪，并且不会再为那个文件描述符发送更多的就绪通知，直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如，你在发送，接收或者接收请求，或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误）。但是请注意，如果一直不对这个fd作IO操作(从而导致它再次变成未就绪)，内核不会发送更多的通知(only once),不过在TCP协议中，ET模式的加速效用仍需要更多的benchmark确认。<br>epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用，具体用法请参考<a href="http://www.xmailserver.org/linux-patches/nio-improve.html"><u><font color=#0000ff>http://www.xmailserver.org/linux-patches/nio-improve.html</font></u></a> ，在<a href="http://www.kegel.com/rn/"><u><font color=#0000ff>http://www.kegel.com/rn/</font></u></a>也有一个完整的例子，大家一看就知道如何使用了<br>Leader/follower模式线程pool实现，以及和epoll的配合。<br><br><strong>5、 epoll的使用方法</strong><br>&nbsp;&nbsp;&nbsp; 首先通过create_epoll(int maxfds)来创建一个epoll的句柄，其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄，之后的所有操作将通过这个句柄来进行操作。在用完之后，记得用close()来关闭这个创建出来的epoll句柄。 之后在你的网络主循环里面，每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口，看哪一个可以读，哪一个可以写了。基本的语法为： <br>nfds = epoll_wait(kdpfd, events, maxevents, -1); <br>其中kdpfd为用epoll_create创建之后的句柄，events是一个epoll_event*的指针，当epoll_wait这个函数操作成功之后，epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是epoll_wait的超时，为0的时候表示马上返回，为-1的时候表示一直等下去，直到有事件范围，为任意正整数的时候表示等这么长的时间，如果一直没有事件，则范围。一般如果网络主循环是单独的线程的话，可以用-1来等，这样可以保证一些效率，如果是和主逻辑在同一个线程的话，则可以用0来保证主循环的效率。</p>
<p>epoll_wait范围之后应该是一个循环，遍利所有的事件： <br>for(n = 0; n &lt; nfds; ++n) { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(events[n].data.fd == listener) { //如果是主socket的事件的话，则表示有新连接进入了，进行新连接的处理。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client = accept(listener, (struct sockaddr *) &amp;local, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;addrlen); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(client &lt; 0){ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("accept"); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setnonblocking(client); // 将新连接置于非阻塞模式 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ev.events = EPOLLIN | EPOLLET; // 并且将新连接也加入EPOLL的监听队列。 <br>注意，这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听，如果有写操作的话，这个时候epoll是不会返回事件的，如果要对写操作也监听的话，应该是EPOLLIN | EPOLLOUT | EPOLLET <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ev.data.fd = client; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &amp;ev) &lt; 0) { <br>// 设置好event之后，将这个新的event通过epoll_ctl加入到epoll的监听队列里面，这里用EPOLL_CTL_ADD来加一个新的epoll事件，通过EPOLL_CTL_DEL来减少一个epoll事件，通过EPOLL_CTL_MOD来改变一个事件的监听方式。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, "epoll set insertion error: fd=%d0, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else // 如果不是主socket的事件的话，则代表是一个用户socket的事件，则来处理这个用户socket的事情，比如说read(fd,xxx)之类的，或者一些其他的处理。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; do_use_fd(events[n].data.fd); <br>}</p>
<p>对，epoll的操作就这么简单，总共不过4个API：epoll_create, epoll_ctl, epoll_wait和close。 <br>如果您对epoll的效率还不太了解，请参考我之前关于网络游戏的网络编程等相关的文章。</p>
<p><br>以前公司的服务器都是使用HTTP连接，但是这样的话，在手机目前的网络情况下不但显得速度较慢，而且不稳定。因此大家一致同意用SOCKET来进行连接。虽然使用SOCKET之后，对于用户的费用可能会增加(由于是用了CMNET而非CMWAP)，但是，秉着用户体验至上的原则，相信大家还是能够接受的(希望那些玩家月末收到帐单不后能够保持克制...)。<br>这次的服务器设计中，最重要的一个突破，是使用了EPOLL模型，虽然对之也是一知半解，但是既然在各大PC网游中已经经过了如此严酷的考验，相信他不会让我们失望，使用后的结果，确实也是表现相当不错。在这里，我还是主要大致介绍一下这个模型的结构。<br>6、Linux下EPOll编程实例<br>EPOLL模型似乎只有一种格式，所以大家只要参考我下面的代码，就能够对EPOLL有所了解了，代码的解释都已经在注释中：</p>
<p>while (TRUE)<br>{<br>int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//等待EPOLL时间的发生，相当于监听，至于相关的端口，需要在初始化EPOLL的时候绑定。<br>if (nfds &lt;= 0)<br>continue;<br>m_bOnTimeChecking = FALSE;<br>G_CurTime = time(NULL);<br>for (int i=0; i<br>{<br>try<br>{<br>if (m_events[i].data.fd == m_listen_http_fd)//如果新监测到一个HTTP用户连接到绑定的HTTP端口，建立新的连接。由于我们新采用了SOCKET连接，所以基本没用。<br>{<br>OnAcceptHttpEpoll ();<br>}<br>else if (m_events[i].data.fd == m_listen_sock_fd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口，建立新的连接。<br>{<br>OnAcceptSockEpoll ();<br>}<br>else if (m_events[i].events &amp; EPOLLIN)//如果是已经连接的用户，并且收到数据，那么进行读入。<br>{<br>OnReadEpoll (i);<br>}</p>
<p>OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据。<br>}<br>catch (int)<br>{<br>PRINTF ("CATCH捕获错误\n");<br>continue;<br>}<br>}<br>m_bOnTimeChecking = TRUE;<br>OnTimer ();//进行一些定时的操作，主要就是删除一些短线用户等。<br>}<br>　其实EPOLL的精华，也就是上述的几段短短的代码，看来时代真的不同了，以前如何接受大量用户连接的问题，现在却被如此轻松的搞定，真是让人不得不感叹，对哪。</p>
<p><br><strong>总结<br></strong>Windows完成端口与Linux epoll技术方案是这2个平台上实现异步IO和设计开发一个大容量，具可扩展性的winsock程序指服务程序的很好的选择，本文对这2中技术的实现原理和实际的使用方法做了一个详细的介绍。</p>
<img src ="http://www.cppblog.com/qiujian5628/aggbug/42165.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-01-30 09:10 <a href="http://www.cppblog.com/qiujian5628/articles/42165.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux2.6内核epoll介绍</title><link>http://www.cppblog.com/qiujian5628/articles/42164.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Wed, 30 Jan 2008 01:09:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/42164.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/42164.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/42164.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/42164.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/42164.html</trackback:ping><description><![CDATA[<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>[作者]：滕昱,2005/3/30,0.1版本</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>[版权声明]：此文档遵循GNU自由文档许可证(GNU Free Documentation License).任何人可以自由复制,分发,修改,不过如果方便,请注明出处和作者:)</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>(1)导言：</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>首先，我强烈建议大家阅读Richard Stevens著作《TCP/IP Illustracted Volume 1,2,3》和《UNIX Network Programming Volume 1,2》。虽然他离开我们大家已经5年多了，但是他的书依然是进入网络编程的最直接的道路。其中的3卷的《TCP/IP Illustracted》卷1是必读－如果你不了解tcp协议各个选项的详细定义，你就失去了优化程序重要的一个手段。卷2,3可以选读一下。比如卷2讲解的是4.4BSD内核TCP/IP协议栈实现----这个版本的协议栈几乎影响了现在所有的主流os，但是因为年代久远，内容不一定那么vogue.在这里我多推荐一本《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》，以2.4内核讲解Linux TCP/IP实现，相当不错.作为一个现实世界中的实现，很多时候你必须作很多权衡，这时候参考一个久经考验的系统更有实际意义。举个例子,linux内核中sk_buff结构为了追求速度和安全，牺牲了部分内存，所以在发送TCP包的时候，无论应用层数据多大,sk_buff最小也有272的字节.</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>其实对于socket应用层程序来说，《UNIX Network Programming Volume 1》意义更大一点.2003年的时候，这本书出了最新的第3版本，不过主要还是修订第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens给出了网络IO的基本模型。在这里最重要的莫过于select模型和Asynchronous I/O模型.从理论上说，AIO似乎是最高效的，你的IO操作可以立即返回，然后等待os告诉你IO操作完成。但是一直以来，如何实现就没有一个完美的方案。最著名的windows完成端口实现的AIO,实际上也是内部用线程池实现的罢了，最后的结果是IO有个线程池，你应用也需要一个线程池......很多文档其实已经指出了这带来的线程context-switch带来的代价。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>在linux平台上，关于网络AIO一直是改动最多的地方，2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布，网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法，在使用了NPTL的linux上面其实和windows的完成端口基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘，对于流服务器在内存平稳性上有相当帮助)。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>所以，剩下的select模型基本上就是我们在linux上面的唯一选择，其实，如果加上no-block socket的配置，可以完成一个"伪"AIO的实现，只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点，所以改进一直是2.4-2.5开发版本内核的任务，包括/dev/poll，realtime signal等等。最终，Davide Libenzi开发的epoll进入2.6内核成为正式的解决方案</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>(2)epoll的优点</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>&lt;1&gt;支持一个进程打开大数目的socket描述符(FD)</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>select最不能忍受的是一个进程所打开的FD是有一定限制的，由FD_SETSIZE设置，默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了 。这时候你一是可以选择修改这个宏然后重新编译内核，不过资料也同时指出这样会带来网络效率的下降，二是可以选择多进程的解决方案(传统的Apache方案)，不过虽然linux上面创建进程的代价比较小，但仍旧是不可忽视的，加上进程间数据同步远比不上线程间同步的高效，所以也不是一种完美的方案。不过epoll则没有这个限制，它所支持的FD上限是最大可以打开文件的数目，这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右，具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>&lt;2&gt;IO效率不随FD数目增加而线性下降</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合，不过由于网络延时，任一时间只有部分的socket是"活跃"的，但是select/poll每次调用都会线性扫描全部的集合，导致效率呈现线性下降。但是epoll不存在这个问题，它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么，只有"活跃"的socket才会主动的去调用callback函数，其他idle状态socket则不会，在这点上，epoll实现了一个"伪"AIO，因为这时候推动力在os内核。在一些benchmark中，如果所有的socket基本上都是活跃的---比如一个高速LAN环境，epoll并不比select/poll有什么效率，相反，如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>&lt;3&gt;使用mmap加速内核与用户空间的消息传递。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间，如何避免不必要的内存拷贝就很重要，在这点上，epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话，一定不会忘记手工mmap这一步的。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>&lt;4&gt;内核微调</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>这一点其实不算epoll的优点了，而是整个linux平台的优点。也许你可以怀疑linux平台，但是你无法回避linux平台赋予你微调内核的能力。比如，内核TCP/IP协议栈使用内存池管理sk_buff结构，那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小---通过echo XXXX&gt;/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度)，也可以根据你平台内存大小动态调整。更甚至在一个</font><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>数据包</font><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>(3)epoll的使用</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>令人高兴的是，2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多，所以，大部分情况下，强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>LT(level triggered)是缺省的工作方式，并且同时支持block和no-block socket.在这种做法中，内核告诉你一个文件描述符是否就绪了，然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作，内核还是会继续通知你的，所以，这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表．</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>ET(edge-triggered)是高速工作方式，只支持no-block socket。在这种模式下，当描述符从未就绪变为就绪时，内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪，并且不会再为那个文件描述符发送更多的就绪通知，直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如，你在发送，接收或者接收请求，或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误）。但是请注意，如果一直不对这个fd作IO操作(从而导致它再次变成未就绪)，内核不会发送更多的通知(only once),不过在TCP协议中，ET模式的加速效用仍需要更多的benchmark确认。</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用，具体用法请参考http://www.xmailserver.org/linux-patches/nio-improve.html ，<br>在http://www.kegel.com/rn/也有一个完整的例子，大家一看就知道如何使用了</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>(4)Leader/follower模式线程pool实现，以及和epoll的配合</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>.....未完成，主要是要避免过多的epoll_ctl调用,以及尝试使用EPOLLONESHOT加速......</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>(5)benchmark</font></p>
<p><font style="FONT-FAMILY: verdana,arial,helvetica,sans-serif" size=2>.......未完成</font></p>
<img src ="http://www.cppblog.com/qiujian5628/aggbug/42164.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-01-30 09:09 <a href="http://www.cppblog.com/qiujian5628/articles/42164.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于内存对齐</title><link>http://www.cppblog.com/qiujian5628/articles/42086.html</link><dc:creator>浪迹天涯</dc:creator><author>浪迹天涯</author><pubDate>Tue, 29 Jan 2008 01:25:00 GMT</pubDate><guid>http://www.cppblog.com/qiujian5628/articles/42086.html</guid><wfw:comment>http://www.cppblog.com/qiujian5628/comments/42086.html</wfw:comment><comments>http://www.cppblog.com/qiujian5628/articles/42086.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qiujian5628/comments/commentRss/42086.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qiujian5628/services/trackbacks/42086.html</trackback:ping><description><![CDATA[<p>#pragma pack（n）<br>第一、如果n大于等于该变量所占用的字节数，那么偏移量必须满足默认的对齐方式<br>第二、如果n小于该变量的类型所占用的字节数，那么偏移量为n的倍数，不用满足默认的对齐方式。结构的总大小也有个约束条件，分下面两种情况：如果n大于所有成员变量类型所占用的字节数，那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数；<br>否则必须为n的倍数。</p>
<p><br>当在C中定义了一个结构类型时，它的大小是否等于各字段(field)大小之和？编译器将如何在内存中放置这些字段？ANSI C对结构体的内存布局有什么要求？而我们的程序又能否依赖这种布局？这些问题或许对不少朋友来说还有点模糊，那么本文就试着探究它们背后的秘密。 </p>
<p>&nbsp;&nbsp;&nbsp; 首先，至少有一点可以肯定，那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的，并且第一个字段的首地址等于整个结构体实例的首地址。比如有这样一个结构体：<br>&nbsp; <br>&nbsp; struct vector{int x,y,z;} s;<br>&nbsp; int *p,*q,*r;<br>&nbsp; struct vector *ps;<br>&nbsp; <br>&nbsp; p = &amp;s.x;<br>&nbsp; q = &amp;s.y;<br>&nbsp; r = &amp;s.z;<br>&nbsp; ps = &amp;s;</p>
<p>&nbsp; assert(p &lt; q);<br>&nbsp; assert(p &lt; r);<br>&nbsp; assert(q &lt; r);<br>&nbsp; assert((int*)ps == p);<br>&nbsp; // 上述断言一定不会失败</p>
<p>&nbsp;&nbsp;&nbsp; 这时，有朋友可能会问:"标准是否规定相邻字段在内存中也相邻?"。 唔，对不起，ANSI C没有做出保证，你的程序在任何时候都不应该依赖这个假设。那这是否意味着我们永远无法勾勒出一幅更清晰更精确的结构体内存布局图？哦，当然不是。不过先让我们从这个问题中暂时抽身，关注一下另一个重要问题————内存对齐。</p>
<p>&nbsp;&nbsp;&nbsp; 许多实际的计算机系统对基本类型数据在内存中存放的位置有限制，它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数，这就是所谓的内存对齐，而这个k则被称为该数据类型的对齐模数(alignment modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数，我们就称类型S的对齐要求比T强(严格)，而称T比S弱(宽松)。这种强制的要求一来简化了处理器与内存之间传输系统的设计，二来可以提升读取数据的速度。比如这么一种处理器，它每次读写内存的时候都从某个8倍数的地址开始，一次读出或写入8个字节的数据，假如软件能保证double类型的数据都从8倍数地址开始，那么读或写一个double类型数据就只需要一次内存操作。否则，我们就可能需要两次内存操作才能完成这个动作，因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。某些处理器在数据不满足对齐要求的情况下可能会出错，但是Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。不过Intel奉劝大家，如果想提升性能，那么所有的程序数据都应该尽可能地对齐。Win32平台下的微软C编译器(cl.exe for 80x86)在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小，即sizeof(T)。比如对于double类型(8字节)，就要求该类型数据的地址总是8的倍数，而char类型数据(1字节)则可以从任何一个地址开始。Linux下的GCC奉行的是另外一套规则(在资料中查得，并未验证，如错误请指正):任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2，而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。</p>
<p>&nbsp;&nbsp;&nbsp; 现在回到我们关心的struct上来。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。嗯？填充区？对，这就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。那么结构体本身有什么对齐要求吗？有的，ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松，可以更严格(但此非强制要求，VC7.1就仅仅是让它们一样严格)。我们来看一个例子(以下所有试验的环境是Intel Celeron 2.4G + WIN2000 PRO + vc7.1，内存对齐编译选项是"默认"，即不指定/Zp与/pack选项):</p>
<p>&nbsp; typedef struct ms1<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; char a;<br>&nbsp;&nbsp;&nbsp;&nbsp; int b;<br>&nbsp; } MS1;</p>
<p>&nbsp;&nbsp;&nbsp; 假设MS1按如下方式内存布局(本文所有示意图中的内存地址从左至右递增):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _____________________________<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; a&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +---------------------------+<br>&nbsp;Bytes:&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4</p>
<p>&nbsp;&nbsp;&nbsp; 因为MS1中有最强对齐要求的是b字段(int)，所以根据编译器的对齐规则以及ANSI C标准，MS1对象的首地址一定是4(int类型的对齐模数)的倍数。那么上述内存布局中的b字段能满足int类型的对齐要求吗？嗯，当然不能。如果你是编译器，你会如何巧妙安排来满足CPU的癖好呢？呵呵，经过1毫秒的艰苦思考，你一定得出了如下的方案：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _______________________________________<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; a&nbsp;&nbsp;|\\padding\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +-------------------------------------+<br>&nbsp;Bytes:&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4</p>
<p>&nbsp;&nbsp;&nbsp; 这个方案在a与b之间多分配了3个填充(padding)字节，这样当整个struct对象首地址满足4字节的对齐要求时，b字段也一定能满足int型的4字节对齐规定。那么sizeof(MS1)显然就应该是8，而b字段相对于结构体首地址的偏移就是4。非常好理解，对吗？现在我们把MS1中的字段交换一下顺序:</p>
<p>&nbsp; typedef struct ms2<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp;&nbsp; char b;<br>&nbsp; } MS2;</p>
<p>&nbsp;&nbsp;&nbsp; 或许你认为MS2比MS1的情况要简单，它的布局应该就是</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _______________________<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; a&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; b&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +---------------------+<br>&nbsp;Bytes:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 </p>
<p>&nbsp;&nbsp;&nbsp; 因为MS2对象同样要满足4字节对齐规定，而此时a的地址与结构体的首地址相等，所以它一定也是4字节对齐。嗯，分析得有道理，可是却不全面。让我们来考虑一下定义一个MS2类型的数组会出现什么问题。C标准保证，任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说，数组各元素之间不会有空隙。按照上面的方案，一个MS2数组array的布局就是:</p>
<p>|&lt;-&nbsp;&nbsp;&nbsp; array[1]&nbsp;&nbsp;&nbsp;&nbsp; -&gt;|&lt;-&nbsp;&nbsp;&nbsp; array[2]&nbsp;&nbsp;&nbsp;&nbsp; -&gt;|&lt;- array[3] .....</p>
<p>__________________________________________________________<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>|&nbsp;&nbsp;&nbsp;&nbsp; a&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; b&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; b&nbsp; |.............<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>+----------------------------------------------------------<br>Bytes:&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1</p>
<p><br>&nbsp;&nbsp;&nbsp; 当数组首地址是4字节对齐时，array[1].a也是4字节对齐，可是array[2].a呢？array[3].a ....呢？可见这种方案在定义结构体数组时无法让数组中所有元素的字段都满足对齐规定，必须修改成如下形式:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ___________________________________<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\\\|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; a&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; b&nbsp;&nbsp;|\\padding\\|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\\\|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +---------------------------------+<br>&nbsp;Bytes:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3</p>
<p>&nbsp;&nbsp;&nbsp; 现在无论是定义一个单独的MS2变量还是MS2数组，均能保证所有元素的所有字段都满足对齐规定。那么sizeof(MS2)仍然是8，而a的偏移为0，b的偏移是4。</p>
<p>&nbsp;&nbsp;&nbsp; 好的，现在你已经掌握了结构体内存布局的基本准则，尝试分析一个稍微复杂点的类型吧。</p>
<p>&nbsp; typedef struct ms3<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; char a;<br>&nbsp;&nbsp;&nbsp;&nbsp; short b;<br>&nbsp;&nbsp;&nbsp;&nbsp; double c;<br>&nbsp; } MS3;</p>
<p>&nbsp;&nbsp;&nbsp; 我想你一定能得出如下正确的布局图:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; padding&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _____v_________________________________<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; |\|&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | a |\|&nbsp; b&nbsp; |\padding\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; |\|&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +-------------------------------------+<br>Bytes:&nbsp; 1&nbsp; 1&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; sizeof(short)等于2，b字段应从偶数地址开始，所以a的后面填充一个字节，而sizeof(double)等于8，c字段要从8倍数地址开始，前面的a、b字段加上填充字节已经有4 bytes，所以b后面再填充4个字节就可以保证c字段的对齐要求了。sizeof(MS3)等于16，b的偏移是2，c的偏移是8。接着看看结构体中字段还是结构类型的情况:</p>
<p>&nbsp; typedef struct ms4<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; char a;<br>&nbsp;&nbsp;&nbsp;&nbsp; MS3 b;<br>&nbsp; } MS4;</p>
<p>&nbsp;&nbsp;&nbsp; MS3中内存要求最严格的字段是c，那么MS3类型数据的对齐模数就与double的一致(为8)，a字段后面应填充7个字节，因此MS4的布局应该是:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _______________________________________<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; a&nbsp;&nbsp; |\\padding\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |\\\\\\\\\\\|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +-------------------------------------+<br>&nbsp;Bytes:&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 16</p>
<p>&nbsp;&nbsp;&nbsp; 显然，sizeof(MS4)等于24，b的偏移等于8。</p>
<p>&nbsp;&nbsp;&nbsp; 在实际开发中，我们可以通过指定/Zp编译选项来更改编译器的对齐规则。比如指定/Zpn(VC7.1中n可以是1、2、4、8、16)就是告诉编译器最大对齐模数是n。在这种情况下，所有小于等于n字节的基本数据类型的对齐规则与默认的一样，但是大于n个字节的数据类型的对齐模数被限制为n。事实上，VC7.1的默认对齐选项就相当于/Zp8。仔细看看MSDN对这个选项的描述，会发现它郑重告诫了程序员不要在MIPS和Alpha平台上用/Zp1和/Zp2选项，也不要在16位平台上指定/Zp4和/Zp8(想想为什么？)。改变编译器的对齐选项，对照程序运行结果重新分析上面4种结构体的内存布局将是一个很好的复习。</p>
<p>&nbsp;&nbsp;&nbsp; 到了这里，我们可以回答本文提出的最后一个问题了。结构体的内存布局依赖于CPU、操作系统、编译器及编译时的对齐选项，而你的程序可能需要运行在多种平台上，你的源代码可能要被不同的人用不同的编译器编译(试想你为别人提供一个开放源码的库)，那么除非绝对必需，否则你的程序永远也不要依赖这些诡异的内存布局。顺便说一下，如果一个程序中的两个模块是用不同的对齐选项分别编译的，那么它很可能会产生一些非常微妙的错误。如果你的程序确实有很难理解的行为，不防仔细检查一下各个模块的编译选项。</p>
<p>&nbsp;&nbsp;&nbsp; 思考题:请分析下面几种结构体在你的平台上的内存布局，并试着寻找一种合理安排字段声明顺序的方法以尽量节省内存空间。</p>
<p>&nbsp;&nbsp;&nbsp; A. struct P1 { int a; char b; int c; char d; };<br>&nbsp;&nbsp;&nbsp; B. struct P2 { int a; char b; char c; int d; };<br>&nbsp;&nbsp;&nbsp; C. struct P3 { short a[3]; char b[3]; };<br>&nbsp;&nbsp;&nbsp; D. struct P4 { short a[3]; char *b[3]; };<br>&nbsp;&nbsp;&nbsp; E. struct P5 { struct P2 *a; char b; struct P1 a[2];&nbsp; };</p>
<p>参考资料:</p>
<p>&nbsp;&nbsp;&nbsp; 【1】《深入理解计算机系统(修订版)》，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (著)Randal E.Bryant; David O'Hallaron，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (译)龚奕利 雷迎春，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 中国电力出版社，2004<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 【2】《C: A Reference Manual》(影印版)，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (著)Samuel P.Harbison; Guy L.Steele，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 人民邮电出版社，2003</p>
<p><br>&nbsp;</p>
<img src ="http://www.cppblog.com/qiujian5628/aggbug/42086.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qiujian5628/" target="_blank">浪迹天涯</a> 2008-01-29 09:25 <a href="http://www.cppblog.com/qiujian5628/articles/42086.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>