设为首页 收藏本站
查看: 940|回复: 0

Windows 8 Metro 关于StreamSocket MSDN示例阅读

[复制链接]

尚未签到

发表于 2015-5-23 06:31:41 | 显示全部楼层 |阅读模式
  话题开始前首先鄙视下自己,博客园5年从未发过任何博文。理由很多不泛于没足够的能力、没足够的信心、没时间、没精力等等。
  终于在失业且方向迷惘中写下第一篇博文。
  文章能力有限,还望看客您多多指点。
  废话到此进入正题。
  8月15日我装上了WIN8正式版(订阅版),VS2012(订阅版)旗舰版,Blend + SketchFlow Preview for Visual Studio 2012。开始尝试Windows 8 Metro APP 的开发。一开始需要注册开发者授权按步骤走下去就是。
  
  由于之前开发的Silverlight客户端连接服务器使用的是异步SOCKET连接(这个在园子里搜索下便有)。而 Metro应用与Silverlight的基本控件以及开发方式几乎一样,便有了把Silverlight的客户端移植到Metro应用中的想法。
  
  我是个比较直接的人,第一个想到的就是连接通信的问题。看了微软的MSDN关于Windows 8 Metro的几种网络连接方式,我针对的看了 StreamWebSocket 以及StreamSocket。 我翻查了微软的MSDN 关于Windows 8 Metro StreamSocket (基本是英文的中文资料甚少,个人觉得自己英文很烂),下载到了关于StreamSocket 的示例 下载地址 。里面的代码具体讲述了 如何创建一个StreamSocket连接的服务器监听 如何利用StreamSocket 连接上服务器 如何发送消息 接收显示消息。一切看上去都是这么的全面。经过详细的阅读代码,我发现Windows 8 Metro StreamSocket 的发送接收都与之前的异步连接方式有相当大的区别。示例内用到了await/async 关键字(关于这个已经有很多博文解释了),对于两个关键字的理解是必要的。
  
  现在让我们来解读下示例内的代码:(忽略App.xaml代码)
  Scenario1.xaml.cs 情景1
  这个情景片段(是这样翻译吧?英文太烂)的主要代码:
  点击按钮开始创建一个服务器监听。



1 ///
2         /// This is the click handler for the 'StartListener' button.
3         ///
4         /// Object for which the event was generated.
5         /// Event's parameters.
6         private async void StartListener_Click(object sender, RoutedEventArgs e)
7         {
8             // Overriding the listener here is safe as it will be deleted once all references to it are gone. However, in many cases this
9             // is a dangerous pattern to override data semi-randomly (each time user clicked the button) so we block it here.
10             if (CoreApplication.Properties.ContainsKey("listener"))
11             {
12                 rootPage.NotifyUser("This step has already been executed. Please move to the next one.", NotifyType.ErrorMessage);
13                 return;
14             }
15
16             if (String.IsNullOrEmpty(ServiceNameForListener.Text))
17             {
18                 rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage);
19                 return;
20             }
21
22             StreamSocketListener listener = new StreamSocketListener();
23             listener.ConnectionReceived += OnConnection;
24
25             // Save the socket, so subsequent steps can use it.
26             CoreApplication.Properties.Add("listener", listener);
27
28             // Start listen operation.
29             try
30             {
31                 await listener.BindServiceNameAsync(ServiceNameForListener.Text);
32                 rootPage.NotifyUser("Listening", NotifyType.StatusMessage);
33             }
34             catch (Exception exception)
35             {
36                 CoreApplication.Properties.Remove("listener");
37                 
38                 // If this is an unknown status it means that the error is fatal and retry will likely fail.
39                 if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
40                 {
41                     throw;
42                 }
43
44                 rootPage.NotifyUser("Start listening failed with error: " + exception.Message, NotifyType.ErrorMessage);
45             }
46         }
  我们可以看到 StreamSocketListener 创建监听是一件相当容易的事情。
  其中代码 await listener.BindServiceNameAsync(ServiceNameForListener.Text);
  listener.BindServiceNameAsync(端口号)为异步开启某端口监听代码。
  
  listener.ConnectionReceived += OnConnection; 这个就类似于我们曾经写的异步SOCKET 数据过来时触发的委托事件。
  
  OnConnection 代码:



