Windows 8 Metro 关于StreamSocket与原异步Socket

前一篇 《Windows 8 Metro 关于 StreamSocket MSDN示例阅读》我们基本懂得如何通过StreamSocket 做监听、连接、发送接收数据。

同时前一篇留下的几个疑问,我们在这里进行实验和解答。

在“原有的异步Socket”连接方式与现在WIN8 Metro App 的StreamSocket 如何通信呢?

首先解释下这里说的“原有的异步Socket” 是什么。

在await/async 关键字出现前,我们的Socket异步是依靠System.Net 以及 System.Net.Sockets 命名空间下的类与方法实现的。

请求连接BeginConnect/ConnectAsync 接受连接 BeginAccept/AcceptAsync 发送信息BeginSend/SendAsyn 收信息BeginReceive/ReceiveAsync

上面列举的都是Socket类下的异步操作主要方法。这就是前面文章说的“原有的异步Socket”。

前一篇里还有两个问题分别是

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

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

这几个问题我想用一个相对简单合适的示例来讲解。

所以我们先来设定一下场景。

我现在面对的情况是,已经有使用原异步Socket的成熟服务器程序以及解决方案了。

这个解决方案可能会用于各种客户端的连接,比如 FLASH客户端 Silverlight客户端 WINFORM客户端 HTML5客户端 等等。

现在我们需要增加一个WIN8 metro APP 的客户端。但是我们不希望重新开发服务器端的东西。

于是乎我们就有了WIN8 metro APP 与 原异步Socket 连接收发信息的需求。

首先是服务器方面的一些主要代码:(如果您之前已经做了很多可以忽略这一部分)

首先建立一个WINFORM工程然后在默认的FORM1上添加一个LISTBOX 和一个 Button 控件

然后转到后台代码。

声明一个Socket 以及一个存储客户端的LIST。 添加Button Click事件代码:

button1_Click
        IList<Client> ClientList = new List<Client>();
        Socket ServerSoket;
        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            ServerSoket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            ServerSoket.Bind(new IPEndPoint(IPAddress.Any, 4518));//监听端口为4518
            ServerSoket.Listen(100);//100是相对稳定连接队列数
            ServerSoket.BeginAccept(new AsyncCallback(ClientConnectComplete), null);
            OutPutMsg("服务器在端口4518开始监听");
        }

ClientConnectComplete 代码:

ClientConnectComplete
        private void ClientConnectComplete(IAsyncResult async)
        {
            Client client = new Client();

            // 完成接受客户端传入的连接的这个异步操作,并返回客户端连入的 socket
            try
            {
                client.Socket = ServerSoket.EndAccept(async);
            }
            catch (Exception)
            {

                return;
            }

            //把连接进来的客户端保存起来
            ClientList.Add(client);

            OutPutMsg(client.Socket.RemoteEndPoint + " 连接成功!");

            try
            {
                // 开始异步接收客户端传入的数据
                client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(ClientReceiveComplete), client);
            }
            catch (SocketException ex)
            {
                // 处理异常
            }

            ServerSoket.BeginAccept(new AsyncCallback(ClientConnectComplete), null);//继续接受下一个连接
        }

ClientReceiveComplete代码:

ClientReceiveComplete
        private void ClientReceiveComplete(IAsyncResult async)
        {
            Client client = async.AsyncState as Client;

            int _receiveCount = 0;//用于统计本次接收到的字节数

            try
            {
                // 完成接收数据的这个异步操作,并返回接收的字节数
                if (client.Socket.Connected)
                {
                    _receiveCount = client.Socket.EndReceive(async);
                }
                else
                {
                }
            }
            catch (SocketException ex)
            {
                // 处理异常
            }

            char startChar = System.BitConverter.ToChar(client.Buffer, 0);//获取到包头
            Int16 msgType = System.BitConverter.ToInt16(client.Buffer, 2);//获取到消息类型
            Int32 dataSize = System.BitConverter.ToInt32(client.Buffer, 4);//获取到包长度
            char endtChar = System.BitConverter.ToChar(client.Buffer, dataSize-2);//获取到包尾

            //验证数据包的正确性
            if (startChar.Equals(Convert.ToChar(2)) && endtChar.Equals(Convert.ToChar(3)))
            {
                //数据验证成功输出信息
                OutPutMsg(client.Socket.RemoteEndPoint + "数据验证成功。");
                string receivedContent = UTF32Encoding.UTF8.GetString(client.Buffer,8, dataSize - 10);//获取到数据内容
                OutPutMsg("接收到的数据内容是:" + receivedContent);

                //服务器应答机制,制造回发给客户端消息
                byte[] sendMsg = GetSendMsg(UTF32Encoding.UTF8.GetBytes("服务器发回数据:" + receivedContent),2);

                try
                {
                    //发送给所有客户端
                    foreach (var clientItem in ClientList)
                    {
                        clientItem.Socket.BeginSend(sendMsg, 0, sendMsg.Length, SocketFlags.None, new AsyncCallback(SendToClientComplete), clientItem);
                    }

                }
                catch (Exception ex)
                {

                    OutPutMsg(ex.Message);
                }
            }

            try
            {
                //继续开始接收客户端传入的数据
                if (client.Socket.Connected)
                {
                    client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, 0, new AsyncCallback(ClientReceiveComplete), client);
                }
            }
            catch (SocketException ex)
            {
                // 处理异常
            }
        }

这里数据接收以及验证数据都是比较简单粗略的,没有考虑掉包拆包复杂的数据验证等等。

SendToClientComplete代码:

SendToClientComplete
        private void SendToClientComplete(IAsyncResult async)
        {
            Client client = async.AsyncState as Client;
            try
            {
                // 完成将信息发送到客户端的这个异步操作
                if (client.Socket.Connected)
                {
                    client.Socket.EndSend(async);
                }
            }
            catch (SocketException ex)
            {
                // 处理异常
            }
            OutPutMsg("服务器数据回发完毕。");
        }

OutPutMsg代码:

OutPutMsg
       /// <summary>
        /// 正常消息输出
        /// </summary>
        /// <param name="msg"></param>
        private void OutPutMsg(string msg)
        {
            this.BeginInvoke(new System.EventHandler(AddToList), msg);
        }

AddToList代码:

AddToList
 /// <summary>
        /// 添加到窗体列表显示
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AddToList(object sender, EventArgs e)
        {
            this.listBox1.Items.Add(sender.ToString());
            try
            {
                this.listBox1.SelectedIndex = this.listBox1.Items.Count - 1;
            }
            catch (Exception)
            {
                //throw;
            }

            if (this.listBox1.Items.Count > 100)
            {
                this.listBox1.Items.RemoveAt(0);
            }
        }

GetSendMsg代码:

/// <summary>
        /// 组装消息包
        /// </summary>
        /// <param name="BArr">内容字节流</param>
        /// <param name="MsgType">消息类型</param>
        /// <returns></returns>
        private byte[] GetSendMsg(byte[] BArr, Int16 MsgType)
        {
            //数据包格式为 包头 + 消息类型 + 包长度 + 消息内容 + 包尾
            //开始字节流 包头
            byte[] bs = System.BitConverter.GetBytes(Convert.ToChar(2));
            //结束字节流 包尾
            byte[] be = System.BitConverter.GetBytes(Convert.ToChar(3));
            //消息体类型
            byte[] b1 = System.BitConverter.GetBytes(MsgType);

            //总长度
            int size = BArr == null ? 10 : BArr.Length + 10;

            byte[] b2 = System.BitConverter.GetBytes(size);

            byte[] b = new byte[size];

            Array.Copy(bs, 0, b, 0, bs.Length);
            Array.Copy(b1, 0, b, 2, b1.Length);
            Array.Copy(b2, 0, b, 4, b2.Length);
            if (BArr != null)
            {
                Array.Copy(BArr, 0, b, 8, BArr.Length);
            }
            Array.Copy(be, 0, b, size - 2, be.Length);

            return b;

        }

这个方法需要讲解下。前一篇博文我已经说到,做原异步socket通信时需要设计的数据包格式。

我的数据包格式 包头 + 消息类型 + 包长度 + 消息内容 + 包尾

当然METRO APP 通信时也是需要设计数据包的,只是数据长度可以单独出来。

class Client 代码:

class Client
    public class Client
    {
        /// <summary>
        /// 客户端 Socket
        /// </summary>
        public Socket Socket { get; set; }

        private byte[] _buffer;
        /// <summary>
        /// 为该客户端 Socket 开辟的缓冲区
        /// </summary>
        public byte[] Buffer
        {
            get
            {
                if (_buffer == null)
                    _buffer = new byte[102400];

                return _buffer;
            }
        }
    }

到这里简单的原异步socket服务器就设计完毕了。

服务器做的事情:接收连接放入客户端列表,接收客户端发来的数据验证处理并回发给所有客户端。

现在我们看看WIN8 METRO APP 的 设计。

首先新建一个 Windows 应用商店 空应用程序。

使用Blend + SketchFlow Preview for Visual Studio 2012 打开工程。

在默认的MainPage.xaml 里添加 两个 button,一个 TextBox, 一个ListBox 控件。

在第一个Button事件内我们写入StreamSocket 的连接以及对已连接上的StreamSocket 实例进行消息监听。

Button_Click_1代码:

Button_Click_1
        StreamSocket sk = new StreamSocket();
        private async void Button_Click_1(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {

            await sk.ConnectAsync(new HostName("127.0.0.1"), "4518");
            await ShowMsg("发送连接完毕。");

            if (sk.Information!= null)
            {
                await BeginReceived();
            }

        }

这段代码我就不解释了,看过前一篇的朋友应该都懂。

BeginReceived 代码:

BeginReceived

       private async Task BeginReceived()
        {

            //绑定已连接的StreamSocket到DataReader
            DataReader reader = new DataReader(sk.InputStream);
            while (true)
            {
                try
                {
                    //尝试从StreamSocket 读取到数据 读取2个字节的数据(包头)
                    uint sizeFieldCount = await reader.LoadAsync(sizeof(UInt16));
                    if (sizeFieldCount != sizeof(UInt16))
                    {
                        return;
                    }
                }
                catch (Exception exception)
                {
                    return;
                }
                //临时处理读取器 的字节流数组
                byte[] tempByteArr;
                tempByteArr = new byte[2];

                //将刚才reader读取到的数据填充到tempByteArr
                reader.ReadBytes(tempByteArr);

                char startChar = System.BitConverter.ToChar(tempByteArr, 0);//获取到包头
                //如果不是包头
                if (!startChar.Equals(Convert.ToChar(2)))
                {
                    return;
                }

                //在读取下2个字节
                await reader.LoadAsync(sizeof(UInt16));
                reader.ReadBytes(tempByteArr);
                Int16 msgType = System.BitConverter.ToInt16(tempByteArr,0);//获取到消息类型
                await ShowMsg("获取到的消息类型:" + msgType.ToString());

                tempByteArr = new byte[4];
                //在读取下4个字节
                await reader.LoadAsync(sizeof(uint));
                reader.ReadBytes(tempByteArr);
                uint dataSize = System.BitConverter.ToUInt32(tempByteArr, 0);//获取到包长度
                await ShowMsg("获取到的数据包长度:" + dataSize.ToString());

                uint contentByteLenth = dataSize - 8;//内容字节流长度
                //根据长度读取内容字节流
                uint actualStringLength = await reader.LoadAsync(contentByteLenth);
                if (contentByteLenth != actualStringLength)
                {
                   //内容数据流未发送完成
                    await ShowMsg("内容数据流未发送完成");
                    return;
                }

                //填充
                tempByteArr = new byte[contentByteLenth];
                reader.ReadBytes(tempByteArr);

                await ShowMsg(System.Text.UnicodeEncoding.UTF8.GetString(tempByteArr, 0, int.Parse(contentByteLenth.ToString())));
            }
        }

BeginReceived方法内我们用一个 DataReader 绑定已经连接上的StreamSocket 实例的InputStream

然后我们利用循环进行监听,监听时需要进行初步的数据读取以便查看是否有数据到达。

我讲解一下BeginReceived内的代码。因为这是这篇博文比较重要的部分。

代码:

try
                {
                    //尝试从StreamSocket 读取到数据 读取2个字节的数据(包头)
                    uint sizeFieldCount = await reader.LoadAsync(sizeof(UInt16));
                    if (sizeFieldCount != sizeof(UInt16))
                    {
                        return;
                    }
                }
                catch (Exception exception)
                {
                    return;
                }

这里要说下为什么要读取 2个字节的数据长度来做验证呢?

前面的服务器设计里我们数据包的格式包头就是2个字节。

DataReader.LoadAsync() 方法的机制是:

从StreamSocket的InputStream读取一定长度的数据字节数到DataReader内,且读取完毕后InputStream内被读取到的数据将被删除。

所以我这里用读取2个字节的包头来做是否有数据的判断。

接下来的代码:

//临时处理读取器 的字节流数组
                byte[] tempByteArr;
                tempByteArr = new byte[2];

                //将刚才reader读取到的数据填充到tempByteArr
                reader.ReadBytes(tempByteArr);

                char startChar = System.BitConverter.ToChar(tempByteArr, 0);//获取到包头
                //如果不是包头
                if (!startChar.Equals(Convert.ToChar(2)))
                {
                    return;
                }

定义一个临时的字节数组 tempByteArr 且数组大小与需要读取到的数据长度一样。

DataReader.ReadBytes()方法是根据传入的字节数组长度读取数据的。

所以接下来的代码:

//再读取下2个字节
                await reader.LoadAsync(sizeof(UInt16));
                reader.ReadBytes(tempByteArr);
                Int16 msgType = System.BitConverter.ToInt16(tempByteArr,0);//获取到消息类型
                await ShowMsg("获取到的消息类型:" + msgType.ToString());

                tempByteArr = new byte[4];
                //再读取下4个字节
                await reader.LoadAsync(sizeof(uint));
                reader.ReadBytes(tempByteArr);
                uint dataSize = System.BitConverter.ToUInt32(tempByteArr, 0);//获取到包长度
                await ShowMsg("获取到的数据包长度:" + dataSize.ToString());

                uint contentByteLenth = dataSize - 8;//内容字节流长度
                //根据长度读取内容字节流
                uint actualStringLength = await reader.LoadAsync(contentByteLenth);
                if (contentByteLenth != actualStringLength)
                {
                   //内容数据流未发送完成
                    await ShowMsg("内容数据流未发送完成");
                    return;
                }

                //填充
                tempByteArr = new byte[contentByteLenth];
                reader.ReadBytes(tempByteArr);

                await ShowMsg(System.Text.UnicodeEncoding.UTF8.GetString(tempByteArr, 0, int.Parse(contentByteLenth.ToString())));

我们读取到包长度后就能顺利的得到我们的消息内容了。

接下来的Button2 发送信息 代码:

Button_Click_2
        DataWriter writer;
        private async void Button_Click_2(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            if (this.SendText.Text != string.Empty)
            {
                if (writer == null)
                {
                    writer = new DataWriter(sk.OutputStream);
                }

                //需要发送的数据
                byte[] sendMsg = GetSendMsg(System.Text.UnicodeEncoding.UTF8.GetBytes(this.SendText.Text), 1);

                //把数据写入到发送流
                writer.WriteBytes(sendMsg);

                //异步发送
                await writer.StoreAsync();

            }
        }

ShowMsg GetSendMsg 代码:

ShowMsg GetSendMsg
       protected async Task ShowMsg(string s)
        {
            MsgList.Items.Insert(0, s);
        }

        /// <summary>
        /// 组装消息包
        /// </summary>
        /// <param name="BArr">内容字节流</param>
        /// <param name="MsgType">消息类型</param>
        /// <returns></returns>
        private byte[] GetSendMsg(byte[] BArr, Int16 MsgType)
        {
            //数据包格式为 包头 + 消息类型 + 包长度 + 消息内容 + 包尾
            //开始字节流 包头
            byte[] bs = System.BitConverter.GetBytes(Convert.ToChar(2));
            //结束字节流 包尾
            byte[] be = System.BitConverter.GetBytes(Convert.ToChar(3));
            //消息体类型
            byte[] b1 = System.BitConverter.GetBytes(MsgType);

            //总长度
            int size = BArr == null ? 10 : BArr.Length + 10;

            byte[] b2 = System.BitConverter.GetBytes(size);

            byte[] b = new byte[size];

            Array.Copy(bs, 0, b, 0, bs.Length);
            Array.Copy(b1, 0, b, 2, b1.Length);
            Array.Copy(b2, 0, b, 4, b2.Length);
            if (BArr != null)
            {
                Array.Copy(BArr, 0, b, 8, BArr.Length);
            }
            Array.Copy(be, 0, b, size - 2, be.Length);

            return b;

        }

到这里基本解答了

在“原有的异步Socket”连接方式与现在WIN8 Metro App 的StreamSocket 如何通信呢?

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

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

时间: 2024-11-05 14:38:01

Windows 8 Metro 关于StreamSocket与原异步Socket的相关文章

python异步socket编程之二

三.异步client与异步server的通信 1. 服务端代码 pythone socket的server段,开放三个端口:10000,10001,10002. 例子中是每个server绑定一个端口,测试的时候需要分别开3个shell,分别运行. 这太麻烦了,就分别用三个Thread来运行这些services #!/usr/bin/env python # # -*- coding:utf-8 -*- # File: multithrd_socket_server.py # import opt

C# 异步Socket

C# 异步Socket (BeginXXXX)服务器代码 前言: 1.最近维护公司的一个旧项目,是Socket通讯的,主要用于接收IPC(客户端)发送上来的抓拍图像,期间要保持通讯,监测数据包并进行处理.但是看之前那人写的代码个人觉得并不是很适合自己,于是就重写了,不过项目暂时弃置了,为了以后能够方便使用,也方便更多像我一样还是渣渣程序员的人,记录一些心得.我还是坚信那句话,分享才能够进步得更快 2.其实在做之前我对这个东西了解也很少,毕竟以我的认识,在国内C#更多地是用来开发网站,于是也在网上

c#异步Socket Tcp服务器实现

原创性申明 本文作者: 小竹zz  本文地址:http://blog.csdn.net/zhujunxxxxx 转载请注明出处. 介绍 我之前写过一篇IOCP的文章: http://blog.csdn.net/zhujunxxxxx/article/details/43573879 这个比异步socket性能好,因为它复用对象了. 在c#中微软已经提供了TcpListener和TcpClient来实现Tcp的通讯,这部分已经有人写了比较好的异步服务器代码 http://www.cnblogs.c

Unity3d 游戏与C#服务器 异步Socket 交互 (一)

Unity3d中提供了Socket供开发者使用,语法和.net中的一致. 一般来说,对于手游客户端,分为两个线程,一个是GLES渲染,另一个就是Socket线程了. 文章转自(http://blog.csdn.net/huutu) 不论是服务器,还是客户端.其间的数据包的接收与发送,都是通过Socket. 比如客户端要登录,我们就新建一个Socket,Connect到帐号服务器.帐号服务器一直在等待客户端的连接,客户端连接进来之后就准备发送接收数据包了. 文章转自(http://blog.csd

异步Socket服务器与客户端

本文灵感来自Andre Azevedo 在CodeProject上面的一片文章,An Asynchronous Socket Server and Client,讲的是异步的Socket通信. Socket连接(Socket Connection) Socket服务(Socket Service) 连接主机(Connection Host) 加密与压缩(Encrypt与Compress) 请求入队(Enqueuing Requests) 确保发送和接收(Ensure send and recie

python中asynchat异步socket命令/响应处理

该模块基于asyncore简化了异步客户端和服务器,并使其更容易元素处理由任意的字符串结束,或者是可变长度的的协议.它提供了抽象类async_chat,提供collect_incoming_data()和found_terminator()方法.循环和asyncore的一样,有2种信道:asyncore.dispatcher和asynchat.async_chat,可以自由混合信道.通常asyncore.dispatcher服务器通道在接收到连接请求时产生新的asynchat.async_cha

Python异步Socket编程

异步网络据说能极大的提高网络server的连接速度,所以打算写一个专题,来学习和了解异步网络.因为Python有个非常出名的异步Lib:Twisted,所以就用Python来完成. OK,首先写一个pythone socket的server段,对开放三个端口:10000,10001,10002.krondo的例子中是每个server绑定一个端口,测试的时候需要分别开3个shell,分别运行.这太麻烦了,就分别用三个Thread来运行这些services. Java代码   import optp

异步SOCKET分包和组包的一种通用算法

异步SOCKET分包和组包的一种算法 unit uFun;// 应用协议// cxg 2016-9-23 interface uses SysUtils, Classes, PeachCtrl.Net.IocpTcpServer, System.Generics.Collections ; const // 包长 pack_len = 8192; const // 命令分类 cmd_qry_req = 1; cmd_qry_res = 2; cmd_post_req = 3; cmd_post_

高性能异步Socket框架

这是一个L/V(Length/Value)模型的异步Socket框架.L是指发送byte数组的长度,L固定为10个byte,V是指要发送的byte数组.可以用于处理超长的消息,处理完成的响应也按照L/V模型发送,客户端接收可以参考服务端解析过程. eg: 1.第一次收到的byte[]:0 0 0 0 0 0 1 1 1 2 28 30 18 26 88 99 77 那么先取0 0 0 0 0 0 1 1 1 2,得到长度为1112,对后面的byte(从index10以后)进行存储 2.第二次收到