VS2010 C# 你得学会并且学得会的Socket编程基础知识(Silverlight Socket编程)

这一篇文章,我将图文并茂地介绍Socket编程的基础知识,我相信,如果你按照步骤做完实验,一定可以对Socket编程有更好地理解。

本文源代码,可以通过这里下载 http://files.cnblogs.com/chenxizhang/SocketWorkshop.rar

 

第一步:创建解决方案

image

第二步:创建服务端程序

这里可以选择“Console Application”这个类型,比较方便调试

image

然后编写如下代码,实现服务器的基本功能

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //将该socket绑定到主机上面的某个端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //启动监听,并且设置一个最大的队列长度
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

 

现在可以启动调试一下看看效果如何,正常情况下应该会看到一个提示,因为我们需要在TCP 4530端口进行监听,所以防火墙会有提示。

image

点击“Allow access”

image

这样,我们的服务器就可以开始监听了。但是这有什么用呢?是的,没有什么用。

我们还需要为服务器添加一些功能,例如接受传入的请求,给客户端发送消息,或者从客户端接收消息等等

第三步:接受传入的请求

我们需要通过Accept,或者(BeginAccept)来接受传入的请求,请注意下面代码中的红色部分

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //将该socket绑定到主机上面的某个端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //启动监听,并且设置一个最大的队列长度
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //开始接受客户端连接请求
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //这就是客户端的Socket实例,我们后续可以将其保存起来
                var client = socket.EndAccept(ar);

                //给客户端发送一个欢迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));
            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

 

wow,看起来不错对吧,我们赶紧做一个客户端来测试一下吧

 

第四步:创建客户端

我们还是使用一个Console Application

image

添加如下的代码,并且创建客户端连接

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//导入的命名空间
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //连接到指定服务器的指定端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);


            Console.WriteLine("connect to the server");
            Console.Read();

        }
    }
}

依次选择SocketServer和SocketClient这两个项目,分别将其启动为调试状态(右键菜单,Debug=>Start new instance)

image

我们看到两个程序都工作正常。

但是,在客户端怎么没有收到服务器发过来的消息呢?那是因为,我们没有在客户端提供这方面的功能。

 

第五步:在客户端中实现接受消息的方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//导入的命名空间
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //连接到指定服务器的指定端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);

            //实现接受消息的方法

            var buffer = new byte[1024];//设置一个缓冲区,用来保存数据
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>
            {
                //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //读取出来消息内容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //显示消息
                Console.WriteLine(message);

            }), null);

            Console.WriteLine("connect to the server");
            Console.Read();

        }
    }
}

请注意以上红色的部分,我们用了BeginReceive方法进行异步的消息侦听,如果收到了,我们就打印出来

image

看起来已经实现了我们需求了:服务器给客户端发了一个消息,而客户端也已经收到了。

但是,这远远不够,因为它们之间的通讯不仅仅是一次性的,那么如果服务器要不断地给客户端发消息,例如每隔两秒钟就发送一个消息,如何实现呢?

 

第六步:实现服务器定期向客户端发消息

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //将该socket绑定到主机上面的某个端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //启动监听,并且设置一个最大的队列长度
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //开始接受客户端连接请求
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //这就是客户端的Socket实例,我们后续可以将其保存起来
                var client = socket.EndAccept(ar);

                //给客户端发送一个欢迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));


                //实现每隔两秒钟给服务器发一个消息
                //这里我们使用了一个定时器
                var timer = new System.Timers.Timer();
                timer.Interval = 2000D;
                timer.Enabled = true;
                timer.Elapsed += (o, a) =>
                {
                    client.Send(Encoding.Unicode.GetBytes("Message from server at " +DateTime.Now.ToString()));
                };
                timer.Start();

            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

 

