1. 乱谈SOA


        SOA的核心思想是整合,把旧平台的应用通过重构、提炼,整合成服务,通过众所周知的协议暴露服务接口,进而实现更大范围的互联互通。服务成为企业的核心价值,借助于安全的访问机制,消费者可以通过特定的方式使用生产者的企业资源,从而任何身处互联网的用户都在消费服务的同时,也可能为他人提供力所能及的服务,服务的集合成为云,云是普世价值。在可预见的将来,孤岛式的应用软件将不复存在,取而代之是一朵朵特定领域的信息云。从软件设计的角度看,云计算的基础是从系统到部件各级别均规定了标准的整合模式,SOA因此应运而生。在C语言的时代,软件被称为模块及其集合,模块之间的整合机制不外两种:源码级的函数与二级制级的动态库,二者都需要头文件加以规约;到了C++的时代,类的概念被引入,但本质上软件之间的整合机制并无变化,具有革新意义的是人们开始意识到软件内部需要更考究更成熟的创建手法,即模式与重构,分别从设计到实现把软件开发从个体化的艺术创作带向团队级的批量制造新纪元。软件越来越大,以至于PC时代不得不过早的离我们渐行渐远。Internet的诞生,对软件生产提出了新课题。原本是各大软件厂商自发考虑的平台化软件互联方法,逐渐演化为超越软件生产线品牌的不得不共同缔造的全贯通互联计算新思路。COM+、EJB、CORBA……打着不同标签的分布式软件构建标准,在彼此的融合中催生了SOA。经过十多年的蜕变,SOA从最初的抽象概念到今天得以落地实施,成为云计算的软件标准架构。微软的SOA也即WCF,同样关注整合与互联,其自身C++~COM~DCOM~COM+~.NET Remoting+Enterprise Service+ASMX+WSE~Indigo~WCF的发展脉络,对从VC6.0开始从事软件开发的人,并不陌生。理解WCF,是传统软件开发人员迈向云时代的一条捷径。


        WCF作为SOA的微软方言,它与其他平台如何整合?最普遍的,是通过JAVA访问WCF服务。简单搜索发现,一般采用Apache的Axis框架,通过WSDL文件生成JAVA类,进而直接调用WCF服务提供的方法。WSDL是web服务描述语言,是一种可扩展标记语言(Content-Type: application/soap+”字段加以标识。HTTP服务器在收到这样的请求后,就知道对方想要调用一个SOAP服务,而SOAP服务在微软平台,也就是WCF。


2.问题的引出


        既然JAVA可以通过SOAP框架Axis调用WCF服务,那么其他语言所编写的软件呢?比如非.NET框架的C++应用程序、采用C语言标准库实现的嵌入式终端等。同样的,也都可以采用SOAP实现WCF调用。这意味着非WCF客户必须按照SOAP协议规定的格式编写消息,然后通过TCP Socket发送给WCF服务器,后者在检查消息语法无误后,提取其中的RPC调用,将参数传递给相应的服务方法,并同样通过SOAP消息向客户发送返回值。本文将以一个简单的例子描述WCF与底层Socket之间的双向通信模式。



3.解决方案描述


        首先,在Visual Studio中新建两个C#类库项目和两个C#控制台应用项目,以及一个C++控制台应用项目。两个类库项目分别用于构建服务契约和服务实现,两个C#控制台应用项目分别用于构建服务宿主和服务客户,C++控制台应用项目用于构建底层Socket程序。


        服务契约如下:

namespace MathServiceContract
{
    [ServiceContract]
    public interface IMathService
    {
        [OperationContract]
        double Sum(double x, double y);

        [OperationContract]
        void Send2TcpSocket(string msg);
    }
}

        服务暴露了两个接口方法:用于返回两数之和的Sum,和用于向TCP客户端发送消息的Send2TcpSocket。Sum主要被底层Socket程序调用,Send2TcpSocket则被WCF服务客户端调用。系统结构如下:


WCF与C程序通信浅析-LMLPHP

       
       Send2TcpSocket的实现如下:



public void Send2TcpSocket(string msg)
{
    string host = "localhost";
    IPHostEntry iphe = Dns.GetHostEntry(host);
    IPAddress[] addList = iphe.AddressList;
    EndPoint ep = new IPEndPoint(addList[0], 9997);
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    socket.Connect(ep);
    if (socket.Connected)
        Console.WriteLine("Connected to socket at " + ep.ToString());
    else
        Console.WriteLine("Error: Unable to connect to " + ep.ToString());

    string sendAsString = msg;
    byte[] sendAsBytes = Encoding.UTF8.GetBytes(sendAsString);
    Console.WriteLine("Sending message to TCP Socket.");
    int numBytesSent = socket.Send(sendAsBytes, sendAsBytes.Length, SocketFlags.None);
}
        简单的通过Socket建立TCP连接向远端发送一个字符串。这个方法实现了WCF客户端(特别有意义的是对于Web页面)通过调用WCF服务向底层TCP Socket发送消息。


        解决了从WCF到Socket的消息传递,问题搞定了一半。接下来需要通过手写SOAP消息实现从Socket到WCF的调用。这里最困难之处在于保证SOAP消息的正确性,在WCF范畴内,不需要如此赤裸裸地面对SOAP,因为.NET框架帮我们做掉了。事实上,即使有经验的开发人员也难以默写一段完整的SOAP消息。这时候有必要借助于调试助手:TCPTrace(http://www.pocketsoap.com/tcptrace/)。该工具是一个路由器,可以如实记录从A端口到B端口传输的TCP消息。因此,在WCF服务宿主的App.config文件中如此设置:

       
       
没有使用netTcpBinding,是因为:“The NetTcpBinding …… is an appropriate Windows Communication Foundation (WCF) system-provided choice for communicating over an Intranet.……, but it is intended only for WCF-to-WCF communication.”

        采用security mode为None的ws2007HttpBinding作为绑定方式,并在不同于服务端口9998的另一端口9990监听,设置监听模式为Explicit。并在WCF客户端的App.config中如下设置:


        启动服务,运行客户端、TCP Trace,设置监听端口为9998,目标端口为9990,即可拦截从9998端口向9990端口发送的SOAP消息。消息格式如下:


POST /MathService HTTP/1.1
Content-Type: application/soap+



        可见纯手写难度之大。好在有了这个模板,在程序中照猫画虎也不难:




#define BUF_SIZE 4096

//......

// 设置发送字符串
char soapBody1[] = "";

char soapBody[BUF_SIZE];

double input1 = 1.1, input2 = 2.2;
sprintf(soapBody, "%s%.1f%s%.1f%s", soapBody1, input1, soapBody2, input2, soapBody3);

char soapHead1[] = "POST /MathService HTTP/1.1\r\nContent-Type: application/soap+

        然后向TCP Socket发送上述soapMessage即可。发送成功后,WCF服务器会返回如下响应消息:


HTTP/1.1 200 OK
Content-Length: 413
Content-Type: application/soap+



        至此,WCF和底层Socket之间的双向通信就全部完成了。


4.参考文献


         [1] 使用套接字进行 WCF 服务测试 http://msdn.microsoft.com/zh-cn/ee309879
        [4]