本篇文章主要介绍了C# 实现Remoting双向通信,.Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的

闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能。于是乎开始学习.Net Remoting.

.Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的。也就是说对象是由服务端创建的。

先上代码

首先是ICommand库

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace ICommand

{

  public interface IRemotingObject

  {

    event SendHandler ClientToServer;

    event ReceiveHandler ServerToClient;

    event UserChangedHandler Login;

    event UserChangedHandler Exit;

    /// <summary>

    /// 加法运算

    /// </summary>

    /// <param name="x1">参数1</param>

    /// <param name="x2">参数2</param>

    /// <returns></returns>

    string SUM(int x1, int x2);

    /// <summary>

    /// 获取服务端事件列表

    /// </summary>

    Delegate[] GetServerEventList();

    /// <summary>

    /// 发送消息

    /// </summary>

    /// <param name="info"></param>

    /// <param name="toName"></param>

    void ToServer(object info, string toName);

    /// <summary>

    /// 接受信息

    /// </summary>

    /// <param name="info"></param>

    /// <param name="toName"></param>

    void ToClient(object info, string toName);

    void ToLogin(string name);

    void ToExit(string name);

  }

  /// <summary>

  /// 客户端发送消息

  /// </summary>

  /// <param name="info">信息</param>

  /// <param name="toName">发送给谁,""表示所有人,null表示没有接收服务器自己接收,其他表示指定某人</param>

  public delegate void SendHandler(object info, string toName);

  /// <summary>

  /// 客户端接收消息

  /// </summary>

  /// <param name="info">信息</param>

  /// <param name="toName">发送给谁,""表示所有人,null表示没有接收服务器自己接收,其他表示指定某人</param>

  public delegate void ReceiveHandler(object info, string toName);

  /// <summary>

  /// 用户信息事件

  /// </summary>

  /// <param name="name">用户名</param>

  public delegate void UserChangedHandler(string name);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace ICommand

{

  public class SwapObject : MarshalByRefObject

  {

    public event ReceiveHandler SwapServerToClient

    {

      add { _receive += value; }

      remove { _receive -= value; }

    }

    /// <summary>

    /// 接受信息

    /// </summary>

    /// <param name="info"></param>

    /// <param name="toName"></param>

    public void ToClient(object info, string toName)

    {

      if (_receive != null)

        _receive(info, toName);

    }

    //无限生命周期

    public override object InitializeLifetimeService()

    {

      return null;

    }

    private ReceiveHandler _receive;

  }

}

第一个类就是定义一些接口,和一些委托,没有实质性的东西。

第二个类是定义了上一个接口类中的ToClient的事件和方法,作用之后会讲到。

然后就是集成ICommand接口的实质性的数据类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using ICommand;

namespace NetRemoting

{

  public class RemotingObject : MarshalByRefObject, IRemotingObject

  {

    /// <summary>

    /// 发送事件

    /// </summary>

    public event SendHandler ClientToServer

    {

      add { _send += value; }

      remove { _send -= value; }

    }

    /// <summary>

    /// 接收消息事件

    /// </summary>

    public event ReceiveHandler ServerToClient;

    /// <summary>

    /// 发送事件

    /// </summary>

    public event UserChangedHandler Login

    {

      add { _login += value; }

      remove { _login -= value; }

    }

    /// <summary>

    /// 发送事件

    /// </summary>

    public event UserChangedHandler Exit

    {

      add { _exit += value; }

      remove { _exit -= value; }

    }

    /// <summary>

    /// 加法运算

    /// </summary>

    /// <param name="x1">参数1</param>

    /// <param name="x2">参数2</param>

    /// <returns></returns>

    public string SUM(int x1, int x2)

    {

      return x1 + "+" + x2 + "=" + (x1 + x2);

    }

    /// <summary>

    /// 绑定服务端向客户端发送消息的事件方法

    /// </summary>

    /// <param name="receive">接收事件</param>

    public Delegate[] GetServerEventList()

    {

      return this.ServerToClient.GetInvocationList();

    }

    /// <summary>

    /// 发送消息

    /// </summary>

    /// <param name="info"></param>

    /// <param name="toName"></param>

    public void ToServer(object info, string toName)

    {

      if (_send != null)

        _send(info, toName);

    }

    /// <summary>

    /// 接收消息

    /// </summary>

    /// <param name="info"></param>

    /// <param name="toName"></param>

    public void ToClient(object info, string toName)

    {

      if (_receive != null)

        _receive(info, toName);

    }

    /// <summary>

    /// 登录

    /// </summary>

    /// <param name="name">用户名</param>

    public void ToLogin(string name)

    {

      if (!_nameHash.Contains(name))

      {

        _nameHash.Add(name);

        if (_login != null)

          _login(name);

      }

      else

      { throw new Exception("用户已存在"); }

    }

    /// <summary>

    /// 退出

    /// </summary>

    /// <param name="name">用户名</param>

    public void ToExit(string name)

    {

      if (_nameHash.Contains(name))

      {

        _nameHash.Remove(name);

        if (_exit != null)

          _exit(name);

      }

    }

    private SendHandler _send;

    private ReceiveHandler _receive;

    private UserChangedHandler _login;

    private UserChangedHandler _exit;

    private HashSet<string> _nameHash = new HashSet<string>();

  }

}

该类集成了MarshalByRefObject

由于Remoting传递的对象是以引用的方式,因此所传递的远程对象类必须继承MarshalByRefObject。MSDN对MarshalByRefObject的说明是:MarshalByRefObject 是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject 继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时,将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法进行通信,因此需要继承MarshallByRefObject。

该类主要是定义了一些方法用于客户端触发事件,ToServer,ToClient,ToLogin,ToExit以及一些事件,客户端发向服务端的事件,和服务端发向客户端的事件。

_nameHash 只是记录有哪些用户登录了。

接下去就是客户端和服务端了。

首先服务端:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Http;

using NetRemoting;

using System.Collections;

using System.Runtime.Serialization.Formatters;

using ICommand;

namespace NetRemotingServer

{

  public partial class Server : Form

  {

    public Server()

    {

      InitializeComponent();

      Initialize();

    }

    /// <summary>

    /// 注册通道

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void Server_Load(object sender, EventArgs e)

    {

      ChannelServices.RegisterChannel(_channel, false);

      //RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject), "SumMessage", WellKnownObjectMode.Singleton); //a方案

      /*将给定的 System.MarshalByRefObject 转换为具有指定 URI 的 System.Runtime.Remoting.ObjRef 类的实例。

       ObjRef :存储生成代理以与远程对象通信所需要的所有信息。*/

      ObjRef objRef = RemotingServices.Marshal(_remotingObject, "SumMessage");//b方案

      _remotingObject.ClientToServer += (info, toName) =>

      {

        rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(info.ToString() + "\r\n"); }));

        SendToClient(info, toName);

      };

      _remotingObject.Login += (name) =>

      {

        rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(name + " 登录" + "\r\n"); }));

      };

      _remotingObject.Exit += (name) =>

      {

        rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(name + " 退出" + "\r\n"); }));

      };

    }

    /// <summary>

    /// 注销通道

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void Server_FormClosing(object sender, FormClosingEventArgs e)

    {

      if (_channel != null)

      {

        _channel.StopListening(null);

        ChannelServices.UnregisterChannel(_channel);

      }

    }

    /// <summary>

    /// 广播消息

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void btnSend_Click(object sender, EventArgs e)

    {

      SendToClient(txtSend.Text, txtName.Text);

    }

    /// <summary>

    /// 发送消息到客户端

    /// </summary>

    /// <param name="info"></param>

    /// <param name="toName"></param>

    private void SendToClient(object info, string toName)

    {

      //foreach (var v in _remotingObject.GetServerEventList())

      //{

      //  try

      //  {

      //    ReceiveHandler receive = (ReceiveHandler)v;

      //    receive.BeginInvoke(info, toName, null, null);

      //  }

      //  catch

      //  { }

      // }

      _remotingObject.ToClient(txtSend.Text, txtName.Text);

    }

    /// <summary>

    /// 初始化

    /// </summary>

    private void Initialize()

    {

      //设置反序列化级别

      BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();

      BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();

      serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高

      IDictionary idic = new Dictionary<string, string>();

      idic["name"] = "serverHttp";

      idic["port"] = "8022";

      _channel = new HttpChannel(idic, clientProvider, serverProvider);

      _remotingObject = new RemotingObject();

    }

    HttpChannel _channel;

    private RemotingObject _remotingObject;

  }

}

