图片 38

上节说到:

前四节,我们讲了通讯基础,从这节起,我们回归到项目中来,要将前面的WCF通讯知识应用进来。

关于双向通讯,官方提供了N种可以双向的,不过今天要用到的,

之前的项目大伙没丢把,重新发下载地址:之前第3阶段代码:点此下载

是pollingDuplexHttpBinding,一个扩展的轮询机制的双向通讯,当你也可以尝试用上面的通讯方式去试一试。

 

既然是扩展,就说明默认没有,那我们首先就要添加扩展了,用的是默认官方是提供的DLL,就在我们安装的Silverlight4的目录里:

我们为Chess项目的解决方案里,再添加WCF应用服务程序

正常路径为:C:\Program Files\Microsoft
SDKs\Silverlight\v4.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll

由于Silverlight+WCF 新手实例 象棋
WCF通讯跨域(十五)
已截图,所以这里不截图了。

 

步骤:

这一节我们来实现PollingDuplexHttpBinding,轮询机制的双向通讯。

1。对着解决方案-》右键-》添加新建项目-》选择WCF应用服务程序-》输入:GameService

 

2。删除默认的IService1.cs和IService1.svc

以下开始内容不上图片,参考Silverlight+WCF 新手实例 象棋
WCF通讯跨域(十五)

3。新建WCF服务,起名为IService

我们再开一个项目来讲解,有了Hellow,有了World,这节就叫HellowWorld

4。把跨域文件clientaccesspolicy.xml复制到项目中去.

文件—》新建->项目-》Silverlight应用程序-》起名叫:HellowWorld

5。设置GameService项目属性的特定启动端口为:8686

确定后还是:HellowWorld和HellowWorld.web应用程序,两个项目

6。添加引用PollingDuplex.dll

 

7。修改配置文件,参考十五小节

我们对着解决方案右键,添加新建项目:建立WCF
服务应用程序->输入名称为:HellowWorldService

上面步骤完成了后

接着我们把默认的Service1.cs和Service1.svc删除:

我们接下来要添加几个新文件:

删除后,我们新建一个新的服务,叫Service.svc

1.新添加一个回调接口:ICallBack

我们提前修改下服务的端口号为12321,这样添加服务引用后,不用再改配置文件的端口。

对着GameService项目右键-》添加-》新建项->选择接口->输入:ICallBack.cs

OK,这时项目情况如下:

2.添加文件夹,用于存放通讯实体[契约数据]

图片 1

对着GameService项目右键-》添加-》新建文件夹-》输入:DataContract

接下来我们要为项目添加DLL,对着项目引用右键-》添加引用:

3.在通讯实体文件夹下,我们添加一个玩家Player实体用于通讯传递。

图片 2

对着DataContract文件夹右键-》添加-》类-》输入:Player.cs

选择浏览,并定位到:C:\Program Files\Microsoft
SDKs\Silverlight\v4.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll

目前的项目结构图如下:

图片 3

图片 4

回车确定,添加引用完后,我们需要修改下服务的配置文件“Web.config”

我们去掉Player的名称空间[.DataContract],其实就是去掉文件夹的名称:

由于轮询为扩展的,所以需要在配置文件里添加两个节点:

using System.Runtime.Serialization;
namespace GameService
{
    /// <summary>
    /// 游戏玩家 by 路过秋天
    /// </summary>
    [DataContract]
    public class Player
    {
       
    }
}

 

 

图片 5图片 6HellowWorldService web.config

接着,我们为Player增加基本属性[以后随着应用会增加]:
ID:用户标识
NickName:昵称
CallBack:玩家的回调,我们把回调放到玩家里。

<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>

RoomID:玩家所在房间,没有的话默认为0;
下面我们实现属性代码:

  <system.web>
    <compilation debug=”true” targetFramework=”4.0″ />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!– 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 –>
          <serviceMetadata httpGetEnabled=”true”/>
          <!– 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 –>
          <serviceDebug includeExceptionDetailInFaults=”false”/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
      <!–这里是添加的开始–>
      <services>
          <service name=”HellowWorldService.Service” >
              <endpoint address=”” binding=”pollingDuplexHttpBinding” contract=”HellowWorldService.IService” />
              <endpoint address=”mex” binding=”mexHttpBinding” contract=”IMetadataExchange”/>
          </service>
      </services>
      <extensions>
          <bindingExtensions>
              <add name=”pollingDuplexHttpBinding” type=”System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″/>
          </bindingExtensions>
      </extensions>
      <!–这里是添加的结束–>
    <serviceHostingEnvironment multipleSiteBindingsEnabled=”true” />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests=”true”/>
  </system.webServer>
  
