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>