1 ///
2         /// Invoked once a connection is accepted by StreamSocketListener.
3         ///
4         /// The listener that accepted the connection.
5         /// Parameters associated with the accepted connection.
6         private async void OnConnection(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
7         {
8             DataReader reader = new DataReader(args.Socket.InputStream);
9             try
10             {
11                 while (true)
12                 {
13                     // Read first 4 bytes (length of the subsequent string).
14                     uint sizeFieldCount = await reader.LoadAsync(sizeof(uint));
15                     if (sizeFieldCount != sizeof(uint))
16                     {
17                         // The underlying socket was closed before we were able to read the whole data.
18                         return;
19                     }
20
21                     // Read the string.
22                     uint stringLength = reader.ReadUInt32();
23                     uint actualStringLength = await reader.LoadAsync(stringLength);
24                     if (stringLength != actualStringLength)
25                     {
26                         // The underlying socket was closed before we were able to read the whole data.
27                         return;
28                     }
29
30                     // Display the string on the screen. The event is invoked on a non-UI thread, so we need to marshal the text back to the UI thread.
31                     NotifyUserFromAsyncThread(String.Format("Receive data: \"{0}\"", reader.ReadString(actualStringLength)), NotifyType.StatusMessage);
32                 }
33             }
34             catch (Exception exception)
35             {
36                 // If this is an unknown status it means that the error is fatal and retry will likely fail.
37                 if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
38                 {
39                     throw;
40                 }
41
42                 NotifyUserFromAsyncThread("Read stream failed with error: " + exception.Message, NotifyType.ErrorMessage);
43             }
44         }
  在 OnConnection 内我们不难发现 StreamSocket 读取数据的方式:DataReader reader = new DataReader(args.Socket.InputStream);
  StreamSocket读取数据依靠的是 DataReader 。在Windows 8 Metro APP 内有许多 InputStream 输入流格式的相关操作。
  
  DataReader 是一个相当好的读取器。
  代码:



                    uint stringLength = reader.ReadUInt32();
uint actualStringLength = await reader.LoadAsync(stringLength);
if (stringLength != actualStringLength)
{
// The underlying socket was closed before we were able to read the whole data.
return;
}

  
  上面的一段代码意义为:读取 InputStream 内预先写好的长度reader.ReadUInt32();(注意非真实数据长度,发送数据时会讲到)
  然后根据预先写好的长度异步读取实际数据 await reader.LoadAsync(stringLength);
  其中reader.LoadAsync 返回的是DataReaderLoadOperation类继承了IAsyncOperation 。隐式转换为UINT类型。
  if (stringLength != actualStringLength) 即为验证预先写好的数据长度与实际接收的数据长度是否一致。
  如果不一致有可能是发送端过早关闭SOCKET,使数据不完整接收,不可用。
  
  接下来代码:



NotifyUserFromAsyncThread(String.Format("Receive data: \"{0}\"", reader.ReadString(actualStringLength)), NotifyType.StatusMessage);

  
  reader.ReadString(actualStringLength)通知输出reader读取到的数据。OK到这里StreamSocket的监听以及数据接收讲解完毕。
  
  接下来我们看到Scenario2.xaml.cs 情景2 的代码:



1         private async void ConnectSocket_Click(object sender, RoutedEventArgs e)
2         {
3             if (CoreApplication.Properties.ContainsKey("clientSocket"))
4             {
5                 rootPage.NotifyUser("This step has already been executed. Please move to the next one.", NotifyType.ErrorMessage);
6                 return;
7             }
8
9             if (String.IsNullOrEmpty(ServiceNameForConnect.Text))
10             {
11                 rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage);
12                 return;
13             }
14
15             // By default 'HostNameForConnect' is disabled and host name validation is not required. When enabling the text
16             // box validating the host name is required since it was received from an untrusted source (user input).
17             // The host name is validated by catching ArgumentExceptions thrown by the HostName constructor for invalid
18             // input.
19             // Note that when enabling the text box users may provide names for hosts on the intErnet that require the
20             // "Internet (Client)" capability.
21             HostName hostName;
22             try
23             {
24                 hostName = new HostName(HostNameForConnect.Text);
25             }
26             catch (ArgumentException)
27             {
28                 rootPage.NotifyUser("Error: Invalid host name.", NotifyType.ErrorMessage);
29                 return;
30             }
31
32             StreamSocket socket = new StreamSocket();
33
34             // Save the socket, so subsequent steps can use it.
35             CoreApplication.Properties.Add("clientSocket", socket);
36
37             rootPage.NotifyUser("Connecting to: " + HostNameForConnect.Text, NotifyType.StatusMessage);
38
39             try
40             {
41                 // Connect to the server (in our case the listener we created in previous step).
42                 await socket.ConnectAsync(hostName, ServiceNameForConnect.Text);
43
44                 rootPage.NotifyUser("Connected", NotifyType.StatusMessage);
45
46                 // Mark the socket as connected. Set the value to null, as we care only about the fact that the property is set.
47                 CoreApplication.Properties.Add("connected", null);
48             }
49             catch (Exception exception)
50             {
51                 // If this is an unknown status it means that the error is fatal and retry will likely fail.
52                 if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
53                 {
54                     throw;
55                 }
56
57                 rootPage.NotifyUser("Connect failed with error: " + exception.Message, NotifyType.ErrorMessage);
58             }
59         }
  嗯。Scenario2就这点代码。这个片段介绍的是如何利用StreamSocket 创建一个连接 连到正在监听的服务器。
  首先接收一个IP或者是计算机网络名称用于创建一个HostName类。这里的HostName有点类似于曾经的Socket连接需要 用到IPEndPoint的IPAddress类。
  然后是声明一个StreamSocket 实例 。
  代码:await socket.ConnectAsync(hostName, ServiceNameForConnect.Text); 异步连接服务器,hostName 为上面的HostName类,ServiceNameForConnect.Text为接收一个端口号。
  可以这样写 socket.ConnectAsync(new HostName("127.0.0.1"), "4518")
  到这里 连接的部分也完成了。
  
  接下来就是发数据。我们看到Scenario3.xaml.cs 情景3的代码:



        private async void SendHello_Click(object sender, RoutedEventArgs e)
{
if (!CoreApplication.Properties.ContainsKey("connected"))
{
rootPage.NotifyUser("Please run previous steps before doing this one.", NotifyType.ErrorMessage);
return;
}
object outValue;
StreamSocket socket;
if (!CoreApplication.Properties.TryGetValue("clientSocket", out outValue))
{
rootPage.NotifyUser("Please run previous steps before doing this one.", NotifyType.ErrorMessage);
return;
}
socket = (StreamSocket)outValue;
// Create a DataWriter if we did not create one yet. Otherwise use one that is already cached.
DataWriter writer;
if (!CoreApplication.Properties.TryGetValue("clientDataWriter", out outValue))
{
writer = new DataWriter(socket.OutputStream);
CoreApplication.Properties.Add("clientDataWriter", writer);
}
else
{
writer = (DataWriter)outValue;
}
// Write first the length of the string as UINT32 value followed up by the string. Writing data to the writer will just store data in memory.
string stringToSend = "Hello";
writer.WriteUInt32(writer.MeasureString(stringToSend));
writer.WriteString(stringToSend);
// Write the locally buffered data to the network.
try
{
await writer.StoreAsync();
SendOutput.Text = "\"" + stringToSend + "\" sent successfully.";
}
catch (Exception exception)
{
// If this is an unknown status it means that the error if fatal and retry will likely fail.
if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
{
throw;
}
rootPage.NotifyUser("Send failed with error: " + exception.Message, NotifyType.ErrorMessage);
}
}

  
  嗯。这个发数据就这点代码。
  
  首先我们看到的是如何获取之前连接好的StreamSocket 实例。
  代码:



            object outValue;
