通过Remoting分布公网上的Server实现对Client的回调

    最近工作中遇到一个Remoting的回调的问题,即Client取得Server注册后对象后调用其函数,并传递一个Delegate参数,以让服务器Callback。这个功能在局域网内测试通过,如果配成公网地址在局域网内也是成功的,但当放到真正的公网环境中就失败了。其中Server和Client都是在各自的局域网内,通过服务器连接公网,服务器都有固定的公网IP地址,并将Server端公网地址的7788端口映射到Server所在机器,将Client端公网地址的7789端口映射到Client所在机器。经过大量资料的查询和调试,终于成功了,现将方法公布出来与大家一起分享。

一.公用部分:Common工程

1.定义Server端要发布的类的Interface:ICoordinator.cs

using System;

namespace Common
{
 //定义Server端要发布的类的Interface
 public interface ICoordinator
 {
  // Register a new Server
  int RegisterServer(ServerInfo serverInfo);
 }
}


2.定义发布类中方法的参数类和委托:ServerInfo.cs
using System;

namespace Common
{
 //定义Callback的Delegate
 public delegate void ReportDelegate(string s);

 //定义发布类中方法的参数类
 [Serializable]
 public class ServerInfo
 {
  public ServerInfo(ReportDelegate onReport)
  {
   this.OnReport = onReport;
  }
 
  public readonly ReportDelegate OnReport;
 }

}


3.Channel注册和反注册的通用代码:ChannelCom.cs

using System;
using System.Collections;
using System.Net;
using System.Reflection;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;

namespace Common
{
 public enum ChannelGroup
 {
  TCP,
  HTTP
 }

 public class ChannelCom
 {
  public static void StartChannel(ChannelGroup channelGroup, string channelName, string ip, int port)
  {
   BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
   BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
   serverProvider.TypeFilterLevel = TypeFilterLevel.Full;

   IDictionary props = new Hashtable();
   if (channelName != null && channelName != "")
    props["name"] = channelName;
   props["port"] = port;

   if(channelGroup == ChannelGroup.TCP)
   {
    TcpChannel channel = (TcpChannel)GetRegisteredChannel(channelName);
    if (channel == null)
    {
     channel = new TcpChannel(props,clientProvider,serverProvider);
     ChannelDataStore channelData = (ChannelDataStore)channel.ChannelData;
     SetChannelUris(channelData, ip);
    }
    ChannelServices.RegisterChannel(channel);
   }
   else
   {
    HttpChannel channel = (HttpChannel)GetRegisteredChannel(channelName);
    if (channel == null)
    {
     channel = new HttpChannel(props,clientProvider,serverProvider);
     ChannelDataStore channelData = (ChannelDataStore)channel.ChannelData;
     SetChannelUris(channelData, ip);
    }
    ChannelServices.RegisterChannel(channel);
   }
  }

  public static void StopChannel(string channelName)
  {
   IChannel channel = GetRegisteredChannel(channelName);
   if (channel != null)
   {
    ChannelServices.UnregisterChannel(channel);
   }
  }

  private static void SetChannelUris(ChannelDataStore channelData, string newIpAddress)
  {
   //检查是否进行调整,"(HOLD)"是个人定义的
   if (newIpAddress != "(HOLD)")
   {
    if (newIpAddress == null || newIpAddress.Trim() == "")
    {
     //获得本机IP地址(最后一个) 即:如果有外网地址使用外网地址,否则使用内网地址
     System.Net.IPAddress[] ipList = System.Net.Dns.GetHostByName(System.Net.Dns.GetHostName()).AddressList;
     string localIPAddress;
     if (ipList.Length > 1)
      localIPAddress = ipList[1].ToString();
     else
      localIPAddress = ipList[0].ToString();
   
     newIpAddress = localIPAddress;
    }

    string localPoint = channelData.ChannelUris[0];
    //取得协议
    int i = localPoint.IndexOf(":");
    string confer = localPoint.Substring(0,i + 3);
    //取得信道的端口号
    i = localPoint.LastIndexOf(":");
    string localPort = localPoint.Substring(i,localPoint.Length - i);
    //重设信道IP地址
    string[] IpAndPort = {confer + newIpAddress + localPort};
    channelData.ChannelUris = IpAndPort;
   }
  }

  private static IChannel GetRegisteredChannel(string channelName)
  {
   foreach(IChannel channel in ChannelServices.RegisteredChannels)
   {
    if (channel.ChannelName == channelName)
    {
     return channel;
    }
   }

   return null;
  }
 }

}


二.Server代码:Server工程

1.实现Server端要发布的类:CenterCoordinator.cs

