65.9K
CodeProject 正在变化。 阅读更多。
Home

WCF 双工、短信、Web 服务器和 Windows 客户端(以及其他一些东西)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (5投票s)

2009 年 6 月 10 日

CPOL

11分钟阅读

viewsIcon

33812

downloadIcon

884

从 Windows 客户端发送和接收文本消息

引言

此示例演示了 Windows 客户端和 Web 服务器之间的 WCF 双工通信,该服务器监听来自(免费)Zeep SMS 短信服务的 HTTP POST 请求。

换句话说,它是一个应用程序,允许 Windows 客户端向订户的手机发送短信,并接收订户的手机发送的短信。

六大主要功能

  1. mobile_settings 网站/页面用于订阅新用户,整个过程由此开始。
  2. SMSClient 向已订阅该服务的用户发送和接收短信。
  3. SMSWebServer 作为 Zeep 在新用户注册或现有用户从手机发送短信时发送 HTTP POST 请求的回调 URL。
  4. MessageService 将来自 SMSWebServer 的订户短信发送到 SMSClient
  5. SMSService 用于通过 Zeep 将短信从 Windows SMSClient 发送给订户。
  6. ZeepmobileSMS.dll 将消息发布到 Zeep,并由 SMSService 使用。

入门

要开始,您需要做的第一件事是访问 Zeepmobile.com 并获取一个帐户。在那里,创建一个测试手机。Zeep 还会分配给您一个 api_key 和一个密钥。您需要将 api_key 附加到新用户订阅过程中提交给 Zeep 的 URL。在此示例提供的 MobileSettings 项目中的 default.aspx.cs 页面中查找该 URL。用您的 api_key 替换 x。

您需要将分配给您的 api_key密钥附加到此示例提供的 ZeepMobileSMS 项目中的名为 ZeepMobile.cs 的源文件中的 public static string 常量。

在创建 Zeep 帐户时,您需要想出一个短信前缀。当您发送短信时,无论是从您的手机还是从 Zeep 测试手机发送,都需要将短信前缀作为消息的第一行输入。所有订户的消息都发送到 88147。Zeep 使用短信前缀来确定消息的发送位置。

当您第一次使用 Zeep 测试手机发送消息时,您将无法向下移动到下一行,因此只需输入前缀、空格,然后是您的消息。另外,您有时需要重新加载测试手机才能看到从 Windows 客户端发送的消息。只需右键单击测试手机并选择重新加载。还有一点:我无法使用 Internet Explorer 查看我的 Zeep 测试手机。当我单击 Zeep 的查看测试手机链接时,Internet Explorer 会引发错误,因此我在浏览他们的网站时使用的是 Firefox。

将 2 个 WCF Web 服务和 Zeep 注册网站(mobile_settings)发布到您的 Web 服务器。在此之前,您需要将任何对“my.domain.name”的引用更改为您自己的域名。

  1. 转到 MessageServiceweb.config 文件,更改终结点地址(和 dns 属性),使其指向您的域。
  2. 转到 MessageService 项目中的 IMessage.cs 文件,并将行“Namespace = http://your.domain.name”更改为您正在使用的任何域名。
  3. 然后,转到 SMSClientSMSWebServer 的服务引用并更新服务引用。检查两者的 app.config 文件,以确保 servicePrincipleName 的终结点标识存在,并且绑定中的 clientBaseAddress 也存在。

我的基础设施

infrastructure.jpg

Windows 客户端

windowsclient.jpg

Windows 客户端使用 basicHttpBinding 发送短信给订户,并使用 wsDualHttpBinding 接收来自订户的消息。

我在这里发现最重要的事情是,没有...

clientBaseAddress=https://:8000/

... 属性在 wsDualHttpBinding 和...

serviceprincipalname value="SMSMessageService"

... 属性在终结点/标识上,它将不起作用。根据我的测试,servicePrincipalName 的值是什么似乎并不重要,只要它有东西就行。对于 clientBaseAddress 之后的内容(如果有的话)在 :8000/ 之后也是如此。

SMSService

SMSService 负责接收来自客户端的消息并将其转发给 Zeep(Zeep 再将其转发给您的订户手机)。

