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

Windows 8 Metro 关于StreamSocket与原异步Socket

[复制链接]

尚未签到

发表于 2015-5-22 07:33:05 | 显示全部楼层 |阅读模式
  前一篇 《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 控件
  
DSC0000.jpg
  
  然后转到后台代码。
  声明一个Socket 以及一个存储客户端的LIST。 添加Button Click事件代码:


DSC0001.gif DSC0002.gif button1_Click


1         IList ClientList = new List();
2         Socket ServerSoket;
3         ///
4         /// 开始监听
5         ///
6         ///
7         ///
8         private void button1_Click(object sender, EventArgs e)
9         {
10             ServerSoket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
11             ServerSoket.Bind(new IPEndPoint(IPAddress.Any, 4518));//监听端口为4518
12             ServerSoket.Listen(100);//100是相对稳定连接队列数
13             ServerSoket.BeginAccept(new AsyncCallback(ClientConnectComplete), null);
14             OutPutMsg("服务器在端口4518开始监听");
15         }
  
  ClientConnectComplete 代码:


ClientConnectComplete


1         private void ClientConnectComplete(IAsyncResult async)
2         {
3             Client client = new Client();
4
5             // 完成接受客户端传入的连接的这个异步操作,并返回客户端连入的 socket
6             try
7             {
8                 client.Socket = ServerSoket.EndAccept(async);
9             }
10             catch (Exception)
11             {
12
13                 return;
14             }
15
16             //把连接进来的客户端保存起来
17             ClientList.Add(client);
18
19             OutPutMsg(client.Socket.RemoteEndPoint + " 连接成功!");
20
21             try
22             {
23                 // 开始异步接收客户端传入的数据
24                 client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(ClientReceiveComplete), client);
25             }
26             catch (SocketException ex)
27             {
28                 // 处理异常
29             }
30
31             ServerSoket.BeginAccept(new AsyncCallback(ClientConnectComplete), null);//继续接受下一个连接
32         }
  
  ClientReceiveComplete代码:


ClientReceiveComplete


1         private void ClientReceiveComplete(IAsyncResult async)
2         {
3             Client client = async.AsyncState as Client;
4
5             int _receiveCount = 0;//用于统计本次接收到的字节数
6
7             try
8             {
9                 // 完成接收数据的这个异步操作,并返回接收的字节数
10                 if (client.Socket.Connected)
11                 {
12                     _receiveCount = client.Socket.EndReceive(async);
13                 }
14                 else
15                 {
16                 }
17             }
18             catch (SocketException ex)
19             {
20                 // 处理异常
21             }
22            
23             char startChar = System.BitConverter.ToChar(client.Buffer, 0);//获取到包头
24             Int16 msgType = System.BitConverter.ToInt16(client.Buffer, 2);//获取到消息类型
25             Int32 dataSize = System.BitConverter.ToInt32(client.Buffer, 4);//获取到包长度
26             char endtChar = System.BitConverter.ToChar(client.Buffer, dataSize-2);//获取到包尾
27            
28             //验证数据包的正确性
29             if (startChar.Equals(Convert.ToChar(2)) && endtChar.Equals(Convert.ToChar(3)))
30             {
31                 //数据验证成功输出信息
32                 OutPutMsg(client.Socket.RemoteEndPoint + "数据验证成功。");
33                 string receivedContent = UTF32Encoding.UTF8.GetString(client.Buffer,8, dataSize - 10);//获取到数据内容
34                 OutPutMsg("接收到的数据内容是:" + receivedContent);
35
36                 //服务器应答机制,制造回发给客户端消息
37                 byte[] sendMsg = GetSendMsg(UTF32Encoding.UTF8.GetBytes("服务器发回数据:" + receivedContent),2);
38
39                 try
40                 {
41                     //发送给所有客户端
42                     foreach (var clientItem in ClientList)
43                     {
44                         clientItem.Socket.BeginSend(sendMsg, 0, sendMsg.Length, SocketFlags.None, new AsyncCallback(SendToClientComplete), clientItem);
45                     }
46                     
47                 }
48                 catch (Exception ex)
49                 {
50
51                     OutPutMsg(ex.Message);
52                 }
53             }
54
55             try
56             {
57                 //继续开始接收客户端传入的数据
58                 if (client.Socket.Connected)
59                 {
60                     client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, 0, new AsyncCallback(ClientReceiveComplete), client);
61                 }
62             }
63             catch (SocketException ex)
64             {
65                 // 处理异常
66             }
67         }
  
  这里数据接收以及验证数据都是比较简单粗略的,没有考虑掉包拆包复杂的数据验证等等。
  
  SendToClientComplete代码:


SendToClientComplete