using System;
using Common;

namespace Server
{
 //实现Server端要发布的类
 public sealed class CenterCoordinator : MarshalByRefObject, ICoordinator
 {
  public override object InitializeLifetimeService()
  {
   return null;
  }

  public int RegisterServer(ServerInfo serverInfo)
  {
   string s = "Hello World!";
 
   //进行Callback
   if (serverInfo != null && serverInfo.OnReport != null)
   {
    serverInfo.OnReport("OK");
    return 1;
   }
   else
    return 0;
  }
 }

}

2.开启服务器的窗体:FmServer.cs

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.Remoting;
using Common;

namespace Server
{
 /// <summary>
 /// Summary description for FmServer.
 /// </summary>
 public class FmServer : System.Windows.Forms.Form
 {
  private CenterCoordinator _coordinator = null;

  private System.Windows.Forms.Label label1;
  private System.Windows.Forms.TextBox txtIP;
  private System.Windows.Forms.Label label2;
  private System.Windows.Forms.TextBox txtPort;
  private System.Windows.Forms.Button btnStart;
  private System.Windows.Forms.RadioButton rbTCP;
  private System.Windows.Forms.RadioButton rbHttp;
  /// <summary>
  /// Required designer variable.
  /// </summary>
  private System.ComponentModel.Container components = null;

  public FmServer()
  {
   //
   // Required for Windows Form Designer support
   //
   InitializeComponent();

   //
   // TODO: Add any constructor code after InitializeComponent call
   //
  }

  /// <summary>
  /// Clean up any resources being used.
  /// </summary>
  protected override void Dispose( bool disposing )
  {
   if( disposing )
   {
    if (components != null)
    {
     components.Dispose();
    }
   }
   base.Dispose( disposing );
  }

  #region Windows Form Designer generated code
  /// <summary>
  /// Required method for Designer support - do not modify
  /// the contents of this method with the code editor.
  /// </summary>
  private void InitializeComponent()
  {
   this.label1 = new System.Windows.Forms.Label();
   this.txtIP = new System.Windows.Forms.TextBox();
   this.txtPort = new System.Windows.Forms.TextBox();
   this.label2 = new System.Windows.Forms.Label();
   this.btnStart = new System.Windows.Forms.Button();
   this.rbTCP = new System.Windows.Forms.RadioButton();
   this.rbHttp = new System.Windows.Forms.RadioButton();
   this.SuspendLayout();
   //
   // label1
   //
   this.label1.Location = new System.Drawing.Point(16, 16);
   this.label1.Name = "label1";
   this.label1.TabIndex = 0;
   this.label1.Text = "本地公网IP:";
   //
   // txtIP
   //
   this.txtIP.Location = new System.Drawing.Point(88, 12);
   this.txtIP.Name = "txtIP";
   this.txtIP.Size = new System.Drawing.Size(112, 21);
   this.txtIP.TabIndex = 1;
   this.txtIP.Text = "";
   //
   // txtPort
   //
   this.txtPort.Location = new System.Drawing.Point(88, 40);
   this.txtPort.Name = "txtPort";
   this.txtPort.Size = new System.Drawing.Size(112, 21);
   this.txtPort.TabIndex = 3;
   this.txtPort.Text = "7788";
   //
   // label2
   //
   this.label2.Location = new System.Drawing.Point(16, 42);
   this.label2.Name = "label2";
   this.label2.TabIndex = 2;
   this.label2.Text = "开放端口号:";
   //
   // btnStart
   //
   this.btnStart.Location = new System.Drawing.Point(16, 72);
   this.btnStart.Name = "btnStart";
   this.btnStart.Size = new System.Drawing.Size(240, 23);
   this.btnStart.TabIndex = 4;
   this.btnStart.Text = "启动";
   this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
   //
   // rbTCP
   //
   this.rbTCP.Checked = true;
   this.rbTCP.Location = new System.Drawing.Point(208, 8);
   this.rbTCP.Name = "rbTCP";
   this.rbTCP.Size = new System.Drawing.Size(48, 24);
   this.rbTCP.TabIndex = 5;
   this.rbTCP.TabStop = true;
   this.rbTCP.Text = "TCP";
   //
   // rbHttp
   //
   this.rbHttp.Location = new System.Drawing.Point(208, 40);
   this.rbHttp.Name = "rbHttp";
   this.rbHttp.Size = new System.Drawing.Size(48, 24);
   this.rbHttp.TabIndex = 6;
   this.rbHttp.Text = "HTTP";
   //
   // FmServer
   //
   this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
   this.ClientSize = new System.Drawing.Size(264, 102);
   this.Controls.Add(this.rbHttp);
   this.Controls.Add(this.rbTCP);
   this.Controls.Add(this.btnStart);
   this.Controls.Add(this.txtPort);
   this.Controls.Add(this.label2);
   this.Controls.Add(this.txtIP);
   this.Controls.Add(this.label1);
   this.Name = "FmServer";
   this.Text = "Server";
   this.Load += new System.EventHandler(this.FmServer_Load);
   this.ResumeLayout(false);

  }
  #endregion

  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main()
  {
   Application.Run(new FmServer());
  }