然后客户端:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Http;

using ICommand;

using System.Runtime.Serialization.Formatters;

using System.Collections;

namespace NetRemotingClient

{

  public partial class Client : Form

  {

    public Client()

    {

      InitializeComponent();

    }

    /// <summary>

    /// 注册通道

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void Client_Load(object sender, EventArgs e)

    {

      try

      {

        //设置反序列化级别

        BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();

        BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();

        serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高

        //信道端口

        IDictionary idic = new Dictionary<string, string>();

        idic["name"] = "clientHttp";

        idic["port"] = "0";

        HttpChannel channel = new HttpChannel(idic, clientProvider, serverProvider);

        ChannelServices.RegisterChannel(channel, false);

        _remotingObject = (IRemotingObject)Activator.GetObject(typeof(IRemotingObject), "http://localhost:8022/SumMessage");

        //_remotingObject.ServerToClient += (info, toName) => { rtxMessage.AppendText(info + "\r\n"); };

        SwapObject swap = new SwapObject();

        _remotingObject.ServerToClient += swap.ToClient;

        swap.SwapServerToClient += (info, toName) =>

        {

          rtxMessage.Invoke((MethodInvoker)(() =>

        {

          if (toName == txtLogin.Text || toName == "")

            rtxMessage.AppendText(info + "\r\n");

        }));

        };

      }

      catch (Exception ex)

      { MessageBox.Show(ex.Message); }

    }