我们还要实现在客户端一直监听消息的机制,而不是一次性接收.请注意下面红色的部分

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//导入的命名空间
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //连接到指定服务器的指定端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);
            Console.WriteLine("connect to the server");

            //实现接受消息的方法

            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage),socket);

            Console.Read();

        }


        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;

                //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //读取出来消息内容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //显示消息
                Console.WriteLine(message);

                //接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex){
                Console.WriteLine(ex.Message);
            }
        }
    }
}

重新调试起来,看起来的效果如下图所示

image

我们继续做下面的实验,一步一步地研究Socket通讯中可能遇到的一些问题

请先关闭掉客户端这个程序,而不要关闭服务端程序,这时会发现一个错误

image

这个错误很容易理解,因为客户端已经关闭,也就是客户端那个Socket已经不存在了,服务器还继续向它发送消息当然会出错。所以,从可靠性方面的考虑,我们必须在发送消息之前检测Socket的活动状态

 

第七步:检测客户端的活动状态

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //将该socket绑定到主机上面的某个端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //启动监听,并且设置一个最大的队列长度
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //开始接受客户端连接请求
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //这就是客户端的Socket实例,我们后续可以将其保存起来
                var client = socket.EndAccept(ar);

                //给客户端发送一个欢迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));


                //实现每隔两秒钟给服务器发一个消息
                //这里我们使用了一个定时器
                var timer = new System.Timers.Timer();
                timer.Interval = 2000D;
                timer.Enabled = true;
                timer.Elapsed += (o, a) =>
                {
                    //检测客户端Socket的状态
                    if(client.Connected)
                    {
                        try
                        {
                            client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
                        }
                        catch(SocketException ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                    else
                    {
                        timer.Stop();
                        timer.Enabled = false;
                        Console.WriteLine("Client is disconnected, the timer is stop.");
                    }
                };
                timer.Start();

            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }
    }
}

上面代码的逻辑很清楚,但有时候还是会触发那个SocketException。为什么呢?这是因为我们的Timer是每隔两秒钟检查一次,那么就很可能有一种情况,我们检查的时候,它还是连接状态,消息发出去之后,它断开了。这种情况肯定是存在的。所以要用Try..catch的结构

 

目前我们实现的场景很简单,服务器只管发消息,客户端只管收消息。但实际工作中,可能希望服务器和客户端都能收发消息。请看下一节

 

第八步:实现双向收发消息

先看服务端的修改

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //将该socket绑定到主机上面的某个端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //启动监听,并且设置一个最大的队列长度
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //开始接受客户端连接请求
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback((ar) =>
            {
                //这就是客户端的Socket实例,我们后续可以将其保存起来
                var client = socket.EndAccept(ar);

                //给客户端发送一个欢迎消息
                client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));


                //实现每隔两秒钟给服务器发一个消息
                //这里我们使用了一个定时器
                var timer = new System.Timers.Timer();
                timer.Interval = 2000D;
                timer.Enabled = true;
                timer.Elapsed += (o, a) =>
                {
                    //检测客户端Socket的状态
                    if(client.Connected)
                    {
                        try
                        {
                            client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
                        }
                        catch(SocketException ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                    else
                    {
                        timer.Stop();
                        timer.Enabled = false;
                        Console.WriteLine("Client is disconnected, the timer is stop.");
                    }
                };
                timer.Start();


                //接收客户端的消息(这个和在客户端实现的方式是一样的)
                client.BeginReceive(buffer,0,buffer.Length,SocketFlags.None,new AsyncCallback(ReceiveMessage),client);

            }), null);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }

        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;

                //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //读取出来消息内容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //显示消息
                Console.WriteLine(message);

                //接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex){
                Console.WriteLine(ex.Message);
            }
        }
    }
}

可以看出来,为了让服务器可以接受消息,其实并不需要什么特别的设计,与客户端接受消息其实可以是一样的

 

再来看看客户端的修改

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//导入的命名空间
using System.Net.Sockets;

