传说中的WCF(14):WCF也可以做聊天程序

传说中的WCF(1):这东西难学吗?
传说中的WCF(2):服务协定的那些事儿
传说中的WCF(3):多个协定
传说中的WCF(4):发送和接收SOAP头
传说中的WCF(5):数据协定(a)
传说中的WCF(6):数据协定(b)
传说中的WCF(7):“单向”&“双向”
传说中的WCF(8):玩转消息协定
传说中的WCF(9):流与文件传输
传说中的WCF(10):消息拦截与篡改
传说中的WCF(11):会话(Session)
传说中的WCF(12):服务器回调有啥用
传说中的WCF(13):群聊天程序
传说中的WCF(14):WCF也可以做聊天程序 

 

 

先看一个截图。

 

上面的图,各位乍一看,可能会觉得是用Socket编写的聊天程序。告诉你吧,这玩意儿不是用Socket实现,呵呵,当然它的底层肯定与Socket有一定关系,我只说我的代码没有用到socket而已。

那么,除了Socket可以用于通信,还有其他技术吗?有啊,首先,如果你足够强大,用HTTP也行,但HTTP初始化的过程貌似比较慢。那么还有吗?当然了,各位还记得.NET以前有一个很X但又很少被关注的技术——Remoting。用过吧?没用过也没关系,因为它已经有替代品了。

这时候大家肯定想到WCF不是一盏“省油”的灯,其实不然,对比于用Socket要编写的代码数量和维护成本,用WCF编写网络通信程序,不仅省油,而且省时省力,最重要的是省心。所以,人的健康,心理健康是占主导的,你看一个心理不健康的人,其身体也不会健康到哪里去,今天这病明天那病。

因而,编程这事啊,越省心越好,有利于我们的健康,赚钱永远不是目的,身心健康才是活在这个世界上的主旋律,至于你信不信,反正我深信不疑。

 

我就这个WCF版的聊天程序的大致思路说一说。

这个程序既可以充当服务器端也同时作为客户端,每个应用实例都扮演着双重角色。这里我不需要引用服务。首先看服务协定和服务类。

using System;
using System.ServiceModel;

namespace ServiceDef
{
[ServiceContract]
public interface IService
{
[OperationContract(IsOneWay = true)]
void SendMessage(string msg);
}

/// <summary>
/// 服务
/// </summary>
public class MyChatService : IService
{
/// <summary>
/// 收到消息后引发的事件
/// </summary>
public event EventHandler<MessageReceiveEventArgs> MessageGot;
public MyChatService()
{
MessageGot += new EventHandler<MessageReceiveEventArgs>(TestApp.Form1.GetMessageCallBack);
}
public void SendMessage(string msg)
{
if (MessageGot != null)
{
MessageGot(this, new MessageReceiveEventArgs(msg));
}
}
}

/// <summary>
/// 收到消息后引发事件的参数
/// </summary>
public class MessageReceiveEventArgs : EventArgs
{
private string m_Message = string.Empty;
public MessageReceiveEventArgs(string message)
{
this.m_Message = message;
}

public string MessageText
{
get { return this.m_Message; }
}
}

}

服务协定没什么好看的了,相信大家都会写,这里的服务类与以往的有些不同,大家看到,里面定义了一个事件。那么,为什么要这样做呢?为什么要在服务方法被调用时引发这个事件呢?

想一想,我们以上代码是与UI分离的,也就是说,与UI分离是一种很好的编程方法,这样在修改维护时不会搞得乱七八糟。但是,我们都知道世间万物皆为阴阳所生,所以才有太极生两仪,两仪生四象,四象成八卦。而阴与阳是统一的,阴中有阳,阳在有阴。

我们的应用程序的UI就是阳,而业务逻辑就是阴,所以编程就是这么简单——阴阳互动。为了完成阴中有阳的功能,我们要想办法让这些代码与窗口上的控件互动,当然方法很多,也相当灵活。使用事件是比较好的。

于是,在服务类中定义一个事件,而事件的处理方法在主窗口类中定义,这样一来,阳与阴之间就有了一个可以相通的渠道。

为了使用访问方便,在窗口类中定义的处理事件的方法使用静态方法,静态方法的好处在于,它不基于对象,而是基于类的,你去到哪里都可以访问,它是全球化的。

这时候有人会问了,静态方法不能访问类对象的成员,那么这个静态方法又如何与窗体上的控件互动呢?技巧都是拿来用的。有了静态方法,难道我不能在窗口类中定义一个保存当前类实例的静态变量吗?

比如,我这个窗口的类名为FormMain,我只要在FormMain里面定义一个static FormMain CurrentForm = null;就完事了,这样不就可以在静态方法中访问了吗?

只要在FormMain的构造函数中赋值就行了,CurrentForm = this;

 

比如本例的代码:

#region 静态成员
static Form1 CurrentInstance = null;
public static void GetMessageCallBack(object sender, ServiceDef.MessageReceiveEventArgs e)
{
if (CurrentInstance != null)
{
CurrentInstance.AddMessageToListBox(e.MessageText);
}
}
#endregion

public Form1()
{

InitializeComponent();
CurrentInstance = this;
…………

你看,这就成了。

然后当然是定义服务器了,这里我们只有一个终结点,就是上面的IService,所以不用基址了,因为我们也不需要引用服务,直接利用ChannelFactory就行了。

#region 与服操作有关
ServiceHost host = null;

/// <summary>
/// 启动服务
/// </summary>
/// <param name="port">监听端口</param>
void OpenService(int port)
{
host = new ServiceHost(typeof(ServiceDef.MyChatService));
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.None;
host.AddServiceEndpoint(typeof(ServiceDef.IService), binding, "net.tcp://" + Dns.GetHostName() + ":"+ port.ToString() + "/chatsvc/");
host.Opened += host_Opened;
host.Closed += host_Closed;
try
{
host.Open();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}

void host_Closed(object sender, EventArgs e)
{
ShowMessage("服务已关闭。");
}

void host_Opened(object sender, EventArgs e)
{
ShowMessage("服务已启动。");
}

/// <summary>
/// 关闭服务
/// </summary>
void CloseService()
{
if (host != null)
{
host.Close();
host.Opened -= host_Opened;
host.Closed -= host_Closed;
}
}

#endregion

 

好了,接下来就是发送消息,其实就是调用服务方法IService.SendMessage,这里我们只用ChannelFactory<TChannel>工厂来生产一个IService通道,而后直接调用就可以了,就不必引用服务,也不用生成什么WSDL文件,也不考虑SOAP版本了。

// 发送消息,即调用服务
NetTcpBinding binding =new NetTcpBinding();
binding.Security.Mode = SecurityMode.None;

try
{
ServiceDef.IService ep = ChannelFactory<ServiceDef.IService>.CreateChannel(binding,new EndpointAddress("net.tcp://" + txtSvrHostName.Text + ":" + rmPort.ToString() + "/chatsvc/"));
ep.SendMessage(txtSendMessage.Text);
txtSendMessage.Clear();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}


哈哈,是不是很简单,而且,你也可以想到,如果用WCF来做文件传输,比如PC与手机上的文件传送,是不是很方便呢?也不必担心TCP粘包问题。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转 IT黄老邪

posted @ 2013-06-05 16:23  洗碗心得  阅读(946)  评论(0编辑  收藏  举报