WCF 双工、短信、Web 服务器和 Windows 客户端(以及其他一些东西)
从 Windows 客户端发送和接收文本消息
引言
此示例演示了 Windows 客户端和 Web 服务器之间的 WCF 双工通信,该服务器监听来自(免费)Zeep SMS 短信服务的 HTTP POST 请求。
换句话说,它是一个应用程序,允许 Windows 客户端向订户的手机发送短信,并接收订户的手机发送的短信。
六大主要功能
mobile_settings
网站/页面用于订阅新用户,整个过程由此开始。SMSClient
向已订阅该服务的用户发送和接收短信。SMSWebServer
作为 Zeep 在新用户注册或现有用户从手机发送短信时发送 HTTP POST 请求的回调 URL。MessageService
将来自SMSWebServer
的订户短信发送到SMSClient
。SMSService
用于通过 Zeep 将短信从 WindowsSMSClient
发送给订户。- 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
”的引用更改为您自己的域名。
- 转到
MessageService
的 web.config 文件,更改终结点地址(和 dns 属性),使其指向您的域。 - 转到
MessageService
项目中的 IMessage.cs 文件,并将行“Namespace = http://your.domain.name
”更改为您正在使用的任何域名。 - 然后,转到
SMSClient
和SMSWebServer
的服务引用并更新服务引用。检查两者的 app.config 文件,以确保servicePrincipleName
的终结点标识存在,并且绑定中的clientBaseAddress
也存在。
我的基础设施

Windows 客户端

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
和一个 ServiceHostFactory
。ServiceHostFactory
能够监视 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 页面。
这个简单的网页看起来像这样

此页面嵌入了 Zeep 的 IFRAME。它会检查数据库以确保您正在订阅的用户和电话号码尚未注册。我还没有在此示例中包含更新数据库的代码,但想法是,每当新订户订阅您的服务时,都应将他的/她的用户 ID 和电话号码更新到数据库。我没有完成这部分的原因是,目前 Zeep 不会发送“STOP
”事件(当订户取消订阅您的服务时会发生),因此没有办法从数据库中删除订户。Zeep 说他们正在努力。
输入您想使用的用户 ID。我建议使用 **testuser**,因为这是我在 Windows 客户端中默认的用户 ID。现在,只需在电话号码文本框中输入任何遵循格式的数字。999-999-9999 就可以。原因是我们将暂时使用 Zeep 的测试手机。
在通过用户 ID / 电话号码编辑后,系统会提示您(重新)输入电话号码。
出于测试目的,单击下拉框并选择 +test 选项(它在列表的底部)。然后输入您的测试手机号码。它应该看起来像这样

单击确认号码按钮。一旦 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 编写了我正在使用的这个非常酷的进程检查器,以确保
SMSClient
和SMSWebServer
只能启动一个实例。Sam 的网站是 http://dotnetperls.com/。谢谢 Sam 和 Allen。 - Sijin 编写了 MessageBoxExLib 文章。虽然我没有将他的解决方案用于其功能的任何接近之处,但我非常喜欢它,所以就想在此包含它。谢谢 Sijin。
- 最后,我想感谢本网站的众多用户和发帖者,以及网上那些乐于分享他们想法和见解的许多优秀的发布者。
修订
我真的很想去掉保持客户端活动的计时器。所以我又搜索了一下,在网站上找到了一个关于无限期保持双工绑定连接的评论。该评论建议在绑定中设置 receiveTimeout="infinite"
,并在客户端和服务器的 app.config 和 web.config 文件中的 reliableSession
上设置 inactivityTimeout="infinite"
。
据我所知,这似乎有效。
但是,一旦我进行了这些更改,然后尝试更新服务引用,配置就没有传输过来。
问题在于服务器上的 reliableSession inactivityTimeout="infinite"
。
将其从绑定中删除,服务引用就会更新。放回去它们就不会更新。
所以,我没有更改代码。我想这个决定最好留给您。如果您想去掉计时器,只需注释掉 SMSWebServer.cs 文件中的计时器代码,并在 MessageService
的 Web.config 中添加以下内容。
<bindings>
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_MessageService"bypassProxyOnLocal="false"
useDefaultWebProxy="true"receiveTimeout="infinite">
<reliableSession ordered="true" inactivityTimeout="infinite" />
</binding>
</wsDualHttpBinding>
</bindings>
然后将 SMSClient app.config 文件中的 receiveTimeout
和 inactivityTimeout
设置为“infinite”。