65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET Remoting 简明指南(真的,就这么简单)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (53投票s)

2003年6月19日

13分钟阅读

viewsIcon

422827

downloadIcon

7369

本文旨在提供一个循序渐进的指南,让读者了解 .NET Remoting 的要点。本教程将指导读者搭建一个功能齐全的聊天程序。

Sample Image - RemotingSimpleEng.gif

引言

本文旨在提供一个循序渐进的指南,让读者了解 .NET Remoting 的要点。我曾尝试寻找优秀的教程,但发现很难找到一个能在我的电脑上运行的!所以,这是我首次尝试用 VB.NET 编写 .NET Remoting 教程。只要按照这些步骤操作,应该不难运行。阅读完本文后,请务必 在此处 阅读本文的续篇。

问:什么是应用程序边界或域?

答:应用程序域是一个逻辑边界,可以是进程边界或机器边界。通常,不存在直接访问跨越这个假想边界的对象的机制。这就是 Remoting 服务发挥作用的地方,因为它能够访问跨域或跨边界的对象。

问:按值传递?按对象传递?这是什么意思?

答:既然您已经了解了应用程序域是什么,我们就会问自己,我们希望如何让某个类的实例在原始应用程序域之外可访问?我们有两个选择:要么请求一个对象的副本,要么请求一个指向原始对象的引用(或者用行话来说,代理)。

问:我们使用按值传递... 但如何做到?

答:如果您希望您的对象在运行时请求远程对象的副本(按值封送),我们必须在远程对象上执行以下操作之一:

  • SerializableAttribute 添加到类

    示例

    <Serializable()> Public Class XYZ
  • 实现 ISerializable 接口(有关更多详细信息,请参阅:.NET Framework 类库,ISerializable 接口)

    示例

    Public Class XYZ
        Implements ISerializable

问:那按引用传递呢...?

答:如果您希望您的对象在运行时请求远程对象的引用(按引用封送 [MBR]),我们必须确保可远程对象的类直接或间接继承自 MarshalByRefObject。这使得运行时能够创建该对象的一个代理。

问:如何开始?

答:通常需要发生两件事。服务器必须发布一个可远程的对象(或服务)。该服务可以位于 TCP 通道或 HTTP 通道上,甚至两者都包含。我们为该服务命名,例如“RemotingChat”。另一方面,客户端连接到特定的服务器或主机名,使用受支持的通道指定服务名称,然后说:“我想要 RemotingChat 这个对象的一个(值)实例或这个(引用)实例”。请记住,所有这些都可以通过编程方式或使用 app.config 文件完成。

问:我是否遗漏了什么... 哦,是的,您还需要决定您希望对象是客户端激活还是服务器激活。它们是什么以及如何做到?

答:服务器激活的对象发布在 URL 上,如下所示:

ProtocolScheme://ComputerName:Port/AppName/ObjectUri

这个 URL 被客户端称为“已知”(即使约翰·琼斯也能使用它!)。因此,它被称为已知类型。甚至 URL 也被称为已知对象 URL。

另一方面,客户端激活为每个客户端创建一个对象,并且该对象的生命周期会持续到以下任一情况发生为止:

  • 客户端崩溃... 并丢失对对象的引用
  • 对象的租约到期(无法再存活...)

您的客户端 URL 将如下所示:

ProtocolScheme://ComputerName:Port/AppName

问:所以您选择了服务器激活?在 SingleCall 和 Single-what 之间有一个选择?Singleton,...

答:还有另一个选择?嗯,当您希望每个对象服务一个且仅一个请求时,您将使用 SingleCall 远程对象。当您希望它在方法调用到来时按需创建时,这很有用。哦,是的,别忘了,方法调用结束后,它就消失了... 因此,它的生命周期仅限于方法调用。您可能希望在无状态应用程序中使用它(简单来说:忘记您曾经调用过它们的应用程序...)。有人说,这是负载均衡应用程序的选择。

再一个呢?啊,是的,它是 singleton。如果您需要所有客户端访问一个且仅一个远程对象,这很有用。在这里,服务器的远程处理层创建一个 Singleton 对象。这个孤胆对象处理来自所有客户端的方法调用。悲哀吧?但它能活100年——它能活到服务器的生命周期。几乎与 SingleCall 同事相反,因此它在有状态应用程序中有用。当创建和维护对象的开销很大时,这是一个不错的选择。