    /// <summary>

    /// 登录

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void btnLogin_Click(object sender, EventArgs e)

    {

      try

      {

        if (txtLogin.Text == "")

          throw new Exception("用户名不得为空");

        _remotingObject.ToLogin(txtLogin.Text);

      }

      catch (Exception ex)

      {

        MessageBox.Show(ex.Message);

      }

    }

    /// <summary>

    /// 退出

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void Client_FormClosing(object sender, FormClosingEventArgs e)

    {

      try

      {

        _remotingObject.ToExit(txtLogin.Text);

      }

      catch

      { }

    }

    /// <summary>

    /// 发送

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void btnSend_Click(object sender, EventArgs e)

    {

      //rtxMessage.AppendText(_remotingObject.SUM(2, 4) + "\r\n");

      _remotingObject.ToServer(txtSend.Text, txtName.Text);

    }

    private IRemotingObject _remotingObject;

  }

}

服务端实现步骤:

1、注册通道

要跨越应用程序域进行通信,必须实现通道。如前所述,Remoting提供了IChannel接口,分别包含TcpChannel和HttpChannel两种类型的通道。这两种类型除了性能和序列化数据的格式不同外,实现的方式完全一致,因此下面我们就以TcpChannel为例。

注册TcpChannel,首先要在项目中添加引用“System.Runtime.Remoting”,然后using名字空间:System.Runtime.Remoting.Channel.Tcp。代码如下:

1

2

TcpChannel channel = new TcpChannel(8022);

ChannelServices.RegisterChannel(channel);

在实例化通道对象时,将端口号作为参数传递。然后再调用静态方法RegisterChannel()来注册该通道对象即可。

2、注册远程对象

注册了通道后,要能激活远程对象,必须在通道中注册该对象。根据激活模式的不同,注册对象的方法也不同。

