• 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下载最新版本

  • 相关阅读:
    FZU-Problem 2150 Fire Game
    LeetCode120——Triangle
    Coder-Strike 2014
    AP INVOICES IMPORT API(NOT request)
    NYOJ-277-车牌号
    软件測试方法
    C++中字符数组和字符串string
    【机器学习算法-python实现】PCA 主成分分析、降维
    主题讲座:移动互联网时代的创业机会
    ubuntu环境eclipse配置
  • 原文地址:https://www.cnblogs.com/wpfworld/p/3409402.html
Copyright © 2020-2023  润新知