现在我们继续进行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 > )
{
//移除出错的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();
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下载最新版本

InChatter系统之服务器开发(二)的更多相关文章

  1. InChatter系统之服务器开发(一)

    服务器端是整个消息系统的中枢,类似与人类的大脑.没有他,根本无法实现客户端之间的交流,为什么呢?这也涉及到我们的系统涉及,在服务器端,每个客户端的标识数据都会在服务器端进行保存,在这种情况下,当某一个 ...

  2. Python服务器开发二:Python网络基础

    Python服务器开发二:Python网络基础   网络由下往上分为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. HTTP是高层协议,而TCP/IP是个协议集,包过许多的子协议.包括: ...

  3. linux服务器开发二(系统编程)--线程相关

    线程概念 什么是线程 LWP:Light Weight Process,轻量级的进程,本质仍是进程(在Linux环境下). 进程:独立地址空间,拥有PCB. 线程:也有PCB,但没有独立的地址空间(共 ...

  4. linux服务器开发二(系统编程)--进程相关

    进程相关的概念 程序与进程 程序,是指编译好的二进制文件,在磁盘上,不占用系统资源(CPU.内存.打开的文件.设备.锁等等). 进程,是一个抽象的概念,与操作系统原理联系紧密.进程是活跃的程序,占用系 ...

  5. EasyDSS点播与直播服务器软件-二次开发接口对接说明示列

    EasyDSS流媒体服务器软件,提供一站式的转码.点播.直播.时移回放服务,极大地简化了开发和集成的工作.其中,点播版本主要包含:上传.转码.分发.直播版本,主要包含:直播.录像, 直播支持RTMP输 ...

  6. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器软件二次开发接口对接说明示列

    EasyDSS相关功能 EasyDSS流媒体服务器软件,提供一站式的转码.点播.直播.时移回放服务,极大地简化了开发和集成的工作.其中,点播版本主要包含:上传.转码.分发.直播版本主要包含:直播.录像 ...

  7. JAVA+PHP+阿里云组件纯手工实现POP、SMTP、IMAP开发邮件服务器(二)

    java开发邮件服务器的接收模块 用java建立socket服务端,监听端口25,实现SMTP协议.即可完成邮件服务器的接收模块. 这里要注意的是,SMTP协议其实可以分为两种.一种是你用手机.PC等 ...

  8. FastAPI(七十二)实战开发《在线课程学习系统》接口开发-- 留言列表开发

    之前我们分享了FastAPI(七十一)实战开发<在线课程学习系统>接口开发-- 查看留言,这次我们分享留言列表开发. 列表获取,也需要登录,根据登录用户来获取对应的留言.逻辑梳理如下. 1 ...

  9. C++服务器开发之笔记三

    为什么需要原子性操作? 我们考虑一个例子:(1)x++这个常见的运算符在内存中是怎样操作的?从内存中读x的值到寄存器中,对寄存器加1,再把新值写回x所处的内存地址 若是有两个线程同时对同一个变量++, ...

随机推荐

  1. 找不到或无法加载主类 ide 正常执行,但是打包jar后报错 maven 引入本地包

    错误: 找不到或无法加载主类 com.myali.TTSmy 问题原因: ide中编译能找到相关包,但是,打包成jar时,本地的jar引入失败 maven将系统用到的包从线上maven仓库下载到本地的 ...

  2. IsNumeric 判断字符串是否为数字(使用Val函数实现),这个函数相当于Java的IsNaN函数

    IsNumeric 判断字符串是否为数字,如果是数字返回true,如果包含有汉字或字符的话返回false. 由于Delphi本身没有IsNumeric这个函数,不像其它语言,这个函数相当于Java的I ...

  3. java jdbc/ojdbc 链接oracle的三种方式

    方法一:使用service_name 连接oracle  jdbc:oracle:thin:@//:/<service_name> 例如: jdbc:oracle:thin:@//10.1 ...

  4. mybatis批量操作数据

    批量查询语句: List<MoiraiProductResource> selectBatchInfo(List<Long> idList); <!-- 批量查询 --& ...

  5. BZOJ2761:不重复数字(splay效率对比)

    给出N个数,要求把其中重复的去掉,只保留第一次出现的数. 例如,给出的数为1 2 18 3 3 19 2 3 6 5 4,其中2和3有重复,去除后的结果为1 2 18 3 19 6 5 4.   In ...

  6. 【SDOI 2014】 旅行

    [题目链接] 点击打开链接 [算法] 树链剖分 每个宗教建一棵线段树,注意数据量大,要动态开点 [代码] #include<bits/stdc++.h> using namespace s ...

  7. P4844 LJJ爱数数 数论

    思路: 化简后得到(a+b)c=ab,设g=(a,b),A=a/g,B=b/g,则g(A+B)c=ABg^2,即(A+B)c=ABg 由题目已知条件:(a,b,c)=1,即(g,c)=1,g|(A+B ...

  8. 【196】Dell 移动工作站系统安装方法

    会出现找不到硬盘的情况,解决方法:安装系统的时候需要加载阵列卡驱动 下载阵列卡驱动,以 Dell T7610 为例根据安装系统进行选择,地址:http://zh.community.dell.com/ ...

  9. HDU 5907 Find Q (水题)

    题意:在他眼前有一个小写字母组成的字符串SSS,他想找出SSS的所有仅包含字母'q'的连续子串. 析:这个题,很容易发现,有 n 个连续个q,就是前 n 项和.注意不要超 int. 代码如下: #pr ...

  10. E20180306-hm-xa

    base  n. 基地; 基础; 根据; 基数(如十进制的10 和二进制的2) designate vt. 指派; 指明,指出; 表明,意味着; 把…定名为;