  private void btnStart_Click(object sender, System.EventArgs e)
  {
   if (btnStart.Text == "启动")
   {
    //Server端开启Channel
    ChannelGroup channelGroup;
    if (rbTCP.Checked)
     channelGroup = ChannelGroup.TCP;
    else
     channelGroup = ChannelGroup.HTTP;
    string channelName = "MyChannel";
    string ip = txtIP.Text; //这是本机在公网上的地址
    int port = int.Parse(txtPort.Text); //这是开放的端口
    ChannelCom.StartChannel(channelGroup, channelName, ip, port);

    //发布对象
    _coordinator = new CenterCoordinator();
    string servant = "MyCoordinator"; //这是发布对象的访问名
    ObjRef objRef = RemotingServices.Marshal(_coordinator, servant);

    btnStart.Text = "停止";
    txtIP.Enabled = false;
    txtPort.Enabled = false;
    rbTCP.Enabled = false;
    rbHttp.Enabled = false;
   }
   else
   {
    RemotingServices.Disconnect(_coordinator);

    ChannelCom.StopChannel("MyChannel");

    btnStart.Text = "启动";
    txtIP.Enabled = true;
    txtPort.Enabled = true;
    rbTCP.Enabled = true;
    rbHttp.Enabled = true;
   }
  }

  private void FmServer_Load(object sender, System.EventArgs e)
  {
   txtIP.Text = System.Net.Dns.Resolve(System.Net.Dns.GetHostName()).AddressList[0].ToString();
  }

 }
}


三.Client代码:Client工程

1.定义包含Callback函数的类:CallbackClass.cs

using System;
using Common;

namespace Client
{
 //包含Callback函数的类,必须保证该回调函数所在类继承自MarshalByRefObject
 [Serializable]
 public class CallbackClass : MarshalByRefObject
 {
  public override object InitializeLifetimeService()
  {
   return null;
  }

  public void OnReport(string s)
  {
   System.Windows.Forms.MessageBox.Show(s, "回调成功");
  }
 }

}

2.连接服务器并调用的客户端窗体:FmClient.cs

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Common;

namespace Client
{
 public class FmClient : System.Windows.Forms.Form
 {
  ICoordinator _coordinator = null;

  private System.Windows.Forms.RadioButton rbHttp;
  private System.Windows.Forms.RadioButton rbTCP;
  private System.Windows.Forms.Label label2;
  private System.Windows.Forms.Label label1;
  private System.Windows.Forms.Label label3;
  private System.Windows.Forms.Label label4;
  private System.Windows.Forms.Label label5;
  private System.Windows.Forms.TextBox txtSvrPort;
  private System.Windows.Forms.TextBox txtSvrIP;
  private System.Windows.Forms.TextBox txtCallbackPort;
  private System.Windows.Forms.TextBox txtLocalIP;
  private System.Windows.Forms.Button btnLink;
  private System.Windows.Forms.Button btnAccess;
  /// <summary>
  /// Required designer variable.
  /// </summary>
  private System.ComponentModel.Container components = null;

  public FmClient()
  {
   //
   // Required for Windows Form Designer support
   //
   InitializeComponent();

   //
   // TODO: Add any constructor code after InitializeComponent call
   //
  }

  /// <summary>
  /// Clean up any resources being used.
  /// </summary>
  protected override void Dispose( bool disposing )
  {
   if( disposing )
   {
    if (components != null)
    {
     components.Dispose();
    }
   }
   base.Dispose( disposing );
  }

