• 网络学院
  • IT资讯
  • 操作系统
  • 网络技术
  • 软件应用
  • 办公软件
  • 编程技术
  • 网站架设
  • 数据库类
  • 平面设计
  • 多媒体类
  • 游戏资讯
  • 教学论文
  • 认证考试
Visual
  站点:
  • 首 页
  • 最新软件
  • 文章教程
  • 国内软件
  • 国外软件
  • 绿色软件
  • 源码下载
  • 字体下载
Visual C#建立简单消息传递系统
软件发布 Visual C#建立简单消息传递系统
网络软件 系统工具 应用软件 联络聊天 图形图像 多媒体类 行业软件 游戏娱乐 编程开发 安全相关 教育教学 数码软件 绿软下载
热门软件: QQ 瑞星 pplive e话通 木马克星 千千静听 office2000 五笔字根 Photoshop 视频分割
返回文章教程首页 >> 编程技术 >> C#教程 >> Visual C#建立简单消息传递系统

Visual C#建立简单消息传递系统

添加时间: 2007-7-12 1:24:36  作者: C#教程  阅读次数:109   来源: http://www.d9soft.com

        摘要:本文讨论基于套接字(socket)的体系结构以及怎样建立一个高效的、易于使用的、可以同时在PC和Pocket PC上运行的消息传递(message-passing)系统。

  套接字和消息
  目前,大多数Web服务和所有的远程应用程序都使用了远程过程调用(remote-procedure-call,RPC)方法。你所做工作好像是在调用一个函数,但是在其后台执行了大量的操作以确保它在服务器上发生。在较低的层次,系统是在两台计算机之间传递消息,但这是不可视的。

  然而,当你转换到套接字操作的时候,你就在纯粹的基于消息的系统中编程了。这会改变你编写的代码的类型,因为读取返回的数据的唯一途径是通过消息。它与使用无返回值或输出参数的.net类有点类似,在这些情况下所有的返回信息都需要通过事件传递。

  由于我希望服务器程序告诉客户端什么时候应该改变曲目,使用消息就很有利,因为信息可以从服务器到达客户端,不需要客户端明确地请求该信息。但是,它要求你使用不同的方式达到目标。

  在解释所有操作之前,我想先谈论一点点安全性方面的问题。如果你在自己的计算机上打开了某个端口,其它人可能利用这个端口做不利的事情。他们可能希望写入没有意义的信号,以确定自己是否能够控制你的计算机或者使它崩溃。当你编写这类应用程序的时候考虑一下这种可能性是必要的。我的例子将运行在防火墙后面的网络上,所以我感觉到相对安全。

  简单的套接字

  我从建立一个服务器程序开始,它能给一个整数加上1,下面是服务器端代码:

public static void Main()
{
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
TcpListener listener = new TcpListener(localAddr, 9999);

Console.WriteLine("Waiting for initial connection");
listener.Start();
Socket socket = listener.AcceptSocket();
Console.WriteLine("Connected");
NetworkStream stream = new NetworkStream(socket);
BinaryReader reader = new BinaryReader(stream);
BinaryWriter writer = new BinaryWriter(stream);

int i = reader.ReadInt32();
i++;
writer.Write(i);
}


  它首先在本机的9999端口上建立了一个TCP监听器,接着启动监听器并等待连接。一旦得到了连接,它就接收一个整数,给它加1,并把它发送回去。

  需要指出的是我在此处使用的本地地址是127.0.0.1。在测试的时候这种情形可以很好地运行,这个时候客户端和服务器程序在相同的计算机上运行,但是当它们在不同的计算机上运行时,程序就不能运行了。在后面的部分中我将给出更复杂的代码。

传递消息

  通过套接字传递未经处理的数据毫无乐趣,而通过套接字传递对象可能更有趣一些。为了达到这个目标,我们需要一个得到对象并把它转换为字节流的途径。最明显的解决方案是使用运行时(runtime)提供的串行化(serialization)支持。不幸的是,使用这种方法会有少量的问题。

  第一个问题是串行化需要很大的开销,这意味着它使用的字节比传递数据必要的字节多一些。如果使用SOAP格式化,这个问题就更严重。这是否成为一个问题依赖于应用程序的性能需求。第二个问题是串行化在简洁框架组件中不能使用。由于没有简单的实现方法,我们需要自己做这个工作。在这个过程中,我们做的事情比串行化要少多了。

  我们从建立一个枚举开始,它定义了可以传递什么类型的消息:

public enum MessageType
{
RequestEmployee = 1,
Employee,
}

  对于每种消息类型,我们需要一个对象定义该对象:

public class RequestEmployee: ISocketObject
{
int id;
public RequestEmployee(int id)
{
this.id = id;
}

}

public RequestEmployee(BinaryReader reader)
{
id = reader.ReadInt32();
}