StreamSocket socket;
if (!CoreApplication.Properties.TryGetValue("clientSocket", out outValue))
{
rootPage.NotifyUser("Please run previous steps before doing this one.", NotifyType.ErrorMessage);
return;
}
socket = (StreamSocket)outValue;

  
  这里我们不讨论CoreApplication相关的东西。我们只需要知道通过这个我们获取到了前面连接上的StreamSocket 实例。
  在前面我们看到 Metro应用内 读取InputStream 的DataReader ,现在我们要声明一个DataWriter。
  
  代码:



DataWriter writer;
if (!CoreApplication.Properties.TryGetValue("clientDataWriter", out outValue))
{
writer = new DataWriter(socket.OutputStream);
CoreApplication.Properties.Add("clientDataWriter", writer);
}
else
{
writer = (DataWriter)outValue;
}

  
  好吧。暂且我们把 CoreApplication当成一个缓存工具。
  代码大概的意思是 尝试从缓存中取到DataWriter实例,如果没有就创建一个。
  我们看到在创建一个DataWriter时将StreamSocket 的 OutputStream 作为参数传入。
  传入OutputStream 在于将DataWriter与建立好连接的StreamSocket进行绑定关联。
  
  然后我们开始制造数据 代码:



            string stringToSend = "Hello";