Client --> SMSService --> Zeep --> Subscriber

MessageService

MessageService 将来自订户手机的短信推送到 Windows 客户端。

Subscriber --> Zeep --> SMSWebServer --> MessageService --> Client

MessageService 的一个很酷的功能是它包含一个 ServiceHost 和一个 ServiceHostFactoryServiceHostFactory 能够监视 serviceHost_Closing 事件。这发生在 IIS 关闭时。当该事件触发时,它会向所有客户端发送一条消息,通知它们 IIS 已关闭,并且客户端需要关闭。但是,SMSWebServer 不会随 IIS 一起关闭。它会继续监视来自订户的入站短信,但当 IIS 关闭时,它会通知它们没有在线客户端可以接收它们的短信。

SMSWebServer

SMSWebServer 是一个独立的 Web 服务器,即不托管在 IIS 中,它充当 Zeep 发送 HTTP 消息的回调 URL。因此,当您设置 Zeep 帐户时,您需要更新 Zeep 回调 URL 为您的地址。

例如:(http://your.domain.name:port)。

您需要转到 SMSWebServer.cs 文件,更新 _server.Start(9999) 这一行,并将 9 替换为您希望服务器监听的端口。

我构建 SMSWebServer 作为独立服务器有几个原因。我第一次尝试构建 Zeep 回调是使用 *.aspx 页面,但不知何故,Zeep 似乎不喜欢它。即使我最终弄清楚如何剥离 IIS 通常发送的所有不必要的响应头(Zeep 不想要返回),不知何故 Zeep 仍然会引发错误。我在 Zeep 消息板上对此有一个疑问,但截至本文撰写之时,我还没有得到答案,所以我继续使用此解决方案。还应指出的是,截至本文撰写之时,Zeep 仍处于测试阶段,因此我不会向他们施压要求答案。

我还想提供一个关于如何在static事件中更新 UI 的示例。所以我将 XF.Server 嵌入到了 Windows Form 中,并在表单上放了一个文本框。每当有新消息到来或发生错误时,我都会从static事件中更新文本框。我也喜欢它不是一个控制台应用程序,而且我编写它的目的是当它最小化时,它会缩小到工具栏的通知区域。Windows Form(而不是控制台应用程序)在关闭和退出时提供了更多对事件的控制。

SMSWebServer 每五分钟通过 MessageService 发送“保持活动”消息给客户端,以防止客户端超时。但是,客户端不发送“保持活动”消息。这让我想起了轮询,而这是我试图避免的。

您可能会注意到使用了 AsyncObject。我实际上并没有用它来做任何其他事情,只是作为一个(如果您愿意的话)“占位符”来存放 InstanceContext 实现对象。在 static OnReadComplete 异步事件内部,我需要一些东西来将消息发送给客户端。

您可能熟悉以下内容

_messageService = new MessageServiceClient(new InstanceContext(this), 
					"WSDualHttpBinding_MessageService");

当您在private事件中使用它时,这效果很好,但在static事件中,“this”上下文不可用。

所以我所做的是一次像这样在static事件外部定义Async对象

internal static AsyncObject _ao = new AsyncObject();

internal static InstanceContext _instanceContext = new InstanceContext(_ao);

... 然后像这样在static OnReadComplete事件中使用它

MessageServiceClient messageService = null;

messageService = new MessageServiceClient
		(_instanceContext, "WSDualHttpBinding_MessageService");

订阅新订户和发送消息

在配置、设置和编译好所有内容后,您需要订阅一个新订户。首先启动 SMSWebServer(如果提示,请取消阻止端口)并启动 SMSClient。然后,在浏览器中导航到 mobile_setting 网站内的 default.aspx 页面。

这个简单的网页看起来像这样

subscribe.jpg

此页面嵌入了 Zeep 的 IFRAME。它会检查数据库以确保您正在订阅的用户和电话号码尚未注册。我还没有在此示例中包含更新数据库的代码,但想法是,每当新订户订阅您的服务时,都应将他的/她的用户 ID 和电话号码更新到数据库。我没有完成这部分的原因是,目前 Zeep 不会发送“STOP”事件(当订户取消订阅您的服务时会发生),因此没有办法从数据库中删除订户。Zeep 说他们正在努力。

输入您想使用的用户 ID。我建议使用 **testuser**,因为这是我在 Windows 客户端中默认的用户 ID。现在,只需在电话号码文本框中输入任何遵循格式的数字。999-999-9999 就可以。原因是我们将暂时使用 Zeep 的测试手机。

在通过用户 ID / 电话号码编辑后,系统会提示您(重新)输入电话号码。

出于测试目的,单击下拉框并选择 +test 选项(它在列表的底部)。然后输入您的测试手机号码。它应该看起来像这样

zeepconfirm.jpg

单击确认号码按钮。一旦 Zeep 确认您已正确订阅,请导航到您在 Zeep 中创建的测试手机。应该有一条消息等待您回复。在手机中输入 YES,然后按 Enter。

如果一切正常,您应该会看到欢迎消息。如果没有,则会看到通信错误 100 消息,这意味着 Zeep 无法联系到 SMSWebServer

希望一切顺利,您现在应该能够发送消息了。在测试手机中输入您的短信前缀,然后输入您的消息。按 Enter,您应该会在服务器文本框和客户端中看到您的消息。

在 Windows 客户端中输入一条消息,然后单击“发送”按钮。您应该会在测试手机中看到您的消息。如果没有,请右键单击手机并选择重新加载。

如果您在客户端运行时关闭 IIS,系统将提示您关闭客户端。

杂项

Zeep 包含“广播”消息给所有订户的功能,但我没有在此示例中包含该功能。

赞誉

我需要特别感谢几个人。

  • Jeff Barnes,他编写了 WCF: Duplex Operations and UI Threads Beer Party 应用。谢谢 Jeff。
  • Artur Sharipov 和 KODART 的团队。Artur 撰写了有关 XF.Server 的文章。感谢他们。
  • Sushant Bhatia。Sushant 负责发布消息到 Zeep 的部分。他的博客 在这里。谢谢 Sushant。
  • Sacha Barber 编写了 .NET 中线程的初学者指南,可以在 这里找到。谢谢 Sacha。
  • Sam Allen 和 Lion Shi。Sam 和 Lion 编写了我正在使用的这个非常酷的进程检查器,以确保 SMSClientSMSWebServer 只能启动一个实例。Sam 的网站是 http://dotnetperls.com/。谢谢 Sam 和 Allen。
  • Sijin 编写了 MessageBoxExLib 文章。虽然我没有将他的解决方案用于其功能的任何接近之处,但我非常喜欢它,所以就想在此包含它。谢谢 Sijin。
  • 最后,我想感谢本网站的众多用户和发帖者,以及网上那些乐于分享他们想法和见解的许多优秀的发布者。

修订

我真的很想去掉保持客户端活动的计时器。所以我又搜索了一下,在网站上找到了一个关于无限期保持双工绑定连接的评论。该评论建议在绑定中设置 receiveTimeout="infinite",并在客户端和服务器的 app.configweb.config 文件中的 reliableSession 上设置 inactivityTimeout="infinite"

据我所知,这似乎有效。

但是,一旦我进行了这些更改,然后尝试更新服务引用,配置就没有传输过来。

问题在于服务器上的 reliableSession inactivityTimeout="infinite"

将其从绑定中删除,服务引用就会更新。放回去它们就不会更新。

所以,我没有更改代码。我想这个决定最好留给您。如果您想去掉计时器,只需注释掉 SMSWebServer.cs 文件中的计时器代码,并在 MessageServiceWeb.config 中添加以下内容

<bindings>
   <wsDualHttpBinding>
      <binding name="WSDualHttpBinding_MessageService"bypassProxyOnLocal="false"
			useDefaultWebProxy="true"receiveTimeout="infinite">
         <reliableSession ordered="true" inactivityTimeout="infinite" />
      </binding>
   </wsDualHttpBinding>
</bindings>

然后将 SMSClient app.config 文件中的 receiveTimeoutinactivityTimeout 设置为“infinite”。

© . All rights reserved.