public int ID
{
get
{
return id;
}
}

public void Send(BinaryWriter writer)
{
writer.Write((int) MessageType.RequestEmployee);
writer.Write(id);
}
}

  我们使用的这种途径与ISerializable接口很相似。ISocketObject接口定义了一个Send()函数,它串行化通过通道的数据,接着还有一个并行化该数据的构造函数。

  在这些对象中的某个串行化自身的时候,它发送的第一个信息是消息标识符。它让接收者知道将到达哪种类型的对象并建立该对象。下面是客户端的代码:

RequestEmployee requestEmployee = new RequestEmployee(15);
requestEmployee.Send(writer);

MessageType messageType = (MessageType) reader.ReadInt32();

switch (messageType)
{
case MessageType.Employee:
Employee employee = new Employee(reader);
Console.WriteLine("{0} = {1}", employee.Name, employee.Address);
break;
}

  RequestEmployee这段代码建立了一个对象并把它发送给服务器程序,接着它找出返回的是哪种对象,并且并行化它。

  尽管这个示例项目被标记为client和server,但是两者之间唯一真正的差别是连接建立的方式。当这个过程完成后,它们都使用相似的代码发送和接收消息,即使它们需要处理自己的消息集合。

面向对象设计vs.实用主义

  这种方法的缺点之一是你必须使用一个大的switch语句结束,但是前辈一直教导我们大的switch语句是较差的设计的表现。通常的面向对象(Object Oriented,OO)的途径是使用多态性(polymorphism)的。为了达到这个目的,我们先建立一个抽象的基类(base class),接着从该类衍生出所有的消息对象。每个类需要执行串行化、并行化和处理消息等多个方法,主要的代码是:

  · 读取消息类型

  · 建立实例(使用反射)

  · 调用虚HandleMessage()函数

  这样做是可以实现的,但是效果很差,我并不喜欢。首先,编写建立实例的代码很难,并且由于它使用了反射,它的速度更慢。更重要的是,消息的处理过程并不在HandleMessage()函数之内,这意味着它必须是共享库的一部分。这是不宜使用的,因为消息的处理过程与消息如何传递没有什么关系。由于这些问题的存在,我依然决定使用较少面向对象但是更加容易编写的途径。

  前面的示例只处理了单个消息。在现实世界中,我们需要同时处理多个消息。

  服务器端的多线程

  我的最终的目标是把该服务器程序的功能添加到一个已有的应用程序中。因为不希望修改已有的应用程序的代码,我就必须在某个线程上运行服务器程序。同样,我希望可以同时接受多个连接。

  上面的例子在端口9999上监听,但是由于一个客户端只能与一个端口对话,我需要为每个连接使用不同端口的途径。SocketListener类将在9999端口上监视,当新的连接请求到达的时候,它将查找一个未使用的端口并把它发送回给客户端。下面是这个类的大致情形:

public class SocketListener
{
int port;
Thread thread;

public SocketListener(int port)
{
this.port = port;
ThreadStart ts = new ThreadStart(WaitForConnection);
thread = new Thread(ts);
thread.IsBackground = true;
thread.Start();
}

public void WaitForConnection()
{
// 主要的代码
}
}

  WaitForConnection()是执行所有这些操作的方法。这个类的构造函数执行建立新线程的任务,这个线程将运行WaitForConnection()。打开套接字并接受连接与前面的例子相似。下面是该线程的主循环:

while (true)
{
Console.WriteLine("Waiting for initial connection");
listener.Start();
Socket socket = listener.AcceptSocket();
NetworkStream stream = new NetworkStream(socket);
BinaryReader reader = new BinaryReader(stream);
BinaryWriter writer = new BinaryWriter(stream);

Console.WriteLine("Connection Requested");

int usERPort = port + 1;
TcpListener specificListener;
while (true)
{
try
{
specificListener =
new TcpListener(localAddr, userPort);
specificListener.Start();
break;
}
catch (SocketException)
{
userPort++;
}
}
//远程用户应该使用specificListener。
//把该端口发送回给远程用户,并为我们在该端口上建立服务器应用程序。
SocketServer socketServer = new SocketServer(specificListener);

writer.Write(userPort);
writer.Close();
reader.Close();
stream.Close();
socket.Close();
}

  我希望能够支持多个连接,因此使用一个端口以便于客户端表明它们希望建立一个连接,接着服务器程序找到一个空的端口并把该端口发送回给客户端,该端口用于特定客户端的连接。

  我没有找到查找未使用端口的方法,因此该While循环用于找出未使用的端口。接着它把该端口号发送回客户端并清除对象。

  此处还有需要指出的一点点微妙之处。SocketServer的原始版本把端口号作为一个参数。不幸的是,这意味着在该端口上建立监听器之前客户端不能作出请求,这是很不好的。为了防止出现这种情况,我在给客户端发送端口号之前建立了TcpListener,它确保不会出现这种紧急情况。

  SocketServer类建立了额外的线程,并使用了下面的主循环:

try
{
while (true)
{
MessageType messageType = (MessageType) reader.ReadInt32();

switch (messageType)
{
case MessageType.RequestEmployee:
Employee employee =
new Employee("Eric Gunnerson", "One Microsoft Way");
employee.Send(writer);
break;
}
}
}
catch (IOException)
{

}
finally
{
socket.Close();
}

  这个主循环是一个简单的获取请求/处理请求的循环。try-catch-finally在此处用于当客户端断开连接的时候从异常中恢复过来。

客户端的事件

  在客户端,我编写了一个Windows传统客户端程序,可以供PC使用也可以供Pocket PC使用。该Windows窗体环境是基于事件的,而且使用事件处理套接字消息也是理想的。这是通过SocketClient类实现的。第一步是为每个消息定义一个委托和事件:

public delegate void EmployeeHandler(Employee employee);
public event EmployeeHandler EmployeeReceived;

  第二步是编写发送事件的代码:

case MessageType.Employee:
Employee employee = new Employee(reader);
if (EmployeeReceived != null)
form.Invoke(EmployeeReceived, new object[] {employee});
break;

  当事件发生的时候就应该更新窗体了。为了更可靠,这个操作需要在主UI线程上发生。这是通过调用窗体的Invoke()实现的,它将安排在主UI线程上调用的委托。

  因为这种基于消息的体系结构,服务器程序要有对于异步事件的内建的支持。示例有一个CurrentCount消息,它是由服务器程序每秒钟发送的。

  总结

  我对这个基于套接字的体系结构很满意,它是轻量级的、易于使用的,并且它可以同时在PC和Pocket PC上运行。

 

上下文章:

 

上一篇文章: 用Visual C#实现网络封包监视 下一篇文章: 创建基于ASP.NET的SMTP邮件服务

相关文章:

  • Windows操作系统的发展简史
  • 微软明日发布2个安全补丁 修复系统组件漏洞
  • 病毒修改EXE文件图标 破坏系统危害极大
  • 磁盘空间整理原来也可以这么简单
  • 简单就是美 开源PDF阅读器Sumatra

相关软件:

  • 大熊家资金管理系统 2.8
  • 友信农资会员管理系统 v2.0
  • 飞翔停车场收费系统 v3.2
  • 利康医药(药店)进销存管理系统 v8.2.8
  • 888行情系统 V3.27
  • 医学三基智能出题组卷系统 1.0

 

快速导航

  • 网络学院
  • 精品汇聚
  • 字体下载
  • 教程下载
  • ASP源码
  • PHP源码
  • Net源码
  • JSP 源码

编程技术分类导航

  • ASP & ASP.NET教程
  • PHP教程
  • JSP教程
  • C/C++教程
  • VB & VB.NET教程
  • VC教程
  • Delphi教程
  • BCB教程
  • VFP教程
  • PB教程
  • JAVA教程
  • XML教程
  • C#教程
  • CGI教程

本类经典文章推荐

  • ADO.NET的开发场景及传统ADO的处理
  • 利用Visual C#实现Window管道技术
  • C#取得汉字的拼音的首字母
  • 使用C#编写DES加密程序的framework
  • Visual C#编写3D游戏框架示例
  • 用C#和本地Windows API操纵系统菜...
  • 在C#程序设计中使用Win32类库
  • Visual C#中调用Windows服务初探
  • 如何在C#的WinForm中制作饼状图和...
  • C#中实现DataGrid双向排序

C#教程阅读排行

  • 如何在C#的WinForm中制作饼状图和...
  • 浅析C#中图形编程
  • 用C#和本地Windows API操纵系统菜...
  • 彻底剖析C# 2.0泛型类的创建和使用
  • 使用C#编写DES加密程序的framework
  • C#如何在Form中嵌入并且操作Excel...
  • Visual C#中调用Windows服务初探
  • Visual C# 2005实现控件中捕获按键
  • C#中利用mediaplayer打造mp3播放器
  • Visual C#编写3D游戏框架示例

编程技术阅读总排行

  • VB入门教程之一
  • Java连接数据库实例
  • VC++之List Box/Check List Box控...
  • 第二章 PowerBuilder 入门之创建新...
  • VC++ List Ctrl控件
  • 第一章 什么是PowerBuilder
  • VC++ Combo Box/Combo Box Ex控件
  • 学C++不得不看的一篇文章
  • VB入门教程之二
  • VC++之Button控件

广告位置

字母检索 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 回到顶部

关于我们 | 版权声明 | 免责条款 | 广告联系 | 软件发布 | 下载帮助 | 下载排行 | 网站地图 | 特别鸣谢 | 友情连接

copyright; 2005-2008 D9soft.com 第九软件网 版权所有