</configuration>

 

 

图片 7图片 8

大伙照着把添加的开始和添加的结束那段Copy过去就完事了。

namespace GameService
{
    /// <summary>
    /// 游戏玩家 by 路过秋天
    /// </summary>
    [DataContract]
    public class Player
    {
        /// <summary>
        /// 玩家ID
        /// </summary>
        [DataMember]
        public Guid ID
        {
            get;
            set;
        }
        /// <summary>
        /// 玩家昵称
        /// </summary>
        [DataMember]
        public string NickName
        {
            get;
            set;
        }
        /// <summary>
        /// 玩家的回调
        /// </summary>
        [DataMember]
        internal ICallBack CallBack
        {
            get;
            set;
        }

OK,配置完事后,我们要写服务端代码了,双向通讯,但然少不了要调用客户端的代码了。

       /// <summary>
       /// 玩家所在房间号
       /// </summary>
       [DataMember]
       public int RoomID
       {
           get;
           set;
       }
    }

看们看下IService.cs文件

 

图片 9图片 10

用于传递的player完成了,现在我们写一下WCF通讯方法,我们为IService接口添加登陆和退出事件:

[ServiceContract(CallbackContract=typeof(ICallBack))]
    public interface IService
    {
        [OperationContract(IsOneWay = true)]
        void SayHellow(string name);
    }
    public interface ICallBack
    {
        [OperationContract(IsOneWay=true)]
        void ShowWorld(string worldName);
    }

图片 11图片 12

 

namespace GameService
{
    [ServiceContract(CallbackContract = typeof(ICallBack))]//头顶上这里写明了回调是ICallBack
    public interface IService
    {
        [OperationContract(IsOneWay = true)]
        void Login(Player player);//登陆

看,接口代码相当的少,就算少还是要说明一下的:

        [OperationContract(IsOneWay = true)]
        void Logout(Player player);//退出
        
    }
}

而且你敲属性的时候,是有智能提示的:

 

图片 13

就是说,客户端直接传递一个Player实体过来了,我们接着实现这个接口:

IService:服务端接口,当然就是客户端调用了。注意头顶上那个CallbackContract=typeof(ICallBack),这里指定了回调接口。

图片 14图片 15

ICallBack:回调接口,我新加的,是给客户端实现,然后服务端调用。这个名称你可以随便起,和typeof里的对应上就行了。

namespace GameService
{
    public class Service : IService
    {
        /// <summary>
        /// 玩家集合
        /// </summary>
        static Dictionary<int, Dictionary<Guid, Player>> playerList = new Dictionary<int, Dictionary<Guid, Player>>();
        #region IService 成员
        public void Login(Player player)
        {
            //待实现
        }

是不是发现多了一个(IsOneWay =
true)属性,什么意思?就是单向调用,不需要返回值。

        public void Logout(Player player)
        {
            //待实现
        }

所以官方推荐,如果你的函数类型返回值为void时,最好加上。

        #endregion
    }
}

 

 

接着我们要实现IService接口的方法了,那ICallBack要不要实现?当然不要,都说留给客户端实现了。

我这在里用了一个静态的全局变量,来保存所有的用户,简单解释一下这个双重的泛型字典集合

哦,那我们就实现IService接口方法去了:

Dictionary<int, Dictionary<Guid, Player>>
翻译一下就变成—-》Dictionary<房间号, 玩家列表>

图片 16图片 17

看到翻译明白了吧,所有的玩家都被分到房间里去了。然后所有的房间的玩家才构成一个大集合。

public class Service : IService
    {
        #region IService 成员
        public void SayHellow(string name)
        {
            name=”hellow”+name;
            ICallBack callBack = OperationContext.Current.GetCallbackChannel<ICallBack>();
            callBack.ShowWorld(name);
        }
        #endregion
    }

 

 

看明白了,现在来实现Login登陆了。

看到方法没有,有一句很长的代码,来获取ICallBack接口,然后调用了那个ShowWorld方法了。

根据以前我们注册一样,先判断用户在不在,在就删除,然后再添加用户。

这个代码记死也行:OperationContext.Current.GetCallbackChannel<ICallBack>();

图片 18图片 19

反正把ICallBack换成你自己的接口名称,就是了。然后就可以调用了。

