这次是一个在线聊天的插件,插件参考了MSDN中Duplex WCF服务的实现和网上一些聊天程序,基本可以实现用户登录和聊天,如果用户不存在就保存聊天数据到数据库,等用户下次登陆的时候读入。
这个是聊天时候的图例:
为什么不使用在客户单添加WCF服务的方法生成代理类然后进行编程序,首先是生成的代理类我没有发现可以实现WCF双向传输的方法,在网上也没有找到,所以就参照msdn和一些网上的代码,自己编写代理类进行操作:
1: 定义聊天时候传输聊天的数据实体,参照服务器端的聊天数据实体的定义,在客户端定义如下的实体:
[DebuggerStepThroughAttribute()]
[GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[DataContractAttribute(Name = "ChatMessage", Namespace = "http://schemas.datacontract.org/2004/07/")]
public partial class ChatMessage : object
{
private string DataField;
private string FromField;
private string ToField;
private int TypeField;
private byte[] ContentField;
[DataMemberAttribute()]
public string Data
{
get
{
return this.DataField;
}
set
{
if ((object.ReferenceEquals(this.DataField, value) != true))
{
this.DataField = value;
}
}
}
[DataMemberAttribute()]
public string From
{
get
{
return this.FromField;
}
set
{
if ((object.ReferenceEquals(this.FromField, value) != true))
{
this.FromField = value;
}
}
}
[DataMemberAttribute()]
public string To
{
get
{
return this.ToField;
}
set
{
if ((object.ReferenceEquals(this.ToField, value) != true))
{
this.ToField = value;
}
}
}
[DataMemberAttribute()]
public int Type
{
get
{
return this.TypeField;
}
set
{
if ((object.ReferenceEquals(this.TypeField, value) != true))
{
this.TypeField = value;
}
}
}
[DataMemberAttribute()]
public byte[] Content
{
get
{
return this.ContentField;
}
set
{
if ((object.ReferenceEquals(this.ContentField, value) != true))
{
this.ContentField = value;
}
}
}
这个实体类的定义其实是可以自动生成的。
2:把对WCF服务的访问方法单独处理成一个对象,模式和结构基本和MSDN上那篇文章一样:
public void Start()
{
// Instantiate the binding and set the time-outs
PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding()
{
ReceiveTimeout = TimeSpan.FromSeconds(45),
InactivityTimeout = TimeSpan.FromMinutes(1)
};
// Instantiate and open channel factory from binding
IChannelFactory<IDuplexSessionChannel> factory =
binding.BuildChannelFactory<IDuplexSessionChannel>(new BindingParameterCollection());
IAsyncResult factoryOpenResult =
factory.BeginOpen(new AsyncCallback(OnOpenCompleteFactory), factory);
if (factoryOpenResult.CompletedSynchronously)
{
CompleteOpenFactory(factoryOpenResult);
}
}
void OnOpenCompleteFactory(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteOpenFactory(result);
}
void CompleteOpenFactory(IAsyncResult result)
{
IChannelFactory<IDuplexSessionChannel> factory =
(IChannelFactory<IDuplexSessionChannel>)result.AsyncState;
factory.EndOpen(result);
// The factory is now open. Create and open a channel from the channel factory.
IDuplexSessionChannel channel =
factory.CreateChannel(new EndpointAddress(ServiceUrl));
IAsyncResult channelOpenResult =
channel.BeginOpen(new AsyncCallback(OnOpenCompleteChannel), channel);
if (channelOpenResult.CompletedSynchronously)
{
CompleteOpenChannel(channelOpenResult);
}
}
void OnOpenCompleteChannel(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteOpenChannel(result);
}
上边这段代码基本是固定模式,表示使用channel模式进行访问WCF服务,打开完成服务之后,开始进行第一次通信,就是发送信条信息,然后进入消息等待模式:
void CompleteOpenChannel(IAsyncResult result)
{
channel = (IDuplexSessionChannel)result.AsyncState;
channel.EndOpen(result);
ChatMessage chatMessage = new ChatMessage();
chatMessage.From = UserName;
chatMessage.To = "SystemServer";
chatMessage.Data = System.DateTime.Now.ToString();
// Channel is now open. Send message
Message message =
Message.CreateMessage(channel.GetProperty<MessageVersion>(),
"WindCloud/WSChatService/Heart", chatMessage);
IAsyncResult resultChannel =
channel.BeginSend(message, new AsyncCallback(OnSend), channel);
if (resultChannel.CompletedSynchronously)
{
CompleteOnSend(resultChannel);
}
//Start listening for callbacks from the service
ReceiveLoop(channel);
}
至此,客户端就会一直等待服务器发来消息,一旦接收到消息,就进入下边的消息处理过程:
void ReceiveLoop(IDuplexSessionChannel channel)
{
// Start listening for callbacks.
IAsyncResult result = channel.BeginReceive(new AsyncCallback(OnReceiveComplete), channel);
if (result.CompletedSynchronously) CompleteReceive(result);
}
void OnReceiveComplete(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteReceive(result);
}
void CompleteReceive(IAsyncResult result)
{
//A callback was received so process data
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
try
{
Message receivedMessage = channel.EndReceive(result);
// Show the service response in the UI.
if (receivedMessage != null)
{
ChatMessage text = receivedMessage.GetBody<ChatMessage>();
_UiThread.Post(Client.ProcessData, text);
}
ReceiveLoop(channel);
}
catch (CommunicationObjectFaultedException exp)
{
_UiThread.Post(delegate(object msg) { System.Windows.Browser.HtmlPage.Window.Alert(msg.ToString()); }, exp.Message);
}
}
上边的代码由于都是使用的异步模式,看起来还是比较多的,但是道理还不是很复杂。这些都是模式相对固定的代码,下边就是我们针对聊天需要的代码了:
告诉系统,加入到聊天室:
public void Join(string userName)
{
ChatMessage chatMessage = new ChatMessage();
chatMessage.From = userName;
chatMessage.To = "";
chatMessage.Data = "";
//
Message message = Message.CreateMessage(channel.GetProperty<MessageVersion>(), "WindCloud/WSChatService/Join", chatMessage);
IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
if (resultChannel.CompletedSynchronously)
{
CompleteOnSend(resultChannel);
}
}
最主要的一个方法,说话:
public void Say(ChatMessage chatMessage)
{
// The channel is now open. Send a message.
Message message = Message.CreateMessage(channel.GetProperty<MessageVersion>(), "WindCloud/WSChatService/Say", chatMessage);
try
{
IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
if (resultChannel.CompletedSynchronously)
{
CompleteOnSend(resultChannel);
}
}
catch (Exception ex)
{
//channel = factory.CreateChannel(new EndpointAddress("http://localhost/ChatServer/Service.svc"));
IAsyncResult channelOpenResult = channel.BeginOpen(new AsyncCallback(OnOpenCompleteChannel), channel);
if (channelOpenResult.CompletedSynchronously)
{
CompleteOpenChannel(channelOpenResult);
}
IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
if (resultChannel.CompletedSynchronously)
{
CompleteOnSend(resultChannel);
}
}
}
客户单服务重要的代码差不多就是这些,需要明白如何发送消息,如果进入消息接受模式。下面看看界面如何处理:
private void StartServer()
{
Uri uri = System.Windows.Browser.HtmlPage.Document.DocumentUri;
string host = uri.AbsoluteUri;
host = host.Substring(0, host.Length - uri.LocalPath.Length);
string servicePath = "/Services/WSChat.svc";
string serviceUri = host + servicePath;
chatSerrice = new ChatServer(this, serviceUri, App.GetLoginUser().UserName);
chatSerrice.Start();
chatSerrice.Join(App.GetLoginUser().UserName);
//
_Timer = new Timer(new TimerCallback(_Timer_Elapsed), null, 30000, Timeout.Infinite);
}
建立新的服务,然后定时发送心跳信息:
private void CreateChat(string friendName)
{
///
if (!chatUsers.ContainsKey(friendName))
{
UserChat userChat = new UserChat();
userChat.txtTitle.Text = "与 " + friendName + " 交谈中";
userChat.OnSend += (o, ev) =>
{
ev.From = App.GetLoginUser().UserName;
chatSerrice.Say(ev);
};
userChat.UserName = friendName;
userChat.OnCloseed += (o, ev) =>
{
chatUsers.Remove(ev);
for (int i = 0; i < canvasChat.Children.Count; i++)
{
if ((canvasChat.Children[i] as UserChat).UserName == ev)
{
canvasChat.Children.RemoveAt(i);
}
}
};
chatUsers.Add(friendName, userChat);
this.canvasChat.Children.Add(userChat);
}
}
针对每个不同的聊天对象,建立独立的窗口。
public void ProcessData(Object receivedData)
{
if (receivedData != null)
{
ChatMessage cm = receivedData as ChatMessage;
if (cm.Type > 0)
{
//聊天窗口是否包含
if (!chatUsers.ContainsKey(cm.From))
{
CreateChat(cm.From);
}
if (chatUsers.ContainsKey(cm.From))
{
string txt = "";
txt = cm.From + " 说:" + cm.Data + ""n"; ;
chatUsers[cm.From].txtChat.Text += txt;
}
}
}
}
接受到不同的信息之后,把对应的消息显示到不同的窗口上,免得混杂在一起:
至此,聊天的主要功能完成。