要在主机上以编程方式激活远程对象,您可以这样编写代码:

new WellKnownServiceTypeEntry(typeof(InBetween.Manager), _ 
       "Manager", WellKnownObjectMode.Singleton);

new WellKnownServiceTypeEntry(typeof(InBetween.Manager), _ 
  "Manager", WellKnownObjectMode.Singlecall); 

问:您提到有两种方法可以配置我的可远程对象的创建:编程方式和使用配置文件。您是什么意思?

答:考虑到您想进行服务器激活;这意味着您要这样做:

<configuration>

  <system.runtime.remoting>

      <application name="WiseOwlMathService">

          <service>

              <wellknown mode="SingleCall" type="InBetween.Manager,Manager"

                  objectUri = "MYOBJECT1" />

             <wellknown mode="Singleton" type="InBetween.Manager,Manager"

                  objectUri = "MYOBJECT2" />

         </service>

      <channels>

                  <channel port="9999" ref="http" />

                  <channel port="8888" ref="tcp" />

      </channels>

      </application>

  </system.runtime.remoting>

  </configuration>

或者这个

using System;

  using System.Reflection;

  using System.Runtime.Remoting;
 class RemotingHost {

  static void Main(string[] args) {

  String s = Assembly.GetExecutingAssembly().Location;

  RemotingConfiguration.Configure ("Yourserver.exe.config");

  Console.ReadLine();

  }

  }

问:客户端激活代码呢?

答:这里是您的代码,您需要在服务器(主机)部分放置以下代码来指定要注册的对象类型:

using System.Runtime.Remoting;

      //. . .blah blah blah

      ActivatedServiceTypeEntry as =

      new ActivatedServiceTypeEntry(typeof(InBetween.Manager));

RemotingConfiguration.RegisterActivatedServiceType(as);

在客户端,

using System;

  using System.Runtime.Remoting;

  using System.Runtime.Remoting.Channels;

  using System.Runtime.Remoting.Channels.Http;

  using System.Runtime.Remoting.Channels.Tcp;


class RemotingHost {
static void Main(string[] args) {
RemotingConfiguration.ApplicationName = "YOURSERVICENAME";
ActivatedServiceTypeEntry as =
new ActivatedServiceTypeEntry(typeof(InBetween.Manager));
RemotingConfiguration.RegisterActivatedServiceType(as
ChannelServices.RegisterChannel(new HttpChannel(9999));
ChannelServices.RegisterChannel(new TcpChannel(8888));
//etc etc
}
}

<configuration>

  <system.runtime.remoting>

  <application name="YOURSERVICENAME">

  <service>

  <activated type="InBetween.Manager,Manager" />

  </service>

  <channels>

  <channel port="9999" ref="http" />

  <channel port="8888" ref="tcp" />

  </channels>

  </application>

  </system.runtime.remoting>

  </configuration>

问:那么,您现在有了客户端。但它如何激活远程对象?

答:如果您指的是位于 URL 的服务器激活或已知对象,您将使用:

  • 客户端使用 Activator.GetObject 获取代理
  • 客户端也可以使用 new 获取代理

否则,您将使用客户端请求对象工厂(存在于 URL 上)来实例化对象并返回其代理,使用:

Activator.CreateInstance

或者使用,我们简单的

new

问:所有对象激活代码之间有什么区别?

答:总结一下,这里有一个表格:

GetObject 这会返回位于指定位置的已知类型的代理。有趣的是,在第一次方法调用之前没有网络流量。这将在客户端从服务器的元数据构建对象代理。服务器在第一次方法调用时激活对象。
GetObject 通过 System.Runtime.Remoting. RemotingServices 有一个 GetObject 包装器,围绕着 System.Runtime.RemotingRemotingServices.Connect 也是如此
Using System.Runtime.Remoting;
   static object o RemotingServices.Connect
   (Type classToProxy, string url);
Activator.CreateInstance

这有点像个坏家伙,因为这种客户端激活需要网络往返。它向服务器发送一个激活消息。但好的一点是,您可以包含构造函数参数(不像简单的 new objectName())。然后服务器使用指定的构造函数通过构建新实例的 ObjRef 来创建对象。然后服务器将带有 ObjRef 的消息发送给客户端。最后(在受到网络流量等的摆布之后),客户端根据 ObjRef 创建代理...

Dim activationAttributes() As Object = { New _
   Activation.UrlAttribute _
   ("https://:9999/RemotingChat")
   }
Dim o As Object = _
 Activator.CreateInstance_
 (GetType(InBetween.Manager),_
  Nothing, activationAttributes)
   InBetween.Manager c = _
     CType (o, InBetween.Manager)

问:还有别的吗?

答:哦,是的……您需要考虑在框架的 v1.1 版本中使用此代码时的一些事项……您需要在 formatter 标记中添加一个名为 typeFilterLevel 的属性,并将其设置为 Full 以放宽安全限制,否则您会收到异常。

System.Security.SecurityException.
 Type System.DelegateSerializationHolder and the types derived from it (such
 as System.DelegateSerializationHolder) are not permitted to be deserialized
 at this security level.
 System.Runtime.Serialization.SerializationException
 Because of security restrictions,
 the type System.Runtime.Remoting.ObjRef cannot
 be accessed.

请务必更改您的配置文件(在服务器上):

<configuration>
<system.runtime.remoting>
<channel ref="http" port="7777">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="soap" typeFilterLevel="Full" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
<service>
<!-- ... Add your services here ... -->
</service>
</system.runtime.remoting>
</configuration>

以及客户端:

<configuration>
<system.runtime.remoting>
<channel ref="http" port="0">
<clientProviders>
<formatter ref="binary" />
</clientProviders>
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
<client>
<!-- ... Add your classes here ... -->
</client>
</system.runtime.remoting>
</configuration>

有关如何以编程方式执行此操作的更多详细信息,请参阅此 链接

问:您能给我们一个解释这一切的图表吗?

答:当然……

Diagram showing all the sinks involved

服务器激活的聊天程序背景

在深入研究代码之前,理解正在发生的事情很重要(别担心,其实不难)。首先,我们将有一个名为 Manager 的可远程对象,它将是一个单例对象(稍后定义)。客户端将通过调用唯一的一个(单例Manager 对象中的 SendText 方法来发送消息,该方法会引发一个事件 evtReceiveText。该事件将由所有聊天客户端处理,然后它们将在文本框 txtReceivedMsgs 中显示接收到的消息。

好的,让我们概念化一下:我们有一个且仅有一个可远程对象,称为 Manager,来自 InBetween 命名空间(在概念上,它位于服务器和客户端之间,来回序列化消息)。接下来,我们有一个服务器,它将注册或创建我们自己的已知服务或应用程序 ChatApplication 的新实例。接下来,我们将有客户端本身,它们将实现 Manager 的名为 evtReceiveText 的事件,这是一个在任何人发送消息时引发给所有客户端的事件。

现在,对于那些已经做过一些远程处理的人来说,我将不使用配置文件(一个用于配置应用程序名称和端口等的 XML 文件),而是使用这两个:

在客户端,获取 Manager 对象(稍后会有更多注释)

theManager = CType(Activator.GetObject(Type.GetType("InBetween.Manager, _
  InBetween"), "http://UP:7777/ChatApplication"),
  InBetween.Manager)

在服务器端,注册已知服务(稍后会有更多注释)

System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType( _
  Type.GetType("InBetween.Manager, InBetween"), _
  "ChatApplication", WellKnownObjectMode.Singleton)

使用代码(Manager.vb)

This is what your solution should look like

(注意:我包含了.config 文件,以防您想尝试使用它们)

  1. 开始时,创建一个空的解决方案。
  2. 添加一个名为 InBetween 的新类库项目。
  3. 将默认的.vb 名称更改为 Manager.vb
  4. 复制下面的代码(务必边看边阅读注释)

    这个家伙是可远程对象。

      Imports System
    
     'We are going to declare an event delegate
    
     'Event delegates sound really big... but what this is doing is that:
    
     ' "I want to be able, from this class,
     ' to raise a type of an event called
     'ReceiveText..."
    
     ' "And, I want those who use Manager class to handle that event"
    
     ' "...Now, I'm going to pass username As String and text As String."
    
     ' "It's gonna be up to you, how you're going to handle it"
    
     Public Delegate Sub ReceiveText_
     (ByVal username As String, ByVal text As String)
      Public Class Manager _
     Inherits MarshalByRefObject
     
     'Why inherit MarshalByRefObject?
    
     'Enables access to objects across application domain boundaries
    
     'in applications that support remoting.
    
     'Src: .NET Framework Class Library
     ' MarshalByRefObject Class [Visual Basic]
    
     'Let's break it down (in simple english)...
    
     'MarshalByRefObject is the means which allows
     ' objects like this class here, to
    
     'communicate across boundaries, via remoting.
    
     'An application domain is a partition in an
     'operating system process where one
     'or more applications reside.
     'What's this? I thought we already declared an event handler?
    
     'Here's where we need to declare the event itself.
    
     'Delegates, as its name suggests
     'are 'ambassadors' who advertises this event
    
     'That's why in our Client.vb,
     'we say "theManager.evtReceiveText",
     'and not 'theManager.ReceiveText'
    
     Public Event evtReceiveText As ReceiveText
     Public Overrides Function _
       InitializeLifetimeService() As Object
    
     'This function, if overriden,
     ' allows us to tell the Manager object how long
    
     'it should live. If by any reason we'd
     'like to have it stay a 'lil longer,
     'we renew it and so forth
    
      'We won't do anything here. So what happens is that
      'the Manager object is governed by a default lifetime.
    
      Return Nothing
    
      End Function
      
      Public Function SendText(ByVal username _
        As String, ByVal text As String)
    
      'Later in the client.vb code,
      'you would see that chat clients (like John Doe),
      'will raise thise event by calling SendText
      'with the appropriate paramaters.
    
      'This event is then propagated to ALL clients,
      ' like Jones and so forth.
    
      'On Jones' PC (for example), Client.vb
      'would handle this event by displaying something like
    
      '"John: yada yada" on the txtReceivedMsgs.
    
      'Of course John's client window sould also show the same
    
      RaiseEvent evtReceiveText(username, text)
    
      End Function
      
      Public Function getHash() As String
    
      'this is just a handy function to reaffirm
      ' that all your clients are communicating
    
      'with a ONE and only Manager object
    
      'Which means (in simple English),
    
      'John and the Jones will see the very same
      'hash code which was assigned to the
      'Manager object
    
      Return Me.GetType.GetHashCode().ToString
    
      End Function
      
      End Class

使用代码(Server.vb)

  1. 添加一个名为 Server 的新控制台应用程序项目,或者您想叫什么名字。
  2. 将默认的.vb 名称更改为 Server.vb
  3. 不要忘记添加对 InBetween 的引用,因为我们将首次调用它并调用其 getHash 方法。
  4. 复制下面的代码(务必边看边阅读注释)

    这是负责注册我们的服务 ChatApplication 的家伙。我们注意到我们创建了一个单例,如下所示:

      Imports System.Runtime.Remoting.Channels
    
     Imports System.Runtime.Remoting.Channels.Http
    
     Imports System.Runtime.Remoting
    
     Imports System
    
     Imports InBetween
      
    
     public class Server
      
      Public Shared Sub Main()
    
     Dim server1 As Server
    
     server1 = New Server()
    
     End Sub
      
     Public Sub New()
       'Create a HTTP channel for our use
    
     'We'll 'talk' on this port
    
     'IMPORTANT: Make sure you don't have
     'anything running on this channel!
    
     Dim chan As IChannel = New HttpChannel(7777)
        'Register it
    
     ChannelServices.RegisterChannel(chan)
       'I could have read the config from an xml file with :
    
     'System.Runtime.Remoting.RemotingConfiguration.
     'Configure(your.config.file)
    
     '(XML format)
    
     'Refer .NET Framework Class Library
     'RemotingConfiguration.Configure Method
     '[Visual Basic]
    
     'BUT somehow, I just couldn't make it work!
     'So I went for this great 1 line code shown below:
     'Notice these things:
    
     '1. We are registering a service: RegisterWellKnownServiceType
    
     '2. It's of type: Type.GetType("InBetween.Manager, InBetween")
    
     ' InBetween is the namespace, Manager is the class
    
     '3. We're calling that application, ChatApplication
    
     '4. It's of type: Singleton
    
     ' Why Singleton and not singlecall? If u chose singlecall,
    
     ' everyone client (John and the Jones)
     'would be creating their own Manager objects
    
     ' which would mean no message ever gets across to anyone
    
     System.Runtime.Remoting.RemotingConfiguration._
       RegisterWellKnownServiceType(_
       Type.GetType("InBetween.Manager, InBetween"), _
       "ChatApplication", WellKnownObjectMode.Singleton)
     'I registered the Manager class and called getHash
    
     'Read Manager.vb for more details on getHash
    
     Dim Manager1 As New Manager()
    
     Console.WriteLine("The Manager object's ID:" _
        & Manager1.getHash())
    
     System.Console.WriteLine("Hit ENTER to exit...")
     'We don't want this object to die out too fast, so we just put a
     'ReadLine here to sustain the object's lifetime
    
     System.Console.ReadLine()
      
      End Sub
    
     End Class

使用代码(Client.vb)

现在,在这里我们设计客户端。

  1. 添加一个名为 Client 的新 Windows 应用程序项目。
  2. 将默认的.vb 名称更改为 Client.vb。如果您打算复制粘贴,请跳到 5。
  3. 添加两个多行文本框,分别命名为 txtReceivedMsgstxtMsgToSend,用于处理接收到的消息和输入要发送的消息。
  4. 添加一个简单的按钮 btnSend
  5. 再次,添加对 InBetweenSystem.Runtime.Remoting 的引用。我们还需要后者,因为我们需要创建一个 HTTPChannel 对象。
  6. 复制下面的代码,确保您将变量命名如上(如果您自己创建了接口)。(务必边看边阅读注释)

Sample Image - RemotingSimpleEng.gif

Imports System.Runtime.Remoting.Channels.Http
 Public Class Client Inherits System.Windows.Forms.Form
  Private theManager As InBetween.Manager
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer. InitializeComponent()
'Add any initialization after the InitializeComponent() call
Dim chan As HttpChannel

'Create a HTTP channel for our use
'IMPORTANT: Make sure you don't have anything running on this channel!
chan = New HttpChannel("8888")

'Registers a channel with the channel services.
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(chan)

'Creates a proxy for a currently running remote object
'This remote object is the our InBetween.Manager's instance
'NOTE: Change 'UP' to your Chat server's name
'http://UP:7777/ChatApplication
theManager = CType(Activator.GetObject(Type.GetType("InBetween.Manager,_
  InBetween"), "http://UP:7777/ChatApplication"), InBetween.Manager)

'Add our event handler here
'In other words, tell this fellar,
'"when you receive an event called evtReceiveText
'(of type InBetween.Manager),
'then use the sub called HandleReceivedMsg
'to handle it
Try
 AddHandler Me.theManager.evtReceiveText, _
  AddressOf Me.HandleReceivedMsg
Catch
 e1 As Exception
 'Our simple exception handler
 MessageBox.Show(e1.Message)
End Try

'Cosmetic, I'm against it, but...
'(This displays a caption on your
'client window that says "Client on <PC NAME>")
Me.Text = "Client on " & _
 Windows.Forms.SystemInformation.ComputerName()

'Now, you would notice that the getHash(),
'will return a string that identifies
'the 'theManager's hash code.
'This Hash code will appear on ALL clients.
'Why? Simple, we are dealing with ONE and
'only ONE instance of InBetween's Manager class
'We specified singleton on the server (Module1.vb), remember?
'It's easy to remember, a 'single' 'ton', "SINGLE-TON"
MessageBox.Show(Me.theManager.getHash())
End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub _
  Dispose(ByVal disposing As Boolean)
 If disposing Then If Not (components Is Nothing) Then
   components.Dispose()
 End If
 End If
 MyBase.Dispose(disposing)
 End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents txtReceivedMsgs As System.Windows.Forms.TextBox
Friend WithEvents btnSend As System.Windows.Forms.Button
Friend WithEvents txtMsgToSend As System.Windows.Forms.TextBox
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.btnSend = New System.Windows.Forms.Button()
Me.txtReceivedMsgs = New System.Windows.Forms.TextBox()
Me.txtMsgToSend = New System.Windows.Forms.TextBox() Me.SuspendLayout()
'
'btnSend
'
Me.btnSend.Location = New System.Drawing.Point(280, 160)
Me.btnSend.Name = "btnSend" Me.btnSend.TabIndex = 0
Me.btnSend.Text = "&Send"
'
'txtReceivedMsgs
'
Me.txtReceivedMsgs.Location = New System.Drawing.Point(0, 8)
Me.txtReceivedMsgs.Multiline = True
Me.txtReceivedMsgs.Name = "txtReceivedMsgs"
Me.txtReceivedMsgs.ReadOnly = True
Me.txtReceivedMsgs.Size = New System.Drawing.Size(360, 88)
Me.txtReceivedMsgs.TabIndex = 1 Me.txtReceivedMsgs.Text = ""
'
'txtMsgToSend
'
Me.txtMsgToSend.Location = New System.Drawing.Point(0, 104)
Me.txtMsgToSend.Multiline = True
Me.txtMsgToSend.Name = "txtMsgToSend"
Me.txtMsgToSend.Size = New System.Drawing.Size(360, 48)
Me.txtMsgToSend.TabIndex = 2
Me.txtMsgToSend.Text = ""
'
'Client
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(360, 189)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
   {Me.txtMsgToSend, Me.txtReceivedMsgs, Me.btnSend})
Me.MaximizeBox = False Me.Name = "Client"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
Me.Text = "Client" Me.ResumeLayout(False)
End Sub
#End Region
Private Sub btnSend_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnSend.Click
'What happenning here?
'Once we press our 'Send' button,
'we raise an event via SendText method of Manager (InBetween)
'Then just, erase our textbox - txtMsgToSend 'Easy isn't it?
'To follow the event, peek at InBetween's Manager's SendText method
Me.theManager.SendText(Windows.Forms.SystemInformation.ComputerName,_
   Me.txtMsgToSend.Text)
txtMsgToSend.Text = ""
End Sub

Sub HandleReceivedMsg(ByVal username As String, ByVal text As String)
'Ok, here's what happens...
'John Doe sends u a message, the Manager object raises an event,
'your client intercepts it, and execution drops down here...
'You then append the text here...
'"... and I thought chat programs were hard..."
'well anyway, here's the line that does it
Me.txtReceivedMsgs.AppendText(username & " : " & text & vbCrLf)
End Sub

Private Sub Client_Closing(ByVal sender As Object, _
  ByVal e As System.ComponentModel.CancelEventArgs)_
  Handles MyBase.Closing
Try
 'Ok, let's undo what we've done
 'We've added a handler, (remember?), so now we need to remove it
 'You basically do this, ...
 RemoveHandler theManager.evtReceiveText, _
    AddressOf Me.HandleReceivedMsg
Catch
 e1 As Exception
 
'Exception handling for... err, simple ppl like us...
MessageBox.Show(e1.Message)
End Try
End Sub

Private Sub Client_Load(ByVal sender As System.Object,_
 ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
End Class

运行它!(结论)

  1. 构建解决方案。您将被提示设置启动对象,请按提示操作。
  2. 当您等待时,请注意以下几点:
    • 我们可以使用.config 文件,但我更喜欢通过代码来展示如何不使用.config
    • 每次客户端窗口加载时,您都会看到相同的哈希码。这是单例 Manager 对象的哈希码。
    • 我们在客户端和服务器两端都使用相同的 HTTP 端口(7777)。如果我们愿意,也可以使用 TCP。您最好确保 John Doe 的 FTP 服务器(或任何其他应用程序)在该端口上未运行(同时运行)!
    • 我们的已知对象已在服务器端公开。
  3. 在您真正运行它之前,请将server.exe 放在与client.exe 相同的目录中。将其他客户端放在其他 PC 上。
  4. 首先运行 server.exe,然后运行所有客户端。
  5. 聊天!

那么结论是什么?

  • 最重要的一点是:我们进行了服务器激活
  • MSDN 说:“……服务器激活对象是其生命周期由服务器直接控制的对象。服务器应用程序域仅在客户端对对象进行方法调用时创建这些对象,而不是在客户端调用 new(Visual Basic 中为 New())或 Activator.GetObject() 时创建;这可以节省一个仅用于实例创建的网络往返。当客户端请求服务器激活类型的实例时,仅在客户端应用程序域中创建一个代理……服务器激活对象有两种激活模式(或 WellKnownObjectMode 值):SingletonSingleCall……” .NET Framework 开发人员指南服务器激活 [Visual Basic]
  • 我们对对象的第一次方法调用是在 Server.vb 中。我们也可以从 client.vb 进行第一次调用。试试这个,在 client.vb 中取消 theManager = New InBetween.Manager() 的注释,然后在 server.vb 中注释掉这些:
    Dim Manager1 As New Manager()
      Console.WriteLine("The Manager object's ID:" & Manager1.getHash()) 

    所以您看,它仍然以相同的方式工作……我必须在 Server.vb 中进行第一次 getHash() 调用,因为我想在 server.vb 上显示哈希码。所以让我回顾一下:我们之所以称之为服务器激活,是因为我们的对象的生命周期是由服务器通过 Singleton 激活直接控制的。我们没有通过说 Dim Manager1 As New Manager() 来实例化任何东西,这只是注册了对象。实际对象是在我们进行方法调用之后才存在的。

关注点

奇怪的现象:第一个执行的客户端必须从 server.exe相同的目录下运行!后续客户端不必从与 server.exe 相同的目录启动。是的,这很奇怪,否则您会得到一个毫无意义的错误:

System.IO.FileNotFoundException
File or assembly name Client , or one of its dependencies, was not found.

是的,这是一个已知问题,您可能想在这里阅读更多关于此的信息:此处

注意:此问题的原因是客户端需要服务器的元数据来构建客户端的代理对象。解决方案在我第三篇文章中,此处

等等

  • 对于那些之前做过 COM 的人,“.NET Remoting 和远程激活的 COM 有什么区别?”与 COM 不同,Remoting 不会为您启动主机或服务器应用程序。这是 .NET Remoting 和 COM 中远程激活之间的一个重要区别。
  • MSDN 将我们的 Manager.vb 称为可远程类型。我们的 Server.vb主机应用程序
  • 您的主机应用程序域不限于我们简单的聊天程序,但它们可以是 Windows 服务、控制台应用程序、Windows 窗体应用程序、Internet 信息服务 (IIS) 进程或 ASP.NET 应用程序。
  • 一般主机任务(例如,在编写自己的主机时应考虑的事项)
    • 我的主机应用程序域将是什么样的?(Windows 服务、控制台应用程序、Windows 窗体应用程序、Internet 信息服务 (IIS) 进程或 ASP.NET 应用程序)
    • 我应该使用哪种激活模型?(客户端/服务器激活)
    • 选择一个通道(HTTP 或 TCP)和一个端口。使用 ChannelServices.RegisterChannel 注册它。请记住,您不能使用与乔叔叔的 FTP 服务器相同的端口……
  • 一般客户端任务(例如,在编写自己的客户端时应考虑的事项)
    • 我的客户端应用程序域将是什么样的?(Windows 服务、控制台应用程序、Windows 窗体应用程序、Internet 信息服务 (IIS) 进程或 ASP.NET 应用程序)
    • 我应该使用哪种激活模型?(客户端/服务器激活)
    • 我想使用客户端激活 URL(例如,ProtocolScheme://ComputerName:Port/PossibleApplicationName)还是远程类型的已知对象 URL(例如,ProtocolScheme://ComputerName:Port/ PossibleApplicationName/ObjectUri)。
    • 选择一个通道(HTTP 或 TCP)和一个端口。使用 ChannelServices.RegisterChannel 注册它。
  • 如果您想尝试使用.config 文件(我不喜欢),请记住,.NET Remoting 的大多数问题都发生在其一些设置不正确或与客户端应用程序的配置设置不匹配。很容易误输入名称、忘记端口或忽略属性。如果您在使用 Remoting 应用程序时遇到问题,请首先检查您的配置设置。

历史

  • 2003 年 6 月 13 日星期五:上传第一个版本。
  • 2003 年 6 月 14 日星期六:上传第二个版本(由于评价很高)。
  • 2003 年 6 月 16 日星期一:添加了等等部分。
  • 2003 年 7 月 27 日星期五:添加了一个稍好的引言。
© . All rights reserved.