  #region Windows Form Designer generated code
  /// <summary>
  /// Required method for Designer support - do not modify
  /// the contents of this method with the code editor.
  /// </summary>
  private void InitializeComponent()
  {
   this.rbHttp = new System.Windows.Forms.RadioButton();
   this.rbTCP = new System.Windows.Forms.RadioButton();
   this.txtSvrPort = new System.Windows.Forms.TextBox();
   this.label2 = new System.Windows.Forms.Label();
   this.txtSvrIP = new System.Windows.Forms.TextBox();
   this.label1 = new System.Windows.Forms.Label();
   this.label3 = new System.Windows.Forms.Label();
   this.txtCallbackPort = new System.Windows.Forms.TextBox();
   this.label4 = new System.Windows.Forms.Label();
   this.txtLocalIP = new System.Windows.Forms.TextBox();
   this.label5 = new System.Windows.Forms.Label();
   this.btnLink = new System.Windows.Forms.Button();
   this.btnAccess = new System.Windows.Forms.Button();
   this.SuspendLayout();
   //
   // rbHttp
   //
   this.rbHttp.Location = new System.Drawing.Point(200, 40);
   this.rbHttp.Name = "rbHttp";
   this.rbHttp.Size = new System.Drawing.Size(48, 24);
   this.rbHttp.TabIndex = 12;
   this.rbHttp.Text = "HTTP";
   //
   // rbTCP
   //
   this.rbTCP.Checked = true;
   this.rbTCP.Location = new System.Drawing.Point(200, 16);
   this.rbTCP.Name = "rbTCP";
   this.rbTCP.Size = new System.Drawing.Size(48, 24);
   this.rbTCP.TabIndex = 11;
   this.rbTCP.TabStop = true;
   this.rbTCP.Text = "TCP";
   //
   // txtSvrPort
   //
   this.txtSvrPort.Location = new System.Drawing.Point(80, 40);
   this.txtSvrPort.Name = "txtSvrPort";
   this.txtSvrPort.Size = new System.Drawing.Size(112, 21);
   this.txtSvrPort.TabIndex = 10;
   this.txtSvrPort.Text = "7788";
   //
   // label2
   //
   this.label2.Location = new System.Drawing.Point(8, 43);
   this.label2.Name = "label2";
   this.label2.TabIndex = 9;
   this.label2.Text = "服务端口号:";
   //
   // txtSvrIP
   //
   this.txtSvrIP.Location = new System.Drawing.Point(80, 16);
   this.txtSvrIP.Name = "txtSvrIP";
   this.txtSvrIP.Size = new System.Drawing.Size(112, 21);
   this.txtSvrIP.TabIndex = 8;
   this.txtSvrIP.Text = "";
   //
   // label1
   //
   this.label1.Location = new System.Drawing.Point(8, 19);
   this.label1.Name = "label1";
   this.label1.TabIndex = 7;
   this.label1.Text = "服务公网IP:";
   //
   // label3
   //
   this.label3.ForeColor = System.Drawing.Color.Red;
   this.label3.Location = new System.Drawing.Point(8, 80);
   this.label3.Name = "label3";
   this.label3.Size = new System.Drawing.Size(184, 23);
   this.label3.TabIndex = 13;
   this.label3.Text = "下面两个仅用于有回调的情况";
   //
   // txtCallbackPort
   //
   this.txtCallbackPort.Location = new System.Drawing.Point(80, 128);
   this.txtCallbackPort.Name = "txtCallbackPort";
   this.txtCallbackPort.Size = new System.Drawing.Size(112, 21);
   this.txtCallbackPort.TabIndex = 17;
   this.txtCallbackPort.Text = "0";
   //
   // label4
   //
   this.label4.Location = new System.Drawing.Point(8, 131);
   this.label4.Name = "label4";
   this.label4.TabIndex = 16;
   this.label4.Text = "回调端口号:";
   //
   // txtLocalIP
   //
   this.txtLocalIP.Location = new System.Drawing.Point(80, 104);
   this.txtLocalIP.Name = "txtLocalIP";
   this.txtLocalIP.Size = new System.Drawing.Size(112, 21);
   this.txtLocalIP.TabIndex = 15;
   this.txtLocalIP.Text = "";
   //
   // label5
   //
   this.label5.Location = new System.Drawing.Point(8, 107);
   this.label5.Name = "label5";
   this.label5.TabIndex = 14;
   this.label5.Text = "本地公网IP:";
   //
   // btnLink
   //
   this.btnLink.Location = new System.Drawing.Point(8, 160);
   this.btnLink.Name = "btnLink";
   this.btnLink.Size = new System.Drawing.Size(112, 32);
   this.btnLink.TabIndex = 18;
   this.btnLink.Text = "连接";
   this.btnLink.Click += new System.EventHandler(this.btnLink_Click);
   //
   // btnAccess
   //
   this.btnAccess.Location = new System.Drawing.Point(128, 160);
   this.btnAccess.Name = "btnAccess";
   this.btnAccess.Size = new System.Drawing.Size(112, 32);
   this.btnAccess.TabIndex = 19;
   this.btnAccess.Text = "访问";
   this.btnAccess.Click += new System.EventHandler(this.btnAccess_Click);
   //
   // FmClient
   //
   this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
   this.ClientSize = new System.Drawing.Size(256, 198);
   this.Controls.Add(this.btnAccess);
   this.Controls.Add(this.btnLink);
   this.Controls.Add(this.txtCallbackPort);
   this.Controls.Add(this.txtLocalIP);
   this.Controls.Add(this.txtSvrPort);
   this.Controls.Add(this.txtSvrIP);
   this.Controls.Add(this.label4);
   this.Controls.Add(this.label5);
   this.Controls.Add(this.label3);
   this.Controls.Add(this.rbHttp);
   this.Controls.Add(this.rbTCP);
   this.Controls.Add(this.label2);
   this.Controls.Add(this.label1);
   this.Name = "FmClient";
   this.Text = "Client";
   this.Load += new System.EventHandler(this.FmClient_Load);
   this.ResumeLayout(false);

  }
  #endregion

  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main()
  {
   Application.Run(new FmClient());
  }

