Using Web Services for Remoting over the Internet. (中)

80酷酷网    80kuku.com

  services|web 
Concept and Design
Concept of the Remoting over Internet is based on dispatching a remoting message over Internet using the Web Service features as a transport layer. The following picture shows this solution:

Client activates a remote WKO to obtain its transparent proxy. During this process the custom remoting channel (ws) is initiated and inserted into the client channel sink chain. Invoking a method on this proxy, the IMessage is created which it represents a runtime image of the method call at the client side. This IMessage is passed into the channel sink. In our solution to the custom client (sender) channel ws. This channel sink has a responsibility to convert IMessage to the SoapMessage in the text/xml format pattern and send it over Internet to the Web Service gateway. The following picture shows this:

The Web Service gateway has two simply WebMethods, one for the SoapMessage format and the other one for a binary  format encoded by base64 into the text string. The first one method enables to use a call from an unknown client, as opposite in the binary formatting message for .Net client.
Lets continue with the IMessage/SoapMessage flows on the Web Service gateway side as it is shown on the above picture. The text/xml formatted SoapMessage sent over Internet might look like the following snippet:
Text/XML formatted SoapMessage Request.
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encoding
xmlns:a3="http://schemas.microsoft.com/clr/nsassem/MyRemoteObject/MyRemoteObject,
Version=1.0.772.24659, Culture=neutral, PublicKeyToken=ec0dd5142ae7a19b"
xmlns:a1="http://schemas.microsoft.com/clr/ns/System.Runtime.Remoting.Messaging">
<SOAP-ENV:Body>
<System.Runtime.Remoting.Messaging.MethodCall id="ref-1">
<__Uri id="ref-2" xsi:type="SOAP-ENC:string">msmq://./reqchannel/endpoint</__Uri>

<__MethodName id="ref-3" xsi:type="SOAP-ENC:string">get_Id</__MethodName>

<__TypeName id="ref-4" xsi:type="SOAP-ENC:string">MyRemoteObject.RemoteObject,
MyRemoteObject, Version=1.0.772.24659, Culture=neutral,
PublicKeyToken=ec0dd5142ae7a19b</__TypeName>