namespace SocketClient
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个Socket
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //连接到指定服务器的指定端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
            socket.Connect("localhost", 4530);
            Console.WriteLine("connect to the server");

            //实现接受消息的方法

            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);

            //接受用户输入,将消息发送给服务器端
            while(true)
            {
                var message = "Message from client : " + Console.ReadLine();
                var outputBuffer = Encoding.Unicode.GetBytes(message);
                socket.BeginSend(outputBuffer, 0, outputBuffer.Length, SocketFlags.None, null, null);
            }

        }


        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;

                //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //读取出来消息内容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //显示消息
                Console.WriteLine(message);

                //接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

我在这里做了一个死循环,用户可以不断地输入,这些消息会被发送给服务器。如下图所示

image

【备注】因为服务器每隔两秒钟会发送新消息过来,所以在输入的时候,动作要稍快一点啦

 

本文最后探讨一个问题,就是如何让我们的服务器可以支持多个客户端

 

第九步:支持多个客户端

这个步骤只需要修改服务端程序即可

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //将该socket绑定到主机上面的某个端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //启动监听,并且设置一个最大的队列长度
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //开始接受客户端连接请求
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }


        public static void ClientAccepted(IAsyncResult ar)
        {

            var socket = ar.AsyncState as Socket;

            //这就是客户端的Socket实例,我们后续可以将其保存起来
            var client = socket.EndAccept(ar);

            //给客户端发送一个欢迎消息
            client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at " + DateTime.Now.ToString()));


            //实现每隔两秒钟给服务器发一个消息
            //这里我们使用了一个定时器
            var timer = new System.Timers.Timer();
            timer.Interval = 2000D;
            timer.Enabled = true;
            timer.Elapsed += (o, a) =>
            {
                //检测客户端Socket的状态
                if(client.Connected)
                {
                    try
                    {
                        client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
                    }
                    catch(SocketException ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                else
                {
                    timer.Stop();
                    timer.Enabled = false;
                    Console.WriteLine("Client is disconnected, the timer is stop.");
                }
            };
            timer.Start();


            //接收客户端的消息(这个和在客户端实现的方式是一样的)
            client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), client);

            //准备接受下一个客户端请求
            socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
        }

        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {

            try
            {
                var socket = ar.AsyncState as Socket;

                //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //读取出来消息内容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //显示消息
                Console.WriteLine(message);

                //接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex){
                Console.WriteLine(ex.Message);
            }
        }
    }
}

最后调试起来看到的效果如下图

image

 

本文源代码,可以通过这里下载 http://files.cnblogs.com/chenxizhang/SocketWorkshop.rar

本文将在这个案例的基础上,加入一个特殊场景,利用Silverlight来实现客户端。有的朋友可能会说,其实是一样的吧。请不要急于下结论,有用过Silverlight的朋友都有这种体会,很多在标准.NET Framework编程中能用的技术,到了Silverlight里面,或多或少会有些限制。不幸的是,Socket这个功能就是其中一个。这本身没有什么好不好的问题,Silverlight首先是运行在一个特殊的沙盒中,受到一些限制也是意料之中的,毕竟安全第一嘛

 

我总结Silverlight中应用Socket的几点特殊之处

1.所有的操作都必须的异步的,包括连接,发送和接收消息

2.Silverlight只能做客户端,不能做服务器(虽然这句看起来说的有点多余,不过确实有朋友想这么做呢)

3.Silverlight的Socket只能访问如下端口,4502-4530,只能用TCP。

4.Silverlight的Socket收到访问策略的限制,服务端必须监听,并提供ClientAccessPolicy的支持。通常是在943端口(TCP)进行监听,也可以在HTTP 80端口监听。

本文完整代码如下 http://files.cnblogs.com/chenxizhang/SocketWorkshop(with-silverlight).rar

 

 

那么,我们就来通过例子学习一下在Silverlight中如何使用Socket技术与服务端通讯吧

第一步:创建Silverlight项目

image

image

第二步:设计Silverlight界面

<UserControl
    x:Class="SocketSilverlightClient.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid
        x:Name="LayoutRoot"
        Background="White"
        Margin="20">

        <Grid.Resources>
            <Style
                TargetType="Button">
                <Setter
                    Property="Width"
                    Value="100"></Setter>
                <Setter
                    Property="HorizontalAlignment"
                    Value="Left"></Setter>
                <Setter
                    Property="Margin"
                    Value="5"></Setter>
            </Style>

            <Style
                TargetType="TextBlock">
                <Setter
                    Property="Margin"
                    Value="5"></Setter>
                <Setter
                    Property="HorizontalAlignment"
                    Value="Left"></Setter>
                <Setter
                    Property="TextWrapping"
                    Value="Wrap"></Setter>
            </Style>
        </Grid.Resources>

        <StackPanel>
            <Button
                Content="Connect"
                x:Name="btConnect"
                Click="btConnect_Click"></Button>

            <TextBlock
                Text="Type your message"></TextBlock>

            <StackPanel
                Margin="5"
                Orientation="Horizontal">
                <TextBox
                    x:Name="txtInput"
                    Width="200"></TextBox>
                <Button
                    Content="Send"
                    x:Name="btSend"
                    Click="btSend_Click"></Button>
            </StackPanel>


            <TextBlock
                Text="Messages from server"></TextBlock>

            <ItemsControl
                Margin="5"
                x:Name="icResult">
            </ItemsControl>
        </StackPanel>

    </Grid>
</UserControl>

这个界面看起来像下面这样

image

 

第三步:编写基本的客户端代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

//导入命名空间
using System.Net.Sockets;

namespace SocketSilverlightClient
{
    /// <summary>
    /// 演示如何在Silverlight中使用Socket技术
    /// 作者:陈希章
    /// </summary>
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 尝试连接到服务器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btConnect_Click(object sender, RoutedEventArgs e)
        {
            //创建一个套接字
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //准备一个异步参数(这是特有的)
            var args = new SocketAsyncEventArgs();
            //设置远程服务器地址,这里用DnsSafeHost,可以获取到宿主远程服务器的主机名称
            args.RemoteEndPoint = new DnsEndPoint(App.Current.Host.Source.DnsSafeHost, 4530);
            //注册Completed事件处理程序
            args.Completed += (o, a) =>
            {
                if(a.SocketError > 0)//0表示成功,其他的表示有错误
                {
                    //注意,因为Completed方法是在工作线程触发的,所以要对主线程进行访问,必须使用Dispatcher机制
                    this.Dispatcher.BeginInvoke(() =>
                    {
                        MessageBox.Show("Connect fail:" + a.SocketError.ToString());
                    });
                }
                else
                {

                    this.Dispatcher.BeginInvoke(() =>
                    {
                        MessageBox.Show("Connect success");
                    });
                }

            };

            //发起异步的连接请求
            socket.ConnectAsync(args);
        }

        private void btSend_Click(object sender, RoutedEventArgs e)
        {

        }
    }
}

【注意】在Silverlight中使用Socket的代码,与一般的客户端还是不同的。最主要的区别在于异步模型。

目前,我这里只编写了Connect的代码,是因为这里就会遇到连接问题,其他代码先不着急写出来。我们可以运行起来看看

image

点击“Connect”之后,我们发现有一个错误,是AccessDenied。这就是说,Silverlight遇到了权限问题无法直接访问到服务器。

我们都知道,Silverlight是运行在一个沙盒里面的,它要访问宿主网站之外的资源,是受到很多限制的。它会先尝试检查目标资源是否有一个ClientAccessPolicy的设置。

这里有一篇详细的介绍 http://msdn.microsoft.com/zh-cn/library/cc197955(VS.95).aspx

 

第四步:为服务器添加PolicyServer。

已经有不少先进同学在这方面有研究了。这个PolicyServer是负责向Silverlight发送策略信息的,也就是说,Silverlight的Socket,在连接之前,会默认去连接目标主机的943端口,请求ClientAccessPolicy的认证,只有通过了,则可以继续访问其他的Socket。

 

这个PolicyServer的设计,不是我的原创,但我稍做了修改。请将下面的代码保存为一个独立的文件,放在SocketServer这个项目里面

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;

namespace SocketServerService
{
    /// <summary>
    /// This is a silverlight socket client access policy file server.
    /// 
    /// Background:
    /// When a socket connection open attempt to some server is made in Silverlight 2.0
    /// Silverlight automatically makes a request to the server in question on port 943 for a policy file
    /// The policy file served includes the valid ports and valid clients for the socket server
    /// 
    /// Outcomes:
    /// The socket request will result in success if the client access policy file served by the socket
    /// server permits access to the requested port and the client URI is in the <allow-from> element
    /// See ClientAccessPolicy.xml & http://msdn.microsoft.com/en-us/library/cc645032(VS.95).aspx for further details
    /// 
    /// The socket request will be denied if the client access policy file is not served or if the client /
    /// port is denied in the client access policy file
    /// </summary>
    class SL_SocketPortPolicyListener
    {
        TcpListener _Listener = null;
        TcpClient _Client = null;
        const string _PolicyRequestString = "<policy-file-request/>";
        int _ReceivedLength = 0;
        byte[] _Policy = null;
        byte[] _ReceiveBuffer = null;
        EventLog eventLog;

        /// <summary>
        /// Initializes a new instance of the <see cref="SL_SocketPortPolicyListener"/> class.
        /// </summary>
        /// <param name="serviceEventLog">The service event log.</param>
        public SL_SocketPortPolicyListener(EventLog serviceEventLog)
        {
            eventLog = serviceEventLog;
            Start();
        }
        /// <summary>
        /// 增加的代码
        /// </summary>
        public SL_SocketPortPolicyListener()
            : this(new EventLog("Application"))
        {

        }

        /// <summary>
        /// Starts this instance.
        /// </summary>
        void Start()
        {
            try
            {
                //增加的代码
                var policyConfig =
                    "<?xml version=\"1.0\" encoding =\"utf-8\"?>" +
                        "<access-policy>" +
                          "<cross-domain-access>" +
                            "<policy>" +
                              "<allow-from>" +
                                "<domain uri=\"*\" />" +
                              "</allow-from>" +
                              "<grant-to>" +
                                "<socket-resource port=\"4502-4530\" protocol=\"tcp\" />" +
                              "</grant-to>" +
                            "</policy>" +
                          "</cross-domain-access>" +
                        "</access-policy>";

                //删除的代码
                //string executionLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                //string policyFile = ConfigurationManager.AppSettings["PolicyFilePath"];
                //using(FileStream fs = new FileStream(executionLocation + policyFile, FileMode.Open))
                //{
                //    _Policy = new byte[fs.Length];
                //    fs.Read(_Policy, 0, _Policy.Length);
                //}

                //增加的代码
                _Policy = Encoding.Default.GetBytes(policyConfig);



                _ReceiveBuffer = new byte[_PolicyRequestString.Length];

                //Using TcpListener which is a wrapper around a Socket
                //Allowed port is 943 for Silverlight sockets policy data
                _Listener = new TcpListener(IPAddress.Any, 943);
                _Listener.Start();
                _Listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null);
            }
            catch(Exception exp)
            {
                LogError(exp);
            }
        }

        /// <summary>
        /// Called when [begin accept].
        /// </summary>
        /// <param name="ar">The ar.</param>
        private void OnBeginAccept(IAsyncResult ar)
        {
            _Client = _Listener.EndAcceptTcpClient(ar);
            _Client.Client.BeginReceive(_ReceiveBuffer, 0, _PolicyRequestString.Length, SocketFlags.None,
                new AsyncCallback(OnReceiveComplete), null);
        }