  private void btnLink_Click(object sender, System.EventArgs e)
  {
   if (btnLink.Text == "连接")
   {
    //获取远程对象
    ChannelGroup channelGroup;
    if (rbTCP.Checked)
     channelGroup = ChannelGroup.TCP;
    else
     channelGroup = ChannelGroup.HTTP;
    string protocol = "tcp";
    if (channelGroup == ChannelGroup.HTTP) protocol = "http";
    string serverIp = txtSvrIP.Text; //Server所在公网地址
    int serverPort = int.Parse(txtSvrPort.Text); //Server开放的端口
    string servant = "MyCoordinator"; //Server端发布对象的访问名
    string uri = string.Format("{0}://{1}:{2}/{3}" , protocol , serverIp , serverPort , servant);
    _coordinator = Activator.GetObject(typeof(ICoordinator) ,uri) as ICoordinator;

    //开启Callback通道的代码
    string channelName = "MyChannel"; //注意这里的名字跟Server端开启的该通道名字一致,这不一定,但建议
    string localip = txtLocalIP.Text; //这是本机在公网上的地址
    int loaclport = int.Parse(txtCallbackPort.Text); //这是Callback的端口,如果你的机器直接连接公网则可以设置为0,否则应设置具体的数字,因为要做端口映射
    ChannelCom.StartChannel(channelGroup, channelName, localip, loaclport);

    btnLink.Text = "断开";
    txtSvrIP.Enabled = false;
    txtSvrPort.Enabled = false;
    txtLocalIP.Enabled = false;
    txtCallbackPort.Enabled = false;
    rbTCP.Enabled = false;
    rbHttp.Enabled = false;
    btnAccess.Enabled = true;
   }
   else
   {
    _coordinator = null;
    
    ChannelCom.StopChannel("MyChannel");

    btnLink.Text = "连接";
    txtSvrIP.Enabled = true;
    txtSvrPort.Enabled = true;
    txtLocalIP.Enabled = true;
    txtCallbackPort.Enabled = true;
    rbTCP.Enabled = true;
    rbHttp.Enabled = true;
    btnAccess.Enabled = false;
   }
  }

  private void FmClient_Load(object sender, System.EventArgs e)
  {
   txtLocalIP.Text = System.Net.Dns.Resolve(System.Net.Dns.GetHostName()).AddressList[0].ToString();
  }

  private void btnAccess_Click(object sender, System.EventArgs e)
  {
   if (_coordinator != null)
   {
    CallbackClass lCall = new CallbackClass();
    ServerInfo lServerInfo = new ServerInfo(new ReportDelegate(lCall.OnReport));

    int i = _coordinator.RegisterServer(lServerInfo);
   }
  }

 }

}


 

 总结一下:

1.关键点是端口的映射(客户端回调通道的端口要在其服务器上以公网地址映射到该电脑上).和通道地址的修改。
2.客户端启动的回调通道的地址要是公网的地址。但想QQ的客户端怎么知道我所在的公网地址呢?自己想办法哦,比如客户端先请求服务端返回当前连接的地址(我猜这总该是客户端公网的地址),然后再用该地址启动回调通道。
3.我在做此测试的时候出现"Cannot find assembly Client"的序列化错误,经过研究发现是Server工程和Client工程都要用到的Common工程生成的Common.dll文件版本不一致的原因。为什么不一致?因为两个工程编译后生成的文件在不同的目录,因为引用关系所以Common工程被编译了两遍,而每编译一遍它的版本号是不同的。解决办法是将Server工程和Client工程的输出目录设为同一个目录,或者在运行时是两边的Common.dll为同一个版本的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值