(1) SingleTon模式

对于WellKnown对象,可以通过静态方法RemotingConfiguration.RegisterWellKnownServiceType()来实现:

1

2

3

RemotingConfiguration.RegisterWellKnownServiceType(

        typeof(ServerRemoteObject.ServerObject),

        "ServiceMessage",WellKnownObjectMode.SingleTon);

(2)SingleCall模式

注册对象的方法基本上和SingleTon模式相同,只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。

1

2

3

RemotingConfiguration.RegisterWellKnownServiceType(

        typeof(ServerRemoteObject.ServerObject),

        "ServiceMessage",WellKnownObjectMode.SingleCall);

客户端实现步骤:

1、注册通道:

1

2

TcpChannel channel = new TcpChannel();

ChannelServices.RegisterChannel(channel);

注意在客户端实例化通道时,是调用的默认构造函数,即没有传递端口号。事实上,这个端口号是缺一不可的,只不过它的指定被放在后面作为了Uri的一部分。

2、获得远程对象

与服务器端相同,不同的激活模式决定了客户端的实现方式也将不同。不过这个区别仅仅是WellKnown激活模式和客户端激活模式之间的区别,而对于SingleTon和SingleCall模式,客户端的实现完全相同。

(1) WellKnown激活模式

要获得服务器端的知名远程对象,可通过Activator进程的GetObject()方法来获得:

1