<__Args href="#ref-5"/>
<__CallContext href="#ref-6"/>
</System.Runtime.Remoting.Messaging.MethodCall>
<SOAP-ENC:Array id="ref-5" SOAP-ENC:arrayType="xsd:ur-type[0]">
</SOAP-ENC:Array>
<a1:LogicalCallContext id="ref-6">
<User href="#ref-8"/>
</a1:LogicalCallContext>
<a3:User id="ref-8">
<FirstName id="ref-9" xsi:type="SOAP-ENC:string">Roman</FirstName>
<LastName id="ref-10" xsi:type="SOAP-ENC:string">Kiss</LastName>
</a3:User>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This string request has to be de-serialized back to the SoapMessage object, which is a clone object of the sender's origin. After that, we have an enough information to perform a Method call on the remote object. Conversion of the SoapMessage to IMessage needs to use some trick, therefore there is no class in .Net namespaces to do it. The trick is based on creating a RealProxy wrapper using the remote object type and its endpoint url address and overriding an Invoke method of  the base RealProxy class. Using the Reflection (late binding) to invoke the remote method, the RealProxy wrapper will catch the IMessage before its processing in the channel sink. Now, the Invoke method can perform updating the IMessage by original images such as LocicalCallContext and url address. After that, the IMessage is the same like on the client side over Internet. Now it's easy to forward this IMessage to the Message Sink calling its SyncProcessMessage method. The rest is done by a remoting paradigm.  
The SyncProcessMessage returns an IMessage which it represents a ReturnMessage from the remote method. Now the process is going to reverse into the text/xml format of the SoapMessage response . I will skip it this process for its simplicity and I will continue on the client custom channel (ws) where a response message has been returned. Before that, have a look the text/xml formatted SoapMessage response how it has been sent back to the custom channel over Internet:
Text/XML formatted SoapMessage Response.
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encoding
xmlns:a3="http://schemas.microsoft.com/clr/nsassem/MyRemoteObject/MyRemoteObject,
Version=1.0.772.24659, Culture=neutral, PublicKeyToken=ec0dd5142ae7a19b"
xmlns:a1="http://schemas.microsoft.com/clr/ns/System.Runtime.Remoting.Messaging">
<SOAP-ENV:Body>
<a1:MethodResponse id="ref-1">
<__Uri xsi:type="xsd:ur-type" xsi:null="1"/>
<__MethodName xsi:type="xsd:ur-type" xsi:null="1"/>
<__MethodSignature xsi:type="xsd:ur-type" xsi:null="1"/>
<__TypeName xsi:type="xsd:ur-type" xsi:null="1"/>
<__Return xsi:type="xsd:int">0</__Return>
<__OutArgs href="#ref-2"/>
<__CallContext href="#ref-3"/>
</a1:MethodResponse>
<SOAP-ENC:Array id="ref-2" SOAP-ENC:arrayType="xsd:ur-type[0]">
</SOAP-ENC:Array>
<a1:LogicalCallContext id="ref-3">
<User href="#ref-5"/>
</a1:LogicalCallContext>
<a3:User id="ref-5">
<FirstName id="ref-6" xsi:type="SOAP-ENC:string">Roman</FirstName>
<LastName id="ref-7" xsi:type="SOAP-ENC:string">Kiss</LastName>
</a3:User>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The result of the remoting call has to be de-serialized into the SoapMessage and then an IMessage can be generated by the function ReturnMessage. This IMessage is returned back to the remoting client infrastructure. In this point, the process of the remoting over Internet is done and the rest is a regular remoting mechanism.  
As I mentioned earlier, the Web Service gateway has two methods, one has been described the above using the SoapFormatter and the other one is using a BinaryFormatter.
Using the Binary Formatted Message.
The .Net clients can use a remoting over Internet in more efficient (faster) way using the BinaryFormatter. The design implementation is more straightforward than using the SoapFormatter. The IMessage is serialize/deserialize by the BinaryFormatter to/from memory stream. Than this stream image is encoded/decoded using the Base64 conversion class to/from text string. Note that this text string is not readable. The IMessage is plugged-in into the server remoting infrastructure using the RemotingServices.Connect and RemotingServices.ExecuteMessage functions from the Remoting namespace. Thanks for these functions, they really saved my time.
Limitation note.
The limitation of the above solution is done by the Web Service functionality. You cannot handle a distributed transaction over Internet. In this case, the Web Service gateway represents a non-transactional client and remote object is a root of the transaction.
Implementation
The implementation is divided into two assemblies - Custom Client Channel and Web Service gateway. Their implementation is straightforward without using any third party library support. I am going to concentrate only for these parts related to the IMessage processing. More details about the design and implementation of the Custom Remoting Channel can be found it in [][1]#[1]]1].
WebServiceChannelLib
This is a Custom Client Channel assembly to process an outgoing remoting message over Internet. The Client Message Sink has an implementation both message processing such as SyncProcessMessage and AsyncProcessMessage. Based on the m_mode value, the IMessage can be formatted by the SoapFormatter or BinaryFormatter.
The SyncProcessMessage function initiates the Web Service client proxy generated by the VS. There is a small modification in its constructor. This proxy is a generic for any location of the Web service, that's why the url address is pass through its constructor instead of its hard coding. Note that any Exception will be converted into the IMessage format and send back to the client.
Sender:
// IMessageSink (MethodCall)
public virtual IMessage SyncProcessMessage(IMessage msgReq)
{
   IMessage msgRsp = null;

   try
   {
      msgReq.Properties["__Uri"] = m_ObjectUri;
      Service webservice = new Service(m_outpath);

      if(m_mode == "SOAP")
      {
         // serialize IMessage into the stream (SoapMessage)
         MemoryStream reqstream = new MemoryStream();
         SoapFormatter sf = new SoapFormatter();
         RemotingSurrogateSelector rss = new RemotingSurrogateSelector();
         rss.SetRootObject(msgReq);
         sf.SurrogateSelector = rss;
         sf.AssemblyFormat = FormatterAssemblyStyle.Full;
         sf.TypeFormat = FormatterTypeStyle.TypesAlways;
         sf.TopObject = new SoapMessage();
         sf.Serialize(reqstream, msgReq);
         ISoapMessage sm = sf.TopObject;
         reqstream.Position = 0;
         StreamReader sr = new StreamReader(reqstream);
         string request = sr.ReadToEnd();
         reqstream.Close();
         sr.Close();

         // call web service
         string respond = webservice.SyncProcessSoapMessage(request);

         // return messages
         StreamWriter rspsw = new StreamWriter(new MemoryStream());
         rspsw.Write(respond);
         rspsw.Flush();
         rspsw.BaseStream.Position = 0;
         ISoapMessage rspsoapmsg = (ISoapMessage)sf.Deserialize(rspsw.BaseStream);
         rspsw.Close();

         if(rspsoapmsg.ParamValues[0] is Exception)
         {
            throw rspsoapmsg.ParamValues[0] as Exception;
         }
         else
         {
            object returnVal = rspsoapmsg.ParamValues[4];
            object[] OutArgs = rspsoapmsg.ParamValues[5] as object[];
            LogicalCallContext lcc = rspsoapmsg.ParamValues[6] as LogicalCallContext;
            ReturnMessage rm = new ReturnMessage(
                       returnVal,            //Object return
                       OutArgs,              //Object[] outArgs
                       OutArgs.Length,       //int outArgsCount
                       lcc,                  //LogicalCallContext callCtx
                       msgReq as IMethodCallMessage    //IMethodCallMessage mcm
                       );
            msgRsp = rm as IMessage;
         }
      }
      else
      {
         msgReq.Properties["__Uri2"] = m_ObjectUri; // workaround!
         // serialize and encode IMessage
         BinaryFormatter bf = new BinaryFormatter();
         MemoryStream reqstream = new MemoryStream();
         bf.Serialize(reqstream, msgReq);
         reqstream.Position = 0;
         string request = Convert.ToBase64String(reqstream.ToArray());
         reqstream.Close();
         // call Web Service
         string respond = webservice.SyncProcessMessage(request);

         // decode and deserialize IMessage
         byte[] rspbyteArray = Convert.FromBase64String(respond);
         MemoryStream rspstream = new MemoryStream();
         rspstream.Write(rspbyteArray, 0, rspbyteArray.Length);
         rspstream.Position = 0;
         msgRsp = (IMessage)bf.Deserialize(rspstream);
         rspstream.Close();
      }
   }
   catch(Exception ex)
   {
      Trace.WriteLine(string.Format("Client:SyncProcessMessage error = {0}", ex.Message));
      msgRsp = new ReturnMessage(ex, (IMethodCallMessage)msgReq);
   }

   return msgRsp;
}

public virtual IMessageCtrl AsyncProcessMessage(IMessage msgReq, IMessageSink replySink)
{
   IMessageCtrl imc = null;

   if(replySink == null) // OneWayAttribute
   {
      Trace.WriteLine("Client-[OneWay]Async:CALL");
      SyncProcessMessage(msgReq);
   }
   else
   {
      Trace.WriteLine("Client-Async:CALL");
      // spawn thread (delegate work)
      delegateAsyncWorker daw = new delegateAsyncWorker(handlerAsyncWorker);
      daw.BeginInvoke(msgReq, replySink, null, null);
   }

return imc;
}
The Web Service Client Proxy changes:
[System.Web.Services.WebServiceBindingAttribute(Name="ServiceSoap",
Namespace="http:]//tempuri.org/")]
public class Service : System.Web.Services.Protocols.SoapHttpClientProtocol {

   [System.Diagnostics.DebuggerStepThroughAttribute()]
      public Service(string uri) {
      this.Url = uri;
   }
   ...
}   
WebServiceListener
This is a Web Service gateway to listen a MethodCall formatted into the string request. There are two different WebMethods for this process: SyncProcessMessage and SyncProcessSoapMessage. The functions have a simple logic divided into tree steps: 

分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: