Windows 8 Metro 关于StreamSocket

我针对的看了 StreamWebSocket 以及StreamSocket。 我翻查了微软的MSDN 关于Windows 8 Metro StreamSocket (基本是英文的中文资料甚少,个人觉得自己英文很烂),下载到了关于StreamSocket 的示例 下载地址 。里面的代码具体讲述了 如何创建一个StreamSocket连接的服务器监听 如何利用StreamSocket 连接上服务器 如何发送消息 接收显示消息。一切看上去都是这么的全面。经过详细的阅读代码,我发现Windows 8 Metro StreamSocket 的发送接收都与之前的异步连接方式有相当大的区别。示例内用到了await/async 关键字(关于这个已经有很多博文解释了),对于两个关键字的理解是必要的。

 

现在让我们来解读下示例内的代码:(忽略App.xaml代码)

Scenario1.xaml.cs 情景1

这个情景片段(是这样翻译吧?英文太烂)的主要代码:

 点击按钮开始创建一个服务器监听。

/// <summary>
/// This is the click handler for the 'StartListener' button.
/// </summary>
/// <param name="sender">Object for which the event was generated.</param>
/// <param name="e">Event's parameters.</param>
private async void StartListener_Click(object sender, RoutedEventArgs e)
{
// Overriding the listener here is safe as it will be deleted once all references to it are gone. However, in many cases this
// is a dangerous pattern to override data semi-randomly (each time user clicked the button) so we block it here.
if (CoreApplication.Properties.ContainsKey("listener"))
{
rootPage.NotifyUser("This step has already been executed. Please move to the next one.", NotifyType.ErrorMessage);
return;
}

if (String.IsNullOrEmpty(ServiceNameForListener.Text))
{
rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage);
return;
}

StreamSocketListener listener = new StreamSocketListener();
listener.ConnectionReceived += OnConnection;

// Save the socket, so subsequent steps can use it.
CoreApplication.Properties.Add("listener", listener);

// Start listen operation.
try
{
await listener.BindServiceNameAsync(ServiceNameForListener.Text);
rootPage.NotifyUser("Listening", NotifyType.StatusMessage);
}
catch (Exception exception)
{
CoreApplication.Properties.Remove("listener");

// If this is an unknown status it means that the error is fatal and retry will likely fail.
if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
{
throw;
}

rootPage.NotifyUser("Start listening failed with error: " + exception.Message, NotifyType.ErrorMessage);
}
}

我们可以看到 StreamSocketListener 创建监听是一件相当容易的事情。

其中代码 await listener.BindServiceNameAsync(ServiceNameForListener.Text);

listener.BindServiceNameAsync(端口号)为异步开启某端口监听代码。

 

listener.ConnectionReceived += OnConnection; 这个就类似于我们曾经写的异步SOCKET 数据过来时触发的委托事件。

 

OnConnection 代码:

/// <summary>
        /// Invoked once a connection is accepted by StreamSocketListener.
        /// </summary>
        /// <param name="sender">The listener that accepted the connection.</param>
        /// <param name="args">Parameters associated with the accepted connection.</param>
        private async void OnConnection(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
        {
            DataReader reader = new DataReader(args.Socket.InputStream);
            try
            {
                while (true)
                {
                    // Read first 4 bytes (length of the subsequent string).
                    uint sizeFieldCount = await reader.LoadAsync(sizeof(uint));
                    if (sizeFieldCount != sizeof(uint))
                    {
                        // The underlying socket was closed before we were able to read the whole data.
                        return;
                    }

                    // Read the string.
                    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;
                    }

                    // 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.
                    NotifyUserFromAsyncThread(String.Format("Receive data: \"{0}\"", reader.ReadString(actualStringLength)), NotifyType.StatusMessage);
                }
            }
            catch (Exception exception)
            {
                // If this is an unknown status it means that the error is fatal and retry will likely fail.
                if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
                {
                    throw;
                }

                NotifyUserFromAsyncThread("Read stream failed with error: " + exception.Message, NotifyType.ErrorMessage);
            }
        }

在 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> 。隐式转换为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 的代码:

private async void ConnectSocket_Click(object sender, RoutedEventArgs e)
        {
            if (CoreApplication.Properties.ContainsKey("clientSocket"))
            {
                rootPage.NotifyUser("This step has already been executed. Please move to the next one.", NotifyType.ErrorMessage);
                return;
            }

            if (String.IsNullOrEmpty(ServiceNameForConnect.Text))
            {
                rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage);
                return;
            }

            // By default 'HostNameForConnect' is disabled and host name validation is not required. When enabling the text
            // box validating the host name is required since it was received from an untrusted source (user input).
            // The host name is validated by catching ArgumentExceptions thrown by the HostName constructor for invalid
            // input.
            // Note that when enabling the text box users may provide names for hosts on the intErnet that require the
            // "Internet (Client)" capability.
            HostName hostName;
            try
            {
                hostName = new HostName(HostNameForConnect.Text);
            }
            catch (ArgumentException)
            {
                rootPage.NotifyUser("Error: Invalid host name.", NotifyType.ErrorMessage);
                return;
            }

            StreamSocket socket = new StreamSocket();

            // Save the socket, so subsequent steps can use it.
            CoreApplication.Properties.Add("clientSocket", socket);

            rootPage.NotifyUser("Connecting to: " + HostNameForConnect.Text, NotifyType.StatusMessage);

            try
            {
                // Connect to the server (in our case the listener we created in previous step).
                await socket.ConnectAsync(hostName, ServiceNameForConnect.Text);

                rootPage.NotifyUser("Connected", NotifyType.StatusMessage);

                // Mark the socket as connected. Set the value to null, as we care only about the fact that the property is set.
                CoreApplication.Properties.Add("connected", null);
            }
            catch (Exception exception)
            {
                // If this is an unknown status it means that the error is fatal and retry will likely fail.
                if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
                {
                    throw;
                }

                rootPage.NotifyUser("Connect failed with error: " + exception.Message, NotifyType.ErrorMessage);
            }
        }

嗯。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 实例。

代码:

bject 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的释放比较有意思,代码贴上具体就不再解读了:

CloseSockets
        private void CloseSockets_Click(object sender, RoutedEventArgs e)
        {
            object outValue;
            if (CoreApplication.Properties.TryGetValue("clientDataWriter", out outValue))
            {
                // Remove the data writer from the list of application properties as we are about to close it.
                CoreApplication.Properties.Remove("clientDataWriter");
                DataWriter dataWriter = (DataWriter)outValue;

                // To reuse the socket with other data writer, application has to detach the stream from the writer
                // before disposing it. This is added for completeness, as this sample closes the socket in
                // very next block.
                dataWriter.DetachStream();
                dataWriter.Dispose();
            }

            if (CoreApplication.Properties.TryGetValue("clientSocket", out outValue))
            {
                // Remove the socket from the list of application properties as we are about to close it.
                CoreApplication.Properties.Remove("clientSocket");
                StreamSocket socket = (StreamSocket)outValue;
                socket.Dispose();
            }

            if (CoreApplication.Properties.TryGetValue("listener", out outValue))
            {
                // Remove the listener from the list of application properties as we are about to close it.
                CoreApplication.Properties.Remove("listener");
                StreamSocketListener listener = (StreamSocketListener)outValue;
                listener.Dispose();
            }

            if (CoreApplication.Properties.ContainsKey("connected"))
            {
                CoreApplication.Properties.Remove("connected");
            }

            rootPage.NotifyUser("Socket and listener closed", NotifyType.StatusMessage);
        }

以上为鄙人解读的 MSDN内关于StreamSocket示例代码。

  解读完后问题才刚刚开始,在原有的异步Socket连接方式与现在WIN8 Metro App 的StreamSocket 如何通信呢?

  StreamSocket在原有的数据包格式如何读取到数据?(没有DataWriter.WriteUInt32(),DataReader.ReadUInt32();)

  WIN8 Metro App 如何像之前的Silverlight一样与服务器进行异步收发数据(问答数据)?

  嗯。现在已是凌晨5点,这几个问题的解答与实验将会在下一章博文里出现。当然如果有人写了,鄙人就不再重复了。

解答的下一篇连接 《Windows 8 Metro 关于StreamSocket与原异步Socket

 

本文来自狐说的博客,原文地址:http://www.cnblogs.com/foxplume/archive/2012/09/20/2694583.html

posted @ 2015-01-13 14:18  海豹  阅读(443)  评论(0编辑  收藏  举报