2

ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject(

       typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");

首先以WellKnown模式激活,客户端获得对象的方法是使用GetObject()。其中参数第一个是远程对象的类型。第二个参数就是服务器端的uri。如果是http通道,自然是用http://localhost:8022/ServiceMessage了。因为我是用本地机,所以这里是localhost,你可以用具体的服务器IP地址来代替它。端口必须和服务器端的端口一致。后面则是服务器定义的远程对象服务名,即ApplicationName属性的内容。

1

2

3

4

5

6

7

8

9

//设置反序列化级别

        BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();

        BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();

        serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高

        //信道端口

        IDictionary idic = new Dictionary<string, string>();

        idic["name"] = "clientHttp";

        idic["port"] = "0";

        HttpChannel channel = new HttpChannel(idic, clientProvider, serverProvider);

从上述代码中可以看到注册方式有所变化,那是因为客户端注册服务端的事件时会报错“不允许类型反序列化”。

还有一个需要注意的是:

1

2

3

ObjRef objRef = RemotingServices.Marshal(_remotingObject, "SumMessage");

//RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject), "SumMessage", WellKnownObjectMode.Singleton);

//调用系统自动创建,导致拿不到_remotingObject对象的实例化,这样后期绑定事件就无法操作下去了,当然也可以直接静态事件绑定,这样就不需要手动实例化对象了

通过该方法手动创建_remotingObject这个对象的实例化。

然后之前讲到了一个SwapObject这个类,这个类的作用是事件交换。

1

2

3

4

5

6

_remotingObject.ServerToClient +=方法();

//这样因为这个方法是客户端的,服务端无法调用,所以需要一个中间转换的

 SwapObject swap = new SwapObject();//先创建一个Swap对象

 _remotingObject.ServerToClient += swap.ToClient;

 //然后服务端事件发信息给swap,然后swap再通过事件发消息给客户端,swap是客户端创建的所以可以发送,而swap是服务端的类,所以服务端也能识别,swap起到了中间过渡的作用

 swap.SwapServerToClient +=方法();

以上就是C#NetRemoting实现双向通信的示例代码分享的详细内容,更多请关注php中文网其它相关文章!

C# 实现Remoting双向通信的更多相关文章

  1. 初探Remoting双向通信(四)

    原 初探Remoting双向通信(四) 2013年06月26日 11:11:32 喜欢特别冷的冬天下着雪 阅读数 2632 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  2. 初探Remoting双向通信(三)

    原 初探Remoting双向通信(三) 2013年06月25日 17:51:08 喜欢特别冷的冬天下着雪 阅读数 4741 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  3. 初探Remoting双向通信(二)

    原 初探Remoting双向通信(二) 2013年06月25日 11:46:24 喜欢特别冷的冬天下着雪 阅读数 2977 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  4. 初探remoting双向通信(一)

    原 初探remoting双向通信(一) 2013年06月24日 15:47:07 喜欢特别冷的冬天下着雪 阅读数 4389 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  5. C# Remoting双向通信

    闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能.于是乎开始学习.Net Remoting. .Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为 ...

  6. .Net Remoting的双向通信和Windows Service的宿主服务

    原文:.Net Remoting的双向通信和Windows Service的宿主服务 作为微软分布式技术之一的.Net Remoting,从性能.安全等各方面来说都是相对比较稳定的,也是一项比较成熟的 ...

  7. .Net remoting, Webservice,WCF,Socket区别

    传统上,我们把计算机后台程序(Daemon)提供的功能,称为"服务"(service).比如,让一个杀毒软件在后台运行,它会自动监控系统,那么这种自动监控就是一个"服务& ...

  8. C#NetRemoting双向通信

    闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能.于是乎开始学习.Net Remoting. .Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为 ...

  9. .NET Remoting与Socket、Webservice和WCF的比较及优势 (转)

    1:Socket VS Remoting 使用socket无疑是效率最高的.但是,在复杂的接口环境下,Socket的开发效率也是最低的.故在兼顾开发效率的情况下,可以使用Remoting来代替Sock ...

随机推荐

  1. [arc066f]Contest with Drinks Hard

    题目大意: 有一些物品,每个买了有代价. 如果存在一个极大区间[l,r]内的物品都被买了,这个区间长度为k,可以获得的收益是k*(k+1)/2. 现在若干次询问,每次问假如修改了某个物品的价格,最大收 ...

  2. 【BZOJ4998】星球联盟

    题解: 应该还是比较水的 首先很容易发现的就是两个点一旦联通他们就永远联通了 所以联通之后我们就把他们之间缩成一个点

  3. [转]scp、sftp命令使用

    http://wangxuedong.com/index.php/archives/182/ 前言 有时候想上传文件到服务器或者从服务器下载一个文件到本地,但是服务器还没有配置ftp等环境,这时候可以 ...

  4. Json常用组件

    Json2.js   开发者:json官网:http://www.json.org/. 适用环境:用于在不支持JSON对象的浏览器(通常是国内使用IE内核的第三方浏览器)下使用.json2.js提供了 ...

  5. Flink应用开发-maven导入

    flink和spark类似,也是一种一站式处理的框架:既可以进行批处理(DataSet),也可以进行实时处理(DataStream) 使用maven导入相关依赖 <properties> ...

  6. Vim的合并行操作

    日常常用到多行合并的功能,记录如下: 第一种, 多行合并成一行,即: AAAAABBBBBCCCCC 合并为:AAAAA BBBBB CCCCC 方法1: normal状态下 3J 其中的3是范围,可 ...

  7. Codeforces 822E Liar dp + SA (看题解)

    Liar 刚开始感觉只要开个dp[ i ][ j ][ 0 / 1 ]表示处理了s的前 i 个用了 k 段, i 是否是最后一段的最后一个字符 的 t串最长匹配长度, 然后wa24, 就gg了.感觉这 ...

  8. weka的基本使用

    目录: 1. 简介 2.界面初识 3.数据格式 4.数据准备 5.关联规则 6.分类与回归 7.聚类分析 8.Weka相关资料 9.Weka二次开发 10.Weka源代码导入 1. 简介 WEKA的全 ...

  9. apache利用http_referer进行防盗链

    http://blog.sina.com.cn/s/blog_8729dd9801011rn1.html

  10. 了解fastadmin标准的控制器模块js的表格事件

    controller/A.php<-------------->public/assets/js/backend/a.js controller/b/A.php<---------- ...