1         private void SendToClientComplete(IAsyncResult async)
2         {
3             Client client = async.AsyncState as Client;
4             try
5             {
6                 // 完成将信息发送到客户端的这个异步操作
7                 if (client.Socket.Connected)
8                 {
9                     client.Socket.EndSend(async);
10                 }
11             }
12             catch (SocketException ex)
13             {
14                 // 处理异常
15             }
16             OutPutMsg("服务器数据回发完毕。");
17         }
  
  OutPutMsg代码:


OutPutMsg


1        ///
2         /// 正常消息输出
3         ///
4         ///
5         private void OutPutMsg(string msg)
6         {
7             this.BeginInvoke(new System.EventHandler(AddToList), msg);
8         }
  
  AddToList代码:


AddToList


1  ///
2         /// 添加到窗体列表显示
3         ///
4         ///
5         ///
6         private void AddToList(object sender, EventArgs e)
7         {
8             this.listBox1.Items.Add(sender.ToString());
9             try
10             {
11                 this.listBox1.SelectedIndex = this.listBox1.Items.Count - 1;
12             }
13             catch (Exception)
14             {
15                 //throw;
16             }
17
18             if (this.listBox1.Items.Count > 100)
19             {
20                 this.listBox1.Items.RemoveAt(0);
21             }
22         }
  
  GetSendMsg代码:



1          ///
2         /// 组装消息包
3         ///
4         /// 内容字节流
5         /// 消息类型
6         ///
7         private byte[] GetSendMsg(byte[] BArr, Int16 MsgType)
8         {
9             //数据包格式为 包头 + 消息类型 + 包长度 + 消息内容 + 包尾
10             //开始字节流 包头
11             byte[] bs = System.BitConverter.GetBytes(Convert.ToChar(2));
12             //结束字节流 包尾
13             byte[] be = System.BitConverter.GetBytes(Convert.ToChar(3));
14             //消息体类型
15             byte[] b1 = System.BitConverter.GetBytes(MsgType);
16
17             //总长度
18             int size = BArr == null ? 10 : BArr.Length + 10;
19
20             byte[] b2 = System.BitConverter.GetBytes(size);
21
22
23             byte[] b = new byte[size];
24
25             Array.Copy(bs, 0, b, 0, bs.Length);
26             Array.Copy(b1, 0, b, 2, b1.Length);
27             Array.Copy(b2, 0, b, 4, b2.Length);
28             if (BArr != null)
29             {
30                 Array.Copy(BArr, 0, b, 8, BArr.Length);
31             }
32             Array.Copy(be, 0, b, size - 2, be.Length);
33
34
35             return b;
36
37         }
  
  这个方法需要讲解下。前一篇博文我已经说到,做原异步socket通信时需要设计的数据包格式。
  我的数据包格式 包头 + 消息类型 + 包长度 + 消息内容 + 包尾
  当然METRO APP 通信时也是需要设计数据包的,只是数据长度可以单独出来。
  
  class Client 代码:
  


class Client


1     public class Client
2     {
3         ///
4         /// 客户端 Socket
5         ///
6         public Socket Socket { get; set; }
7
8         private byte[] _buffer;
9         ///
10         /// 为该客户端 Socket 开辟的缓冲区
11         ///
12         public byte[] Buffer
13         {
14             get
15             {
16                 if (_buffer == null)
17                     _buffer = new byte[102400];
18
19                 return _buffer;
20             }
21         }
22     }
  
  
  到这里简单的原异步socket服务器就设计完毕了。
  服务器做的事情:接收连接放入客户端列表,接收客户端发来的数据验证处理并回发给所有客户端。
  
  现在我们看看WIN8 METRO APP 的 设计。
  首先新建一个 Windows 应用商店 空应用程序。
  使用Blend + SketchFlow Preview for Visual Studio 2012 打开工程。
DSC0003.jpg
  
  在默认的MainPage.xaml 里添加 两个 button,一个 TextBox, 一个ListBox 控件。
  在第一个Button事件内我们写入StreamSocket 的连接以及对已连接上的StreamSocket 实例进行消息监听。
  
  Button_Click_1代码:
  


Button_Click_1


1         StreamSocket sk = new StreamSocket();
2         private async void Button_Click_1(object sender, Windows.UI.Xaml.RoutedEventArgs e)
3         {
4           
5             await sk.ConnectAsync(new HostName("127.0.0.1"), "4518");
6             await ShowMsg("发送连接完毕。");
7
8             if (sk.Information!= null)
9             {
10                 await BeginReceived();
11             }
12            
13         }
  
  
  这段代码我就不解释了,看过前一篇的朋友应该都懂。
  
  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个字节的包头来做是否有数据的判断。
  
  接下来的代码:



1                 //临时处理读取器 的字节流数组
2                 byte[] tempByteArr;
3                 tempByteArr = new byte[2];
4
5                 //将刚才reader读取到的数据填充到tempByteArr
6                 reader.ReadBytes(tempByteArr);
7
8                 char startChar = System.BitConverter.ToChar(tempByteArr, 0);//获取到包头
9                 //如果不是包头
10                 if (!startChar.Equals(Convert.ToChar(2)))
11                 {
12                     return;
13                 }
  定义一个临时的字节数组 tempByteArr 且数组大小与需要读取到的数据长度一样。
  DataReader.ReadBytes()方法是根据传入的字节数组长度读取数据的。
  所以接下来的代码:



1                 //再读取下2个字节
2                 await reader.LoadAsync(sizeof(UInt16));
3                 reader.ReadBytes(tempByteArr);
4                 Int16 msgType = System.BitConverter.ToInt16(tempByteArr,0);//获取到消息类型
5                 await ShowMsg("获取到的消息类型:" + msgType.ToString());
6
7                 tempByteArr = new byte[4];
8                 //再读取下4个字节
9                 await reader.LoadAsync(sizeof(uint));
10                 reader.ReadBytes(tempByteArr);
11                 uint dataSize = System.BitConverter.ToUInt32(tempByteArr, 0);//获取到包长度
12                 await ShowMsg("获取到的数据包长度:" + dataSize.ToString());
13
14                 uint contentByteLenth = dataSize - 8;//内容字节流长度
15                 //根据长度读取内容字节流
16                 uint actualStringLength = await reader.LoadAsync(contentByteLenth);
17                 if (contentByteLenth != actualStringLength)
18                 {
19                    //内容数据流未发送完成
20                     await ShowMsg("内容数据流未发送完成");
21                     return;
22                 }
23
24                 //填充
25                 tempByteArr = new byte[contentByteLenth];
26                 reader.ReadBytes(tempByteArr);
27
28                 await ShowMsg(System.Text.UnicodeEncoding.UTF8.GetString(tempByteArr, 0, int.Parse(contentByteLenth.ToString())));
  
  我们读取到包长度后就能顺利的得到我们的消息内容了。
  
  
  接下来的Button2 发送信息 代码:


Button_Click_2


1         DataWriter writer;
2         private async void Button_Click_2(object sender, Windows.UI.Xaml.RoutedEventArgs e)
3         {
4             if (this.SendText.Text != string.Empty)
5             {
6                 if (writer == null)
7                 {
8                     writer = new DataWriter(sk.OutputStream);
9                 }
10
11                 //需要发送的数据
12                 byte[] sendMsg = GetSendMsg(System.Text.UnicodeEncoding.UTF8.GetBytes(this.SendText.Text), 1);
13
14                 //把数据写入到发送流
15                 writer.WriteBytes(sendMsg);
16
17                 //异步发送
18                 await writer.StoreAsync();
19
20             }
21         }
  
  ShowMsg GetSendMsg 代码:


ShowMsg GetSendMsg


1        protected async Task ShowMsg(string s)
2         {
3             MsgList.Items.Insert(0, s);
4         }
5
6         ///
7         /// 组装消息包
8         ///
9         /// 内容字节流
10         /// 消息类型
11         ///
12         private byte[] GetSendMsg(byte[] BArr, Int16 MsgType)
13         {
14             //数据包格式为 包头 + 消息类型 + 包长度 + 消息内容 + 包尾
15             //开始字节流 包头
16             byte[] bs = System.BitConverter.GetBytes(Convert.ToChar(2));
17             //结束字节流 包尾
18             byte[] be = System.BitConverter.GetBytes(Convert.ToChar(3));
19             //消息体类型
20             byte[] b1 = System.BitConverter.GetBytes(MsgType);
21
22             //总长度
23             int size = BArr == null ? 10 : BArr.Length + 10;
24
25             byte[] b2 = System.BitConverter.GetBytes(size);
26
27
28             byte[] b = new byte[size];
29
30             Array.Copy(bs, 0, b, 0, bs.Length);
31             Array.Copy(b1, 0, b, 2, b1.Length);
32             Array.Copy(b2, 0, b, 4, b2.Length);
33             if (BArr != null)
34             {
35                 Array.Copy(BArr, 0, b, 8, BArr.Length);
36             }
37             Array.Copy(be, 0, b, size - 2, be.Length);
38
39
40             return b;
41
42         }
  
  到这里基本解答了
  在“原有的异步Socket”连接方式与现在WIN8 Metro App 的StreamSocket 如何通信呢?
  
  StreamSocket在原有的数据包格式如何读取到数据?(没有DataWriter.WriteUInt32(),DataReader.ReadUInt32();)
  
  WIN8 Metro App 如何像之前的Silverlight一样与服务器进行异步收发数据(问答数据)?
  
  最后给上 示例的源码:点击这里下载
  
  国际惯例如果您看得起鄙人,转载请注明出处谢谢。

运维网声明 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-69323-1-1.html 上篇帖子: 重新想象 Windows 8 Store Apps (66) 下篇帖子: Windows 8 Metro 应用开发入门(三):工具栏和对话框
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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