writer.WriteUInt32(writer.MeasureString(stringToSend));
writer.WriteString(stringToSend);

  
  这里我们需要发送的数据是"Hello"。
  writer.WriteUInt32(writer.MeasureString(stringToSend));便是我们前面的 情景1 读取数据时需要用到 预先写好的数据长度。
  writer.MeasureString()返回的是要发送数据的字符串长度,writer.WriteUInt32()将字符串长度写入到输出流。
  然后writer.WriteString(stringToSend);是将字符串写入输出流。
  
  到这里我们不难发现StreamSocket 利用DataReader DataWriter 读取和写入数据的同时帮助我们制造数据包格式。
  如果您是原异步Socket的使用经验者,应该会考虑到数据包格式定义的问题。
  比如 数据包 包头+数据包长度+(数据包类型+数据包命令+数据包内容)+包尾 类似的设计。
  数据包设计必然会出现的数据包长度,在StreamSocket 的读取写入 已是单独实现,不需要再到数据包里面找了。
  
  接下来是异步发送数据:



await writer.StoreAsync();

  
  这个异步方法的注解为:将缓冲区的数据提交到备份存储区。
  这个注解我本人比较笨理解不过来,大概是说将数据流数据放入发送的SOCKET BUFFER内 等待计算机网络空闲时才把数据发送出去。(这个理解为个人理解,希望有高人指点)
  到此我们的发送情景也就完结了。
  
  Scenario4.xam.cs 情景4 为一系列移除CoreApplication 以及释放StreamSocket StreamSocketListener DataWriter 。DataWriter的释放比较有意思,代码贴上具体就不再解读了:


DSC0000.gif DSC0001.gif CloseSockets


1         private void CloseSockets_Click(object sender, RoutedEventArgs e)
2         {
3             object outValue;
4             if (CoreApplication.Properties.TryGetValue("clientDataWriter", out outValue))
5             {
6                 // Remove the data writer from the list of application properties as we are about to close it.
7                 CoreApplication.Properties.Remove("clientDataWriter");
8                 DataWriter dataWriter = (DataWriter)outValue;
9
10                 // To reuse the socket with other data writer, application has to detach the stream from the writer
11                 // before disposing it. This is added for completeness, as this sample closes the socket in
12                 // very next block.
13                 dataWriter.DetachStream();
14                 dataWriter.Dispose();
15             }
16
17             if (CoreApplication.Properties.TryGetValue("clientSocket", out outValue))
18             {
19                 // Remove the socket from the list of application properties as we are about to close it.
20                 CoreApplication.Properties.Remove("clientSocket");
21                 StreamSocket socket = (StreamSocket)outValue;
22                 socket.Dispose();
23             }
24
25             if (CoreApplication.Properties.TryGetValue("listener", out outValue))
26             {
27                 // Remove the listener from the list of application properties as we are about to close it.
28                 CoreApplication.Properties.Remove("listener");
29                 StreamSocketListener listener = (StreamSocketListener)outValue;
30                 listener.Dispose();
31             }
32
33             if (CoreApplication.Properties.ContainsKey("connected"))
34             {
35                 CoreApplication.Properties.Remove("connected");
36             }
37
38             rootPage.NotifyUser("Socket and listener closed", NotifyType.StatusMessage);
39         }
  
  以上为鄙人解读的 MSDN内关于StreamSocket示例代码。
  解读完后问题才刚刚开始,在原有的异步Socket连接方式与现在WIN8 Metro App 的StreamSocket 如何通信呢?
  StreamSocket在原有的数据包格式如何读取到数据?(没有DataWriter.WriteUInt32(),DataReader.ReadUInt32();)
  WIN8 Metro App 如何像之前的Silverlight一样与服务器进行异步收发数据(问答数据)?
  嗯。现在已是凌晨5点,这几个问题的解答与实验将会在下一章博文里出现。当然如果有人写了,鄙人就不再重复了。
  解答的下一篇连接 《Windows 8 Metro 关于StreamSocket与原异步Socket》
  国际惯例如果您看得起鄙人,转载请注明出处谢谢。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-69649-1-1.html 上篇帖子: Windows Phone 8初学者开发—第14部分:在运行时绑定到真实的数据 下篇帖子: Windows 8实用窍门系列:6.Windows 8中的Popup使用方式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表