zoukankan      html  css  js  c++  java
  • 稳扎稳打Silverlight(24) 2.0通信之Socket, 开发一个多人聊天室

    [索引页]
    [源码下载]


    稳扎稳打Silverlight(24) - 2.0通信之Socket, 开发一个多人聊天室


    作者:webabcd


    介绍
    Silverlight 2.0 Socket通信。开发一个多人聊天室
        服务端:实例化Socket, 绑定, 监听, 连接, 接收数据, 发送数据
        客户端:实例化Socket, 指定服务端地址, 连接, 接收数据, 发送数据


    在线DEMO
    http://www.cnblogs.com/webabcd/archive/2008/10/09/1307486.html


    示例
    1、Policy服务(向客户端发送策略文件的服务)
    clientaccesspolicy.xml
    <?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-4534" protocol="tcp" />
                
    </grant-to>
            
    </policy>
        
    </cross-domain-access>
    </access-policy>

    Main.cs
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;

    using System.Net.Sockets;
    using System.IO;
    using System.Net;

    namespace PolicyServer
    {
        
    public partial class Main : Form
        
    {
            
    // 客户端 socket 发送到服务端的对策略文件的请求信息
            private readonly string _policyRequestString = "<policy-file-request/>";

            
    private Socket _listener; // 服务端监听的 socket
            private byte[] _policyBuffer; // 服务端策略文件的 buffer
            private byte[] _requestBuffer; // 客户端 socket 发送的请求信息的 buffer

            
    private int _received; // 接收到的信息字节数

            
    private bool _flag = false// 标志位。服务端是否要处理传入的连接

            System.Threading.SynchronizationContext _syncContext;

            
    public Main()
            
    {
                InitializeComponent();

                _flag 
    = true;

                lblStatus.Text 
    = "PolicyServer状态:启动";
                lblStatus.ForeColor 
    = Color.Green;

                
    // 启动 PolicyServer
                StartupPolicyServer();

                
    // UI 线程
                _syncContext = System.Threading.SynchronizationContext.Current;
            }


            
    private void btnStartup_Click(object sender, EventArgs e)
            
    {
                _flag 
    = true;

                lblStatus.Text 
    = "PolicyServer状态:启动";
                lblStatus.ForeColor 
    = Color.Green;
            }


            
    private void btnPause_Click(object sender, EventArgs e)
            
    {
                _flag 
    = false;

                lblStatus.Text 
    = "PolicyServer状态:暂停";
                lblStatus.ForeColor 
    = Color.Red;
            }


            
    /// <summary>
            
    /// 启动 PolicyServer
            
    /// </summary>

            private void StartupPolicyServer()
            
    {
                
    string policyFile = Path.Combine(Application.StartupPath, "clientaccesspolicy.xml");

                
    using (FileStream fs = new FileStream(policyFile, FileMode.Open, FileAccess.Read))
                
    {
                    
    // 将策略文件的内容写入 buffer
                    _policyBuffer = new byte[fs.Length];
                    fs.Read(_policyBuffer, 
    0, _policyBuffer.Length);
                }


                
    // 初始化 socket , 然后与端口绑定, 然后对端口进行监听
                _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _listener.Bind(
    new IPEndPoint(IPAddress.Any, 943)); // socket 请求策略文件使用 943 端口
                _listener.Listen(100);

                
    // 开始接受客户端传入的连接
                _listener.BeginAccept(new AsyncCallback(OnClientConnect), null);
            }


            
    private void OnClientConnect(IAsyncResult result)
            
    {
                
    if (!_flag)
                
    {
                    
    // PolicyServer 停用的话,则不再处理传入的连接
                    _listener.BeginAccept(new AsyncCallback(OnClientConnect), null);
                    
    return;
                }

                     
                Socket client; 
    // 客户端发过来的 socket

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

                
    catch (SocketException)
                
    {
                    
    return;
                }


                _requestBuffer 
    = new byte[_policyRequestString.Length];
                _received 
    = 0;

                
    try
                
    {
                    
    // 开始接收客户端传入的数据
                    client.BeginReceive(_requestBuffer, 0, _policyRequestString.Length, SocketFlags.None, new AsyncCallback(OnReceive), client);
                }

                
    catch (SocketException)
                
    {
                    
    // socket 出错则关闭客户端 socket
                    client.Close();
                }


                
    // 继续开始接受客户端传入的连接
                _listener.BeginAccept(new AsyncCallback(OnClientConnect), null);
            }



            
    private void OnReceive(IAsyncResult result)
            
    {
                Socket client 
    = result.AsyncState as Socket;

                
    try
                
    {
                    
    // 完成接收数据的这个异步操作,并计算累计接收的数据的字节数
                    _received += client.EndReceive(result);

                    
    if (_received < _policyRequestString.Length)
                    
    {
                        
    // 没有接收到完整的数据,则继续开始接收
                        client.BeginReceive(_requestBuffer, _received, _policyRequestString.Length - _received, SocketFlags.None, new AsyncCallback(OnReceive), client);
                        
    return;
                    }


                    
    // 把接收到的数据转换为字符串
                    string request = System.Text.Encoding.UTF8.GetString(_requestBuffer, 0, _received);

                    
    if (StringComparer.InvariantCultureIgnoreCase.Compare(request, _policyRequestString) != 0)
                    
    {
                        
    // 如果接收到的数据不是“<policy-file-request/>”,则关闭客户端 socket
                        client.Close();
                        
    return;
                    }


                    
    // 开始向客户端发送策略文件的内容
                    client.BeginSend(_policyBuffer, 0, _policyBuffer.Length, SocketFlags.None, new AsyncCallback(OnSend), client);
                }


                
    catch (SocketException)
                
    {
                    
    // socket 出错则关闭客户端 socket
                    client.Close();
                }

            }


            
    private void OnSend(IAsyncResult result)
            
    {
                Socket client 
    = result.AsyncState as Socket;

                
    try
                
    {
                    
    // 完成将信息发送到客户端的这个异步操作
                    client.EndSend(result);

                    
    // 获取客户端的ip地址及端口号,并显示
                    _syncContext.Post(ResultCallback, client.LocalEndPoint.ToString());
                }

                
    finally
                
    {
                    
    // 关闭客户端 socket
                    client.Close();
                }

            }


            
    void ResultCallback(object result)
            
    {
                
    // 输出客户端的ip地址及端口号
                txtMsg.Text += result.ToString() + "\r\n";
            }

        }

    }



    2、Socket服务端(聊天室的服务端)
    ClientSocketPacket.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace SocketServer
    {
        
    /// <summary>
        
    /// 对客户端 Socket 及其他相关信息做一个封装
        
    /// </summary>

        public class ClientSocketPacket
        
    {
            
    /// <summary>
            
    /// 客户端 Socket
            
    /// </summary>

            public System.Net.Sockets.Socket Socket getset; }

            
    private byte[] _buffer;
            
    /// <summary>
            
    /// 为该客户端 Socket 开辟的缓冲区
            
    /// </summary>

            public byte[] Buffer
            
    {
                
    get
                
    {
                    
    if (_buffer == null)
                        _buffer 
    = new byte[32];

                    
    return _buffer;
                }

            }


            
    private List<byte> _receivedByte;
            
    /// <summary>
            
    /// 客户端 Socket 发过来的信息的字节集合
            
    /// </summary>

            public List<byte> ReceivedByte
            
    {
                
    get
                
    {
                    
    if (_receivedByte == null)
                        _receivedByte 
    = new List<byte>();

                    
    return _receivedByte;
                }

            }

        }

    }


    Main.cs
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;

    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    using System.IO;

    namespace SocketServer
    {
        
    public partial class Main : Form
        
    {
            SynchronizationContext _syncContext;

            System.Timers.Timer _timer;

            
    // 信息结束符,用于判断是否完整地读取了用户发送的信息(要与客户端的信息结束符相对应)
            private string _endMarker = "^";

            
    // 服务端监听的 socket
            private Socket _listener;

            
    // 实例化 ManualResetEvent, 设置其初始状态为非终止状态(可入状态)
            private ManualResetEvent _connectDone = new ManualResetEvent(false);

            
    // 客户端 Socket 列表
            private List<ClientSocketPacket> _clientList = new List<ClientSocketPacket>();

            
    public Main()
            
    {
                InitializeComponent();

                
    // UI 线程
                _syncContext = SynchronizationContext.Current;

                
    // 启动后台线程去运行 Socket 服务
                Thread thread = new Thread(new ThreadStart(StartupSocketServer));
                thread.IsBackground 
    = true;
                thread.Start();
            }


            
    private void StartupSocketServer()
            
    {
                
    // 每 10 秒运行一次计时器所指定的方法
                _timer = new System.Timers.Timer();
                _timer.Interval 
    = 10000d;
                _timer.Elapsed 
    += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
                _timer.Start();

                
    // 初始化 socket , 然后与端口绑定, 然后对端口进行监听
                _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _listener.Bind(
    new IPEndPoint(IPAddress.Any, 4518)); // Silverlight 2.0 使用 Socket 只能连接 4502-4534 端口
                _listener.Listen(100);


                
    while (true)
                
    {
                    
    // 重置 ManualResetEvent,由此线程来控制 ManualResetEvent,其它到这里来的线程请等待
                    
    // 为求简单易懂,本例实际上只有主线程会在这里循环运行
                    _connectDone.Reset();

                    
    // 开始接受客户端传入的连接
                    _listener.BeginAccept(new AsyncCallback(OnClientConnect), null);

                    
    // 阻止当前线程,直到当前 ManualResetEvent 调用 Set 发出继续信号
                    _connectDone.WaitOne();
                }

            }


            
    private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
            
    {
                
    // 每 10 秒给所有连入的客户端发送一次消息
                SendData(string.Format("webabcd 对所有人说:大家好! 【信息来自服务端 {0}】", DateTime.Now.ToString("hh:mm:ss")));
            }


            
    private void OnClientConnect(IAsyncResult async)
            
    {
                
    // 当前 ManualResetEvent 调用 Set 以发出继续信号,从而允许继续执行一个或多个等待线程
                _connectDone.Set();

                ClientSocketPacket client 
    = new ClientSocketPacket();
                
    // 完成接受客户端传入的连接的这个异步操作,并返回客户端连入的 socket
                client.Socket = _listener.EndAccept(async);

                
    // 将客户端连入的 Socket 放进客户端 Socket 列表
                _clientList.Add(client);


                SendData(
    "一个新的客户端已经成功连入服务器。。。 【信息来自服务端】");


                
    try
                
    {
                    
    // 开始接收客户端传入的数据
                    client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(OnDataReceived), client);
                }

                
    catch (SocketException ex)
                
    {
                    
    // 处理异常
                    HandleException(client, ex);
                }

            }


            
    private void OnDataReceived(IAsyncResult async)
            
    {
                ClientSocketPacket client 
    = async.AsyncState as ClientSocketPacket;

                
    int count = 0;

                
    try
                
    {
                    
    // 完成接收数据的这个异步操作,并返回接收的字节数
                    if (client.Socket.Connected)
                        count 
    = client.Socket.EndReceive(async);
                }

                
    catch (SocketException ex)
                
    {
                    HandleException(client, ex);
                }


                
    // 把接收到的数据添加进收到的字节集合内
                
    // 本例采用UTF8编码,中文占用3字节,英文占用1字节,缓冲区为32字节
                
    // 所以如果直接把当前缓冲区转成字符串的话可能会出现乱码,所以要等接收完用户发送的全部信息后再转成字符串
                foreach (byte b in client.Buffer.Take(count))
                
    {
                    
    if (b == 0continue// 如果是空字节则不做处理

                    client.ReceivedByte.Add(b);
                }


                
    // 把当前接收到的数据转换为字符串。用于判断是否包含自定义的结束符
                string receivedString = UTF8Encoding.UTF8.GetString(client.Buffer, 0, count);

                
    // 如果该 Socket 在网络缓冲区中没有排队的数据 并且 接收到的数据中有自定义的结束符时
                if (client.Socket.Connected && client.Socket.Available == 0 && receivedString.Contains(_endMarker))
                
    {
                    
    // 把收到的字节集合转换成字符串(去掉自定义结束符)
                    
    // 然后清除掉字节集合中的内容,以准备接收用户发送的下一条信息
                    string content = UTF8Encoding.UTF8.GetString(client.ReceivedByte.ToArray());
                    content 
    = content.Replace(_endMarker, "");
                    client.ReceivedByte.Clear();

                    
    // 发送数据到所有连入的客户端,并在服务端做记录
                    SendData(content);
                    _syncContext.Post(ResultCallback, content);
                }


                
    try
                
    {
                    
    // 继续开始接收客户端传入的数据
                    if (client.Socket.Connected)
                        client.Socket.BeginReceive(client.Buffer, 
    0, client.Buffer.Length, 0new AsyncCallback(OnDataReceived), client);
                }

                
    catch (SocketException ex)
                
    {
                    HandleException(client, ex);
                }

            }


            
    /// <summary>
            
    /// 发送数据到所有连入的客户端
            
    /// </summary>
            
    /// <param name="data">需要发送的数据</param>

            private void SendData(string data)
            
    {
                
    byte[] byteData = UTF8Encoding.UTF8.GetBytes(data);

                
    foreach (ClientSocketPacket client in _clientList)
                
    {
                    
    if (client.Socket.Connected)
                    
    {
                        
    try
                        
    {
                            
    // 如果某客户端 Socket 是连接状态,则向其发送数据
                            client.Socket.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnDataSent), client);
                        }

                        
    catch (SocketException ex)
                        
    {
                            HandleException(client, ex);
                        }

                    }

                    
    else 
                    
    {
                        
    // 某 Socket 断开了连接的话则将其关闭,并将其清除出客户端 Socket 列表
                        
    // 也就是说每次向所有客户端发送消息的时候,都会从客户端 Socket 集合中清除掉已经关闭了连接的 Socket
                        client.Socket.Close();
                        _clientList.Remove(client);
                    }

                }

            }


            
    private void OnDataSent(IAsyncResult async)
            
    {
                ClientSocketPacket client 
    = async.AsyncState as ClientSocketPacket;

                
    try
                
    {
                    
    // 完成将信息发送到客户端的这个异步操作
                    if (client.Socket.Connected)
                        client.Socket.EndSend(async);
                }

                
    catch (SocketException ex)
                
    {
                    HandleException(client, ex);
                }

            }


            
    /// <summary>
            
    /// 处理 SocketException 异常
            
    /// </summary>
            
    /// <param name="client">导致异常的 ClientSocketPacket</param>
            
    /// <param name="ex">SocketException</param>

            private void HandleException(ClientSocketPacket client, SocketException ex)
            
    {
                
    // 在服务端记录异常信息,关闭导致异常的 Socket,并将其清除出客户端 Socket 列表
                _syncContext.Post(ResultCallback, client.Socket.RemoteEndPoint.ToString() + " - " + ex.Message);
                client.Socket.Close();
                _clientList.Remove(client);
            }


            
    private void ResultCallback(object result)
            
    {
                
    // 输出相关信息
                txtMsg.Text += result.ToString() + "\r\n";
            }

        }

    }


    3、Socket客户端(聊天室的客户端)
    SocketClient.xaml
    <UserControl x:Class="Silverlight20.Communication.SocketClient"
        xmlns
    ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x
    ="http://schemas.microsoft.com/winfx/2006/xaml">
        
    <StackPanel HorizontalAlignment="Left" Width="600" Margin="5" Background="Gray">

            
    <ScrollViewer x:Name="scrollChat" Height="400" VerticalScrollBarVisibility="Auto" Background="White" Margin="10">
                
    <TextBlock x:Name="txtChat" TextWrapping="Wrap" />
            
    </ScrollViewer>

            
    <StackPanel Orientation="Horizontal" Margin="5">
                
    <TextBox x:Name="txtName" Margin="5" Width="100" />
                
    <TextBox x:Name="txtInput" Margin="5" Width="400" KeyDown="txtInput_KeyDown" />
                
    <Button x:Name="btnSend" Margin="5" Width="60" Content="Send" Click="btnSend_Click"/>
            
    </StackPanel>

        
    </StackPanel>
    </UserControl>

    SocketClient.xaml.cs
    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 Silverlight20.Communication
    {
        
    public partial class SocketClient : UserControl
        
    {
            
    // 信息结束符,用于判断是否完整地读取了用户发送的信息(要与服务端的信息结束符相对应)
            private string _endMarker = "^";

            
    // 客户端 Socket
            private Socket _socket;

            
    // Socket 异步操作对象
            private SocketAsyncEventArgs _sendEventArgs;

            
    public SocketClient()
            
    {
                InitializeComponent();

                
    this.Loaded += new RoutedEventHandler(Page_Loaded);
            }


            
    void Page_Loaded(object sender, RoutedEventArgs e)
            
    {
                
    // 初始化姓名和需要发送的默认文字
                txtName.Text = "匿名用户" + new Random().Next(09999).ToString().PadLeft(4'0');
                txtInput.Text 
    = "hi";

                
    // 实例化 Socket
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                
    // 实例化 SocketAsyncEventArgs ,用于对 Socket 做异步操作,很方便
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                
    // 服务器的 EndPoint
                args.RemoteEndPoint = new DnsEndPoint("wanglei-pc"4518);
                
    // 异步操作完成后执行的事件
                args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);

                
    // 异步连接服务端
                _socket.ConnectAsync(args);
            }


            
    private void OnSocketConnectCompleted(object sender, SocketAsyncEventArgs e)
            
    {
                
    // 设置数据缓冲区
                byte[] response = new byte[1024];
                e.SetBuffer(response, 
    0, response.Length);

                
    // 修改 SocketAsyncEventArgs 对象的异步操作完成后需要执行的事件
                e.Completed -= new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
                e.Completed 
    += new EventHandler<SocketAsyncEventArgs>(OnSocketReceiveCompleted);

                
    // 异步地从服务端 Socket 接收数据
                _socket.ReceiveAsync(e);

                
    // 构造一个 SocketAsyncEventArgs 对象,用于用户向服务端发送消息
                _sendEventArgs = new SocketAsyncEventArgs();
                _sendEventArgs.RemoteEndPoint 
    = e.RemoteEndPoint;

                
    string data = "";
                
    if (!_socket.Connected)
                    data 
    = "无法连接到服务器。。。请刷新后再试。。。";
                
    else
                    data 
    = "成功地连接上了服务器。。。";

                WriteText(data);
            }


            
    private void OnSocketReceiveCompleted(object sender, SocketAsyncEventArgs e)
            
    {
                
    try
                
    {
                    
    // 将接收到的数据转换为字符串
                    string data = UTF8Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);

                    WriteText(data);
                }

                
    catch (Exception ex)
                
    {
                    WriteText(ex.ToString());
                }


                
    // 继续异步地从服务端 Socket 接收数据
                _socket.ReceiveAsync(e);
            }


            
    private void WriteText(string data)
            
    {
                
    // 在聊天文本框中输出指定的信息,并将滚动条滚到底部
                this.Dispatcher.BeginInvoke(
                    
    delegate
                    
    {
                        txtChat.Text 
    += data + "\r\n";
                        scrollChat.ScrollToVerticalOffset(txtChat.ActualHeight);
                    }

                );
            }


            
    private void SendData()
            
    {
                
    if (_socket.Connected)
                
    {
                    
    // 设置需要发送的数据的缓冲区
                    _sendEventArgs.BufferList =
                        
    new List<ArraySegment<byte>>() 
                        

                            
    new ArraySegment<byte>(UTF8Encoding.UTF8.GetBytes(txtName.Text + "" + txtInput.Text + _endMarker)) 
                        }
    ;

                    
    // 异步地向服务端 Socket 发送消息
                    _socket.SendAsync(_sendEventArgs);
                }

                
    else
                
    {
                    txtChat.Text 
    += "无法连接到服务器。。。请刷新后再试。。。\r\n";
                    _socket.Close();
                }


                txtInput.Focus();
                txtInput.Text 
    = "";
            }


            
    private void btnSend_Click(object sender, RoutedEventArgs e)
            
    {
                SendData();
            }


            
    private void txtInput_KeyDown(object sender, KeyEventArgs e)
            
    {
                
    // 按了回车键就向服务端发送数据
                if (e.Key == Key.Enter)
                    SendData();
            }

        }

    }



    OK
    [源码下载]
  • 相关阅读:
    .net core实现的全程序跟踪
    gmap.net
    Spring Cloud实践:降级、限流、滚动、灰度、AB、金丝雀的实现思路
    服务的协作:服务间的消息传递——《微服务设计》读书笔记
    使用消息系统进行微服务间通讯时,如何保证数据一致性
    How to distribute a database among microservices
    微服务间如何选择推送和拉取数据
    Android 怎么使用Bitmap+Canvas 自适应屏幕
    Android 音乐播放器之--错误状态下调用导致的异常
    Android应用截图和SurfaceView截图问题总结
  • 原文地址:https://www.cnblogs.com/webabcd/p/1359551.html
Copyright © 2011-2022 走看看