InChatter系统之服务器开发(二)

现在我们继续进行InChatter系统的服务器端的开发,今天我们将实现服务契约同时完成宿主程序的开发,今天结束之后服务器端将可以正常运行起来。

系统的开发是随着博客一起的,颇有点现场直播的感觉,所有在写博的过程中,可能会回头重新讲解和修复以前的设计不合理的地方,同时也可能会融合新的想法以及功能模块,提前跟各位看客交代下,请大家见谅。不过我想这个过程对大家也是有利的,在这个过程中,一是带大家重新回顾一下以前的设计想法并与现在进行比较,二是可以增长大家的项目设计的感觉,增长经验,这也是项目开发中不可避免的。所以,这也是我坚持直播的原因,如果文章中有什么不对的地方或者修改意见,欢迎大家指正,好的修改建议我会在开发过程中融入进来。

本人文笔较差,表达可能不够详尽,如果什么不节的地方,欢迎大家指正。

一、服务器端的开发

我们修改了服务契约,增加了一个GetOnlineClient的方法

[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
 string GetOnlineClient();

通过这个方法,我们可以获取除了自己以外其他的所有在线客户端,同时修改了Login方法的返回值,从而我们可以得到登录的反馈状态

[OperationContract(IsOneWay=false,IsInitiating=true,IsTerminating=false)]
 bool Login(string clientId);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using InChatter.Service.Data;

namespace InChatter.Service
{
    public class Chat : IChat
    {
        public static Dictionary<string, IChatCallback> Callbacks = new Dictionary<string, IChatCallback>();
        public bool Login(string clientId)
        {
            if (Callbacks.Keys.Contains(clientId))
            {
                return false;
            }
            IChatCallback callback;
            //告知其他用户,新用户上线
            BroadcastUserState(clientId, true);
            callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
            Callbacks.Add(clientId, callback);
            //返回在线客户端
            return true;
        }

        public string GetOnlineClient()
        {
            IChatCallback callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
            //获取除本客户端以外的其他在线客户端
            var result = Callbacks.Where(p => p.Value != callback).Select(p => p.Key).ToList();
            return string.Join(",", result);
        }

        public void SendMsg(Data.InChatterMessage message)
        {
            if (message == null)
            {
                return;
            }
            if (message.Type == "notice")
            {
                SendNotice(message);
            }
            else if (message.Type == "msg")
            {
                try
                {
                    Callbacks[message.ReceiverID].ReceiveMsg(message);
                }
                catch
                {
                    Logout(message.ReceiverID);
                }
            }
        }

        public void Logout(string clientId)
        {
            try
            {
                lock (Callbacks)
                {
                    //移除退出用户的callBacks
                    Callbacks.Remove(clientId);
                }
                //广播下线消息
                BroadcastUserState(clientId, false);
            }
            catch
            {

            }
        }

        /// <summary>
        /// 通知其他用户上线或者下线消息
        /// </summary>
        /// <param name="employeeId"></param>
        /// <param name="isLogin"></param>
        private void BroadcastUserState(string clientId, bool isLogin)
        {
            List<string> list = new List<string>();
            foreach (var item in Callbacks)
            {
                InChatterMessage txt = new InChatterMessage();
                txt.Content = "";
                txt.SenderID = clientId;
                txt.ReceiverID = item.Key;
                if (isLogin)
                {
                    txt.Type = "logon";
                }
                else
                {
                    txt.Type = "logoff";
                }
                txt.SendTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                try
                {
                    item.Value.ReceiveMsg(txt);
                }
                catch
                {
                    list.Add(item.Key);
                }
            }
            RemoveDisconnectCallBacks(list);
        }

        /// <summary>
        /// 发送通知消息,接受者为所有在线客户
        /// </summary>
        /// <param name="msg"></param>
        private void SendNotice(InChatterMessage msg)
        {
            List<string> list = new List<string>();
            foreach (var item in Callbacks)
            {
                try
                {
                    item.Value.ReceiveMsg(msg);
                }
                catch
                {
                    list.Add(item.Key);
                }
            }
            RemoveDisconnectCallBacks(list);
        }

        /// <summary>
        /// 移除断开连接客户端
        /// </summary>
        /// <param name="list"></param>
        private void RemoveDisconnectCallBacks(List<string> list)
        {
            if (list.Count > 0)
            {
                //移除出错的callBack
                foreach (var item in list)
                {
                    Callbacks.Remove(item);
                }
                //将连接通道出错的项认定为下线,发送下线消息给在线客户端
                foreach (var item in list)
                {
                    BroadcastUserState(item, false);
                }
            }
        }
    }
}

如果我们定义一个关于Chat类的一个构造函数,那么我们可以看到客户端与服务器建立回话时,都会调用构造函数初始化一个Chat的实例,也就是说每一个客户端与服务器端对应的一个Chat类交互。

在Chat类中我们定义一个静态的全局变量Callbacks,我们在该变量中存储客户端标识Id和对应的回调实体,这样便可以在所有的Chat类中操作我们需要的回调,来完成对指定客户端的操作。

二、宿主程序的实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;

namespace InChatter.Service.Host
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri baseUri = new Uri("http://localhost:1378/InChatter");
            using (ServiceHost host = new ServiceHost(typeof(Chat), baseUri))
            {
                NetTcpBinding binding = new NetTcpBinding();
                binding.Security.Mode = SecurityMode.None;
                //会话保持时间
                binding.ReceiveTimeout = TimeSpan.FromHours(2);
                host.AddServiceEndpoint(typeof(IChat), binding, "net.tcp://localhost:1121/InChatter");
                host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
                host.Opened += host_Opened;
                try
                {
                    host.Open();

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                Console.WriteLine("Press 'exit' to exit!");
                string enterStr = Console.ReadLine();
                while (enterStr.ToLower() != "exit")
                {
                    enterStr = Console.ReadLine();
                }
            }
        }

        public static void host_Opened(object sender, EventArgs e)
        {
            Console.WriteLine("Service Opened!");
        }
    }
}

启动运行,在程序没有错误的情况下,如果防火墙开启,则会有如下提示:

这个代表我们的程序没有错误,但是运行以后呢?

系统出错了,提示不具有命名空间访问权限,其实就是我们的程序需要以管理员身份运行。

OK,成功了!

这里我们通过代码实现了整个服务的寄宿,同时我们也可以使用配置文件来实现:

下面我们是WCF配置工具来实现客户端的配置:

1.打开WCF服务配置编辑器

2.找到服务契约生成的dll,点击打开

3.选中我们的服务实现类型

4.点击下一步,在类型中选择TCP

5.进入下一步,输入终结点地址,如下所示

5.点击下一步,并完成,保存配置后,将配置文件复制到宿主程序的目录下,覆盖默认的App.config

生成的配置文件如下:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="InChatter.Service.Chat">
        <endpoint address="net.tcp://localhost:1121/InChatter"
            binding="netTcpBinding" 
            bindingConfiguration="" contract="InChatter.Service.IChat" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

代码调用也比较简单:

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;

namespace InChatter.Service.Host
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(Chat)))
            {
                host.Opened += host_Opened;
                try
                {
                    host.Open();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                Console.ReadLine();
            }
        }

        public static void host_Opened(object sender, EventArgs e)
        {
            Console.WriteLine("Service Opened!");
        }
    }
}

同样需要以管理员方式启动程序!

但是目前的配置文件存在一定的问题,稍后在客户端处理的时候,我们再详细讲解~

服务端已经可以正常运行了,源码提供给大家:下载源码到CodePlex下载最新版本

posted @ 2013-11-05 22:07  Skysper  阅读(887)  评论(0编辑  收藏  举报