        /// <summary>
        /// Called when [receive complete].
        /// </summary>
        /// <param name="ar">The ar.</param>
        private void OnReceiveComplete(IAsyncResult ar)
        {
            try
            {
                _ReceivedLength += _Client.Client.EndReceive(ar);
                //See if there's more data that we need to grab
                if(_ReceivedLength < _PolicyRequestString.Length)
                {
                    //Need to grab more data so receive remaining data
                    _Client.Client.BeginReceive(_ReceiveBuffer, _ReceivedLength,
                        _PolicyRequestString.Length - _ReceivedLength,
                        SocketFlags.None, new AsyncCallback(OnReceiveComplete), null);
                    return;
                }

                //Check that <policy-file-request/> was sent from client
                string request = System.Text.Encoding.UTF8.GetString(_ReceiveBuffer, 0, _ReceivedLength);
                if(StringComparer.InvariantCultureIgnoreCase.Compare(request, _PolicyRequestString) != 0)
                {
                    //Data received isn't valid so close
                    _Client.Client.Close();
                    return;
                }
                //Valid request received....send policy file
                _Client.Client.BeginSend(_Policy, 0, _Policy.Length, SocketFlags.None,
                    new AsyncCallback(OnSendComplete), _Client.Client);
            }
            catch(Exception exp)
            {
                _Client.Client.Close();
                LogError(exp);
            }
            _ReceivedLength = 0;
            //listen for the next client
            _Listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null);
        }

        /// <summary>
        /// Called when [send complete].
        /// </summary>
        /// <param name="ar">The ar.</param>
        private void OnSendComplete(IAsyncResult ar)
        {
            Socket socket = (Socket)ar.AsyncState;
            try
            {
                socket.EndSend(ar);
            }
            catch(Exception exp)
            {
                LogError(exp);
            }
            finally
            {
                socket.Close();
            }
        }

        /// <summary>
        /// Logs the error.
        /// </summary>
        /// <param name="exp">The exp.</param>
        private void LogError(Exception exp)
        {
            eventLog.WriteEntry(string.Format("Error in PolicySocketServer: {0} \r\n StackTrace: {1}", exp.Message, exp.StackTrace));
        }
    }
}

 

然后,在SocketServer的主程序中,加入下面的代码(只需要添加红色这一行即可

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;

namespace SocketServer
{
    class Program
    {
        /// <summary>
        /// Socket Server 演示
        /// 作者:陈希章
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {

            var policyServer = new SocketServerService.SL_SocketPortPolicyListener();


            //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //将该socket绑定到主机上面的某个端口
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
            socket.Bind(new IPEndPoint(IPAddress.Any, 4530));

            //启动监听,并且设置一个最大的队列长度
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
            socket.Listen(4);

            //开始接受客户端连接请求
            //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
            socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);


            Console.WriteLine("Server is ready!");
            Console.Read();
        }


        public static void ClientAccepted(IAsyncResult ar)
        {

            var socket = ar.AsyncState as Socket;

            //这就是客户端的Socket实例,我们后续可以将其保存起来
            var client = socket.EndAccept(ar);

            //给客户端发送一个欢迎消息
            client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at " + DateTime.Now.ToString()));


            //实现每隔两秒钟给服务器发一个消息
            //这里我们使用了一个定时器
            var timer = new System.Timers.Timer();
            timer.Interval = 2000D;
            timer.Enabled = true;
            timer.Elapsed += (o, a) =>
            {
                //检测客户端Socket的状态
                if(client.Connected)
                {
                    try
                    {
                        client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
                    }
                    catch(SocketException ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                else
                {
                    timer.Stop();
                    timer.Enabled = false;
                    Console.WriteLine("Client is disconnected, the timer is stop.");
                }
            };
            timer.Start();


            //接收客户端的消息(这个和在客户端实现的方式是一样的)
            client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), client);

            //准备接受下一个客户端请求
            socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
        }

        static byte[] buffer = new byte[1024];

        public static void ReceiveMessage(IAsyncResult ar)
        {

            try
            {
                var socket = ar.AsyncState as Socket;

                //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
                var length = socket.EndReceive(ar);
                //读取出来消息内容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //显示消息
                Console.WriteLine(message);

                //接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch(Exception ex){
                Console.WriteLine(ex.Message);
            }
        }
    }
}