 public void Login(Player player)
        {
            //待实现
            Player oldPlayer = FindPlayer(player.ID);
            if (oldPlayer != null)//用户已存在了
            {
                RemovePlayer(player);//删除用户
            }
            AddPlayer(player, 0);//添加用户
        }

话说ICallBack方法是留给客户端实现的,我们服务端这里先调用着先,反正你得按接口实现,按接口办事,放心的很。

 

 

所以这里我们还要补上三个方法:

那三行代码总来来说就是:

FindPlayer:从全局里找用户

1。客户端调用服务端的SayHellow(传入“路过秋天”);

RemovePlayer:从全局里移除用户

2。服务端收到调用,自然会知道对方从哪条路上来的,所以能够GetCallbackChannel了。

AddPlayer:从全局里添加一个用户

3。接约定好的接口,我调用了你的ShowWorld方法,同时把加了“hellow:路过秋天“传过去。

图片 20图片 21

至此,服务端代码写完了。是不是相当相当的简单,只要理解好了。

static Player FindPlayer(Guid playerID)
        {
            foreach (KeyValuePair<int, Dictionary<Guid, Player>> item in playerList)
            {
                if (item.Value.ContainsKey(playerID))
                {
                    return item.Value[playerID];
                }
            }
            return null;
        }
        static void RemovePlayer(Player player)
        {
            playerList[player.RoomID].Remove(player.ID);
        }
        static void AddPlayer(Player player, int roomID)
        {
            player.RoomID = roomID;
            //注册回调
            player.CallBack = OperationContext.Current.GetCallbackChannel<ICallBack>();
            Dictionary<Guid, Player> players;
            if (playerList.ContainsKey(roomID))//房间已存在
            {
                players = playerList[roomID];//房间所有用户

不过服务端还是有点事,什么事?加那个跨域文件啊,谁让你独立一个服务出来。

                if (!players.ContainsKey(player.ID))
                {
                    players.Add(player.ID, player);
                }
            }
            else
            {
                players = new Dictionary<Guid, Player>();
                players.Add(player.ID, player);
                playerList.Add(roomID, players);
            }
        }

加就加了,还是新建一个:clientaccesspolicy.xml文件,内容为:

 

图片 22图片 23

全面两个函数都短一点,后面添加就长一点了。

<?xml version=”1.0″ encoding=”utf-8″?>
<access-policy>
    <cross-domain-access>
        <policy>
            <allow-from http-request-headers=”*”>
                <domain uri=”*”/>
            </allow-from>
            <grant-to>
                <resource path=”/” include-subpaths=”true”/>
            </grant-to>
        </policy>
    </cross-domain-access>
</access-policy>

要判断房间是否存在,然后还要判断房间里是不是已有用户了,最后才添加。

 

用户登陆就到此了,那退出呢?

这下服务端事件就全搞完了,接下来看客户端的了。

退出就一行代码搞完了:

记得先添加服务引用-》发现->引用名称叫:HellowWorldService

 public void Logout(Player player)
        {
            //待实现
            RemovePlayer(player);//删除用户
        }

OK,接着我们还是要弄一个和上两次一样的界面,来调用,从上节那里Copy来xaml的代码,-_-..这界面重复三次了:

 

图片 24图片 25

OK,WCF的服务端就写完了,写完就要编绎下服务端代码,确保是正常通过的。

<Grid x:Name=”LayoutRoot” Background=”White”>
        <Button Content=”WCF 调用” Height=”23″ HorizontalAlignment=”Left” Margin=”84,111,0,0″ Name=”btnCallWCF” VerticalAlignment=”Top” Width=”75″ Click=”btnCallWCF_Click” />
        <TextBox Height=”23″ HorizontalAlignment=”Left” Margin=”84,71,0,0″ Name=”txtName” VerticalAlignment=”Top” Width=”120″ />
        <TextBlock Height=”23″ HorizontalAlignment=”Left” Margin=”228,71,0,0″ Name=”tbMsg” Text=”显示的内容” VerticalAlignment=”Top” />
    </Grid>

 

 

接着是客户端要调用开始了,这里先:

后台代码调用除了差不多也就是有一点小变化:

1。当然是添加服务引用了,并起名为GameService。

我们不是实例一个BasicHttp通道了,而是实例化一个PollingDuplex通道了。并设置了下每次轮询建立的有效时间为20分钟。

图片 26

图片 27图片 28

接着我们回到App.xaml.cs里,我们把GameService做成一个全局变量,在应用程序开始时实例化一次,以后调用就不用到New了:

 private void btnCallWCF_Click(object sender, RoutedEventArgs e)
        {
            PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding()
            {
                InactivityTimeout = TimeSpan.FromMinutes(20)
            };
            //Binding binding =new BasicHttpBinding();
            EndpointAddress endPoint = new EndpointAddress(“http://localhost:12321/Service.svc“);
            HellowWorldService.ServiceClient client = new HellowWorldService.ServiceClient(binding, endPoint);
            client.SayHellowAsync(txtName.Text);

图片 29图片 30

            client.ShowWorldReceived += new EventHandler<HellowWorldService.ShowWorldReceivedEventArgs>(client_ShowWorldReceived);
        }

Grid root = new Grid();
        public static GameService.ServiceClient client;//回调的客户端
        public static GameService.Player player;//当前玩家
        public App()
        {
            this.Startup += this.Application_Startup;
            this.Exit += this.Application_Exit;
            this.UnhandledException += this.Application_UnhandledException;

        void client_ShowWorldReceived(object sender, HellowWorldService.ShowWorldReceivedEventArgs e)
        {
            tbMsg.Text = e.worldName;
        }

            InitializeComponent();
            InitiallizeGlobalVar();
        }
        private void InitiallizeGlobalVar()
        {
            PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding()
            {
                InactivityTimeout = TimeSpan.FromMinutes(20)
            };
            EndpointAddress endPoint = new EndpointAddress(“http://localhost:8686/Service.svc“);
            client = new GameService.ServiceClient(binding, endPoint);
            player = new GameService.Player();
        }

 

 

客户端的接口实现是哪句啊?

这里有一点提一下:

看出来没,这两句就是那个ICallBack接口的实现了,当用户调用ShowWorld时候,就是tbMsg.Text=e.参数的时候了。

本人机子装了VS2005+VS2010

图片 31图片 32

新建的项目Silverlight是2.0的库,WCF服务应用程序是4.0的库。所以在引用DLL方面,有点小插曲。

 client.ShowWorldReceived += new EventHandler<HellowWorldService.ShowWorldReceivedEventArgs>(client_ShowWorldReceived);
        }

这不,WCF引用的轮询是4.0的,到Silverlight里,就只能引用2.0的。好在也能用着。

        void client_ShowWorldReceived(object sender, HellowWorldService.ShowWorldReceivedEventArgs e)
        {
            tbMsg.Text = e.worldName;
        }

顺便提一下,之所以不用wsDualHttpBinding双工通讯方式,就是因为Silverlight是2.0的,

 

因此引用不了4.0的wsDualHttpBinding库,所以没用它了。

一切就绪:F5运行,输入”路过秋天”

 

图片 33

现回到Login页面,简单修改下以前的代码:

回车调用,OK,结果出来了。

图片 34图片 35

图片 36

 private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            nickName = txtNickName.Text.Trim();
            if (nickName == “”)
            {
                MessageBox.Show(“请输入昵称!”);
                return;
            }
            if (nickName.Contains(“,”))
            {
                MessageBox.Show(“昵称不能包含非法字符!”);
                return;
            }
            btnLogin.IsEnabled = false;
            //下面这几句代码变了一下:
            App.player.ID = userID;
            App.player.NickName = nickName;
            App.client.LoginCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_LoginCompleted);
            App.client.LoginAsync(App.player);
           
        }
        void client_LoginCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {//设置Cookie
            System.Windows.Browser.HtmlPage.Document.Cookies = userID + “,” + nickName;
            ((App)(Application.Current)).RedirectTo(new Room());
        }

 

 

OK,WCF通讯基础到此就结束了,下节开始大干特干的应用于了。

好了按F5运行,

提供源码下载:点击下载

图片 37

 

点击登陆,正常转向房间

 

图片 38

admin

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注