本篇文章主要介绍了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. [BZOJ4709][JSOI2011]柠檬 决策单调性优化dp

    题解: 解法1: 单调栈优化 首先发现一个性质就是 如果当前从i转移比从j转移更加优秀 那么之后就不会从j转移 所以我们考虑利用这个性质 我们要维护一个队列保证前一个超过后一个的时间单调不减 怎么来维 ...

  2. MySQl 查询性能优化相关

    0. 1.参考 提升网站访问速度的 SQL 查询优化技巧 缓存一切数据,读取内存而不是硬盘IO 如果你的服务器默认情况下没有使用MySQL查询缓存,那么你应该开启缓存.开启缓存意味着MySQL 会把所 ...

  3. x86 版的 Arduino Intel Galileo 开发板的体验、分析和应用

    1.前言 在今年(2013)罗马举办的首届欧洲 Make Faire 上,Intel 向对外发布了采用 x86 构架的 Arduino 开发板:Intel Galileo.这无疑是一个开源硬件领域的重 ...

  4. tomcat调度配置

    调度可以基于nginx和http的调度 配置环境 1 安装tomcat 2 创建一个test测试页面 mkdir  /var/lib/tomcat/webapps/test/{WEB-INF,META ...

  5. JQuery函数大全

    $(”p”).addClass(css中定义的样式类型); 给某个元素添加样式 $(”img”).attr({src:”test.jpg”,alt:”test Image”}); 给某个元素添加属性/ ...

  6. LoadRunner的函数

     一.基础函数 在VU左边导航栏中,有三个LR框架函数,分别是vuser_init(),Action(),vuser_end(). 这三个函数存在于任何Vuser类型的脚本中: ●vuser_init ...

  7. day20 模块-sys,time,collection

    所有常用模块的用法:  http://www.cnblogs.com/Eva-J/articles/7228075.html 前情回顾: # 常用模块 # 常用模块 —— 东西多 # 异常处理 # 什 ...

  8. day10.函数,函数的参数

    函数的思维导图: 老师的笔记 昨天内容概括 #组长:默写统一交给组长 #不建议看视频 #上课敲过的所有的例子 # 1.看一遍.看能不能看懂 # 2.给每一道题起一个名字或者一句描述 # 3.看着文字, ...

  9. img-html-2

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. exporter API(导出、输出器api)moodel3.3

    Moodle[导出器]是接收数据并将其序列化为一个简单的预定义结构的类.它们确保输出的数据格式统一,易于维护.它们也用于生成外部函数的签名(参数和返回值) 外部函数定义在moodle/lib/exte ...