再次测试,我们就发现Silverlight客户端能够连接到服务器了

image

 

既然连接上了服务器,那么就让我们来将Silverlight客户端里面其他的一些功能都实现一下吧

 

第五步:实现Silverlight客户端的消息收发

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

//导入命名空间
using System.Net.Sockets;
using System.Text;

namespace SocketSilverlightClient
{
    /// <summary>
    /// 演示如何在Silverlight中使用Socket技术
    /// 作者:陈希章
    /// </summary>
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }


        //创建一个套接字
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


        /// <summary>
        /// 尝试连接到服务器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btConnect_Click(object sender, RoutedEventArgs e)
        {
            //准备一个异步参数(这是特有的)
            var args = new SocketAsyncEventArgs();
            //设置远程服务器地址,这里用DnsSafeHost,可以获取到宿主远程服务器的主机名称
            args.RemoteEndPoint = new DnsEndPoint(App.Current.Host.Source.DnsSafeHost, 4530);
            //注册Completed事件处理程序
            args.Completed += ConnectCompleted;

            //发起异步的连接请求
            socket.ConnectAsync(args);
        }


        /// <summary>
        /// 该事件在连接成功时发生
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void ConnectCompleted(object sender, SocketAsyncEventArgs e)
        {
            if(e.SocketError > 0)//0表示成功,其他的表示有错误
            {
                //注意,因为Completed方法是在工作线程触发的,所以要对主线程进行访问,必须使用Dispatcher机制
                this.Dispatcher.BeginInvoke(() =>
                {
                    MessageBox.Show("Connect fail:" + e.SocketError.ToString());
                });
            }
            else
            {
                this.Dispatcher.BeginInvoke(() =>
                {
                    //MessageBox.Show("Connect success");

                    //将连接按钮禁用掉
                    btConnect.Content = "Connected";
                    btConnect.IsEnabled = false;


                    var buffer = new byte[1024];
                    e.SetBuffer(buffer, 0, buffer.Length);
                    e.Completed -= ConnectCompleted;
                    e.Completed += ReceiveCompleted;
                    socket.ReceiveAsync(e);
                });
            }
        }

        /// <summary>
        /// 该事件在接收消息时发生
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            //将消息显示在界面上
            var result = Encoding.Unicode.GetString(e.Buffer, 0, e.Count);
            this.Dispatcher.BeginInvoke(() =>
            {
                icResult.Items.Add(result);
            });
            //递归继续接收消息
            socket.ReceiveAsync(e);
        }



        private void btSend_Click(object sender, RoutedEventArgs e)
        {
            var args = new SocketAsyncEventArgs();
            //将用户输入的文本转成字节
            var buffer = Encoding.Unicode.GetBytes(txtInput.Text);
            args.SetBuffer(buffer, 0, buffer.Length);
            //设置远程服务器地址,这里用DnsSafeHost,可以获取到宿主远程服务器的主机名称
            args.RemoteEndPoint = new DnsEndPoint(App.Current.Host.Source.DnsSafeHost, 4530);

            //发送完成的话,将控件清空,激活
            args.Completed += (o, a) => {
                this.Dispatcher.BeginInvoke(() =>
                {
                    txtInput.Text = string.Empty;
                    btSend.IsEnabled = true;
                });
            };
            //禁用按钮
            btSend.IsEnabled = false;
            //发送消息
            socket.SendAsync(args);
        }
    }
}

运行起来看看吧

image

image

 

还不错对吧,这个例子给大家演示了如何在Silverlight中使用Socket,接下来大家可以结合自己的现实工作做一些研究和扩展吧

本文完整代码如下 http://files.cnblogs.com/chenxizhang/SocketWorkshop(with-silverlight).rar


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值