WCF全称Windows Communication Foundation, 是微软爸爸开发出的用于构建面向服务的应用程序的统一编程模型。用于替代和统一.net remoting和web service。
WCF大致是以web service那个基于xml的soap为基础,又除http/https外同时支持tcp(net.tcp)、udp(net.udp)、msmq、named pipe这些通讯方式。也提供了json格式对象序列化方式(好像默认仅限web?)。当然实际上还支持扩展(比较麻烦),比如说把序列化协议改成protobuff。
Deep♂Dark♂Fantastic
举个栗子,这是http通讯方式下。
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope"> <s:Header> <a:Action s:mustUnderstand="1">http://tempuri.org/IDateService/FetchDateTime</a:Action> <a:MessageID>urn:uuid:d8339919-5fd5-4579-b99d-10eb635c3749</a:MessageID> <a:ReplyTo> <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address> </a:ReplyTo> </s:Header> <s:Body> <FetchDateTime xmlns="http://tempuri.org/" /> </s:Body> </s:Envelope>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"> <s:Header> <a:Action s:mustUnderstand="1">http://tempuri.org/IDateService/FetchDateTimeResponse</a:Action> <a:RelatesTo>urn:uuid:1401480b-4c99-4e46-a391-fe8cdd0bddad</a:RelatesTo> </s:Header> <s:Body> <FetchDateTimeResponse xmlns="http://tempuri.org/"> <FetchDateTimeResult>2018-04-16T13:17:21.2341923+08:00</FetchDateTimeResult> </FetchDateTimeResponse> </s:Body> </s:Envelope>
M<+ ? 2 \Device\NPF_{88D4FAB5-D621-475E-8706-637A09F5DB15} port 8066 + 64-bit Windows 7 Service Pack 1, build 7601 ? d 餴 #NTB B BZ8<? 燿 E 4?譆 € ?#m?V8鷡倻幔? € 邀 ? d d 餴 %NTB B 燿 BZ8<? E 4[@ }敩V8?#m傶~"靧覝幔葊 壛 ? d X 餴 CNT6 6 BZ8<? 燿 E (?谸 € ?#m?V8鷡倻幔?靧覲@)扬 X $ 餴 ?NT BZ8<? 燿 E ?貮 € ?#m?V8鷡倻幔?靧覲@)医 POST /DateService/ HTTP/1.1 Content-Type: application/soap+xml; charset=utf-8 Host: 172.16.86.56:8066 Content-Length: 526 Expect: 100-continue Accept-Encoding: gzip, deflate Connection: Keep-Alive $ p 餴 ?NTO O 燿 BZ8<? E A\@ }啲V8?#m傶~"靧訙幛朠 矑 HTTP/1.1 100 Continue p d 餴 ?NTD D BZ8<? 燿 E 6?贎 € ?#m?V8鷡倻幛?靧霵@"育 <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">http://tempuri.org/IDateService/FetchDateTime</a:Action><a:MessageID>urn:uuid:1401480b-4c99-4e46-a391-fe8cdd0bddad</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand="1">http://172.16.86.56:8066/DateService/</a:To></s:Header><s:Body><FetchDateTime xmlns="http://tempuri.org/"/></s:Body></s:Envelope>d ? 餴 ?NT? ? 燿 BZ8<? E ?]@ }!?V8?#m傶~"靧鞙幡 ? HTTP/1.1 200 OK Content-Length: 478 Content-Type: application/soap+xml; charset=utf-8 Server: Microsoft-HTTPAPI/2.0 Date: Mon, 16 Apr 2018 05:17:21 GMT <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">http://tempuri.org/IDateService/FetchDateTimeResponse</a:Action><a:RelatesTo>urn:uuid:1401480b-4c99-4e46-a391-fe8cdd0bddad</a:RelatesTo></s:Header><s:Body><FetchDateTimeResponse xmlns="http://tempuri.org/"><FetchDateTimeResult>2018-04-16T13:17:21.2341923+08:00</FetchDateTimeResult></FetchDateTimeResponse></s:Body></s:Envelope> ? X 餴 ?QT6 6 BZ8<? 燿 E (?跕 € ?#m?V8鷡倻幡?靫iP?冄? X
可以看到这是非常标准的soap的方式。而tcp模式下,微软爸爸则对这些soap这套繁复的东西进行了压缩。所以这个东西就被命名成net.tcp了,也只有.net客户端可以调用,微软爸爸把序列化反序列化方法都隐藏了。大概在 System.ServiceModel.Channels.BinaryMessageEncoderFactory 、 System.ServiceModel.Channels.BinaryFormatBuilder 、 System.ServiceModel.Channels.BinaryFormatParser 中。具体就长这样。
M<+ ? 2 \Device\NPF_{435FEB21-F35A-400C-BFFF-281D55F2EE9B} ' tcp dst port 8818 or tcp src port 8818 + 64-bit Windows 7 Service Pack 1, build 7601 ? d 汣 g虲B B ?X蠵橺〞 E 43珸 € ?R:?V8织"r湜 € ? ? d ` 汣 €蜟> > 蠵橺〞 ?X E 0A瓳 ~簡?V8?R:"r织囲?湝p 雝 ? ` X 汣 挛C6 6 ?X蠵橺〞 E (3瑻 € ?R:?V8织"r湝囲?P ? X ? 汣 螩l l ?X蠵橺〞 E ^3瓳 € ?R:?V8织"r湝囲?P ? ,net.tcp://172.16.86.56:8818/Tcp/ShareManager? \ 汣 轮C< < 蠵橺〞 ?X E )A疈 ~簩?V8?R:"r织囲?溡P2 \ \ 汣 e淓; ; ?X蠵橺〞 E -3聾 € ?R:?V8织"r溡囲?P? ??*http://tempuri.org/IShareManager/GetShares,net.tcp://172.16.86.56:8818/Tcp/ShareManager GetShareshttp://tempuri.org/ customerNo pageIndexpageCaptionVsaVD 偒D?Y?N㎡秊5??D,D*?D 偒VB B ?1c2c67c137d249fc9c84293a4aa00026B傿 ? \ \ 汣 鈭H< < 蠵橺〞 ?X E (A菮 ~簍?V8?R:"r织囲?澴P= \ 汣 K芅? ? 蠵橺〞 ?X E 蹵這 ~穿?V8?R:"r织囲?澴P ? ??2http://tempuri.org/IShareManager/GetSharesResponseGetSharesResponsehttp://tempuri.org/GetSharesResult3http://schemas.datacontract.org/2004/07/Em.Entities)http://www.w3.org/2001/XMLSchema-instanceCodeMessagenil ErrorCode ErrorMessages9http://schemas.microsoft.com/2003/10/Serialization/ArraysMessage OldMessageResultShare CreateTime IsEnabled UpdateTime CustomerNoAvailableVolAverageCost BankCardNoBankCodeBusinFrozenVol ChargeTypeDepositAccountNoEmFrozenVolEmRemark FrozenVolFundCodeIdOnwayVol OpenProfitTotalVolUnPaid UnPaidDateUnitCostUnitValSucceedpreValueVsaVD 偒D?Y?N㎡秊5??D 偒VB Bb iE ?E?E?cEEEEE?#O蒃炓E!嘐#?#O蒃炓E%?1c2c67c137d249fc9c84293a4aa00026E'? ? E)? E+?6214850210872799E-?007E/? d E1E3?2000355387E5? E7?E9? E;?519588E=嵄iE?? EA? EC? ? EE? EG? EI? EK? EE???椧E!嘐#???椧E%?1c2c67c137d249fc9c84293a4aa00026E'? (# E)? E+?6214850210872799E-?007E/? ? E1E3?2000355387E5? E7?E9? E;?470028E=崑iE?? EA? EC? ' EE? EG? EI? EK? EE梹k椧E!嘐#梹k椧E%?1c2c67c137d249fc9c84293a4aa00026E'? ? ? 汣 てN? ? 蠵橺〞 ?X E 郂 ~份?V8?R:"r织囲防澴P[1 E)? E+?6214850210872799E-?007E/? E1E3?2000355387E5? E7?E9? E;?540002E=崐iE?? EA? EC? ? EE? EG? EI? EK? EE?寲?椧E!嘐#?寲?椧E%?1c2c67c137d249fc9c84293a4aa00026E'? ? E)? E+?6214850210872799E-?007E/? p E1E3?2000355387E5? E7?E9? E;?660012E=崏iE?? EA? EC? ' EE? EG? EI? EK? EM嘐O? ? X 汣 势N6 6 ?X蠵橺〞 E (3酅 € ?R:?V8织"r澴囲?P ? X \ 汣 x( C< < 蠵橺〞 ?X E (}駺 ~~J?V8?R:"r织囲?澴P .? \
wireshark截出来很多乱码,只能将就着看了,实际上压缩思想大致是把soap消息的header部分大致改成key-value形式,而body部分则是先给出scheme,再给出数据,跟json相比减少了数组中对key的冗余。整体与json相比肯定是强了一点,然而与thrift和protobuf比压缩率还是不够看。
WCF还提供基于MSMQ的队列工作模式、(分布式)事务支持、P2P网络模型(???)、RSS(wcf-syndication,鬼知道为什么起这名字)等奇奇怪怪一大堆功能(微软全家桶,真香)。(这些我都没用过
Deep♂Dark♂Fantastic
因为每个接口都要求详细的接口调用日志,这种用postsharp的反射可以轻松实现(。然而公司不允许使用postsharp(迂腐),不过也有基于IMessage的实现方式。因为windows整个系统是基于消息的,WCF调用起来实际上内部是用反射实现的,这样就会有MethodCallMessage。这样大致上实现ContextAttribute特性,然后实现IContextProperty,再实现IMessageSink,就可以为所欲为了(baidu一下就有了。
这里讲另一种方法,假如又懒得实现aop的话,那就要在每个函数末尾添加log方法,然而每个函数又都要拼接一大堆参数又非常累。虽然我们可以通过调试信息堆栈帧拿到当前执行的函数信息,然而我却拿不到参数值,这就非常尴尬。(虽然我觉得可以用WinAPI的ReadProcessMemory胡来一下,但这个还是得冷静一下
MethodBase mb = (new StackFrame(1, true)).GetMethod(); var pts = mb.GetParameters(); foreach (var pt in pts) { dict.Add(pt.Name, "");//只能赋个空值 }
然而我发现微软贴心的在WCF的OperationContext中包含了RequestMessage,原始的soap消息。这样我们就可以这么做。
public static Dictionary<string, string> GetWcfRequestInfo() { //<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope"> // <s:Header> // <a:Action s:mustUnderstand="1">http://tempuri.org/IRealTAPay/RealTAPayResultQry</a:Action> // <a:MessageID>urn:uuid:33fed715-f529-4349-b556-38b569307a39</a:MessageID> // <a:ReplyTo> // <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address> // </a:ReplyTo> // <a:To s:mustUnderstand="1">net.tcp://localhost:8860/RealTAPay</a:To> // </s:Header> // <s:Body> // <RealTAPayResultQry xmlns="http://tempuri.org/"> // <customerno i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> // </customerno> // <appserialno i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> // </appserialno> // <payno i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> // </payno> // </RealTAPayResultQry> // </s:Body> //</s:Envelope> Dictionary<string, string> ret = new Dictionary<string, string>(); //Stopwatch sw = new Stopwatch(); //sw.Start(); try { try { var message = OperationContext.Current.RequestContext.RequestMessage.ToString(); message = message.Substring(message.IndexOf("<s:Body>") + 8); message = message.Substring(0, message.IndexOf("</s:Body>")); //var doc = new XmlDocument(); //doc.LoadXml(message); //var body = doc.GetElementsByTagName("s:Body").Item(0).InnerXml; //XDocument xdoc = XDocument.Parse(message); //dynamic dyobj = XmlDeserializer.Deserialize(body); dynamic dyobj = XmlDeserializer.Deserialize(message); var json = JsonHelper.ConvertObjToJson(dyobj); ret["request"] = json; } catch { var mb = (new StackFrame(1, true)).GetMethod(); var pts = mb.GetParameters(); foreach (var pt in pts) { ret.Add(pt.Name, ""); } } } catch { } //sw.Stop(); //ret["sw"] = sw.ElapsedTicks.ToString(); return ret; }
public class XmlDeserializer { /// <summary> /// 反序列化xml到object /// https://www.codeproject.com/Tips/227139/Converting-XML-to-an-Dynamic-Object-using-ExpandoO /// </summary> /// <param name="xml"></param> /// <param name="node"></param> /// <returns></returns> public static dynamic Deserialize(string xml, XElement node = null) { if (String.IsNullOrWhiteSpace(xml) && node == null) return null; // If a file is not empty then load the xml and overwrite node with the // root element of the loaded document node = !String.IsNullOrWhiteSpace(xml) ? XDocument.Parse(xml).Root : node; IDictionary<String, dynamic> result = new ExpandoObject(); // implement fix as suggested by [ndinges] PluralizationService pluralizationService = PluralizationService.CreateService(CultureInfo.CreateSpecificCulture("en-us")); // use parallel as we dont really care of the order of our properties node.Elements().AsParallel().ForAll(gn => { // Determine if node is a collection container var isCollection = gn.HasElements && ( // if multiple child elements and all the node names are the same gn.Elements().Count() > 1 && gn.Elements().All( e => e.Name.LocalName.ToLower() == gn.Elements().First().Name.LocalName) || // if there's only one child element then determine using the PluralizationService if // the pluralization of the child elements name matches the parent node. gn.Name.LocalName.ToLower() == pluralizationService.Pluralize( gn.Elements().First().Name.LocalName).ToLower() ); // If the current node is a container node then we want to skip adding // the container node itself, but instead we load the children elements // of the current node. If the current node has child elements then load // those child elements recursively var items = isCollection ? gn.Elements().ToList() : new List<XElement>() { gn }; var values = new List<dynamic>(); // use parallel as we dont really care of the order of our properties // and it will help processing larger XMLs items.AsParallel().ForAll(i => values.Add((i.HasElements) ? Deserialize(null, i) : i.Value.Trim())); // Add the object name + value or value collection to the dictionary result[gn.Name.LocalName] = isCollection ? values : values.FirstOrDefault(); }); return result; } }
在我本机上性能测试了一下,用字符串的方法取出body部分输出大概只需要1ms,取出body部分后把body转为json大概需要2ms,然而用System.Xml.Linq直接解析xml然后取body部门再转json需要4ms。可以说xml解析性能相当惨烈(实际上System.Xml.Linq应该背锅,这个解析比System.Xml慢
这里还用到了一个万能xml反序列化到object的方法,从codeproject上抄来的。(然而json却可以直接、简单的反序列到object。
Deep♂Dark♂Fantastic
关于WCF的性能,微软爸爸其实对WCF已经规划了很多种情况,见会话、实例与并发。但实际上我们大多数情况下只用到WCF简单的rpc功能,而且在分布式系统下,我们不能保证客户端到服务端的会话是固定,所以会话模式大多数情况下是不适用的,我们也可以参照restful的相关理念,在参数中加入token以实现类似效果。
这样的话,我们可以直接设定我们实例设定为单服务实例多线程并发模式。当然PreCall模式也是可以接受的,只不过每次会创建新的服务实例,应该有另外的开销(PreCall模式下设置ConcurrencyMode实际上也没有意义)。
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple, InstanceContextMode=InstanceContextMode.Single)] public class RpcService : IRpc { //... }
其中maxConcurrentCalls配置值在实际运行中会乘以CPU核数才是实际值。比如说默认值16在一台16核的服务器上的实际限制是同时最多有 16 * 16 = 256次调用。看起来是不是很少,但这里就是并发与并行的问题了。
<configuration> <appSettings> </appSettings> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior> <serviceThrottling maxConcurrentCalls="16" maxConcurrentSessions="0" maxConcurrentInstances="0" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>