天天看點

上接穩紮穩打Silverlight(24) - 2.0通信之Socket, 開發一個多人聊天室

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; 

                                // 完成接收資料的這個異步操作,并傳回接收的位元組數 

                                if (client.Socket.Connected) 

                                        count = client.Socket.EndReceive(async); 

                        // 把接收到的資料添加進收到的位元組集合内 

                        // 本例采用UTF8編碼,中文占用3位元組,英文占用1位元組,緩沖區為32位元組 

                        // 是以如果直接把目前緩沖區轉成字元串的話可能會出現亂碼,是以要等接收完使用者發送的全部資訊後再轉成字元串 

                        foreach (byte b in client.Buffer.Take(count)) 

                                if (b == 0) continue; // 如果是空位元組則不做處理 

                                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); 

                                // 繼續開始接收用戶端傳入的資料 

                                        client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, 0, new AsyncCallback(OnDataReceived), client); 

                /// <summary> 

                /// 發送資料到所有連入的用戶端 

                /// </summary> 

                /// <param name="data">需要發送的資料</param> 

                private void SendData(string data) 

                        byte[] byteData = UTF8Encoding.UTF8.GetBytes(data); 

                        foreach (ClientSocketPacket client in _clientList) 

                                { 

                                        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) 

                                // 完成将資訊發送到用戶端的這個異步操作 

                                        client.Socket.EndSend(async); 

                /// 處理 SocketException 異常 

                /// <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.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; 

namespace Silverlight20.Communication 

        public partial class SocketClient : UserControl 

                // 資訊結束符,用于判斷是否完整地讀取了使用者發送的資訊(要與服務端的資訊結束符相對應) 

                // 用戶端 Socket 

                private Socket _socket; 

                // Socket 異步操作對象 

                private SocketAsyncEventArgs _sendEventArgs; 

                public SocketClient() 

                        this.Loaded += new RoutedEventHandler(Page_Loaded); 

                void Page_Loaded(object sender, RoutedEventArgs e) 

                        // 初始化姓名和需要發送的預設文字 

                        txtName.Text = "匿名使用者" + new Random().Next(0, 9999).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) 

                                // 将接收到的資料轉換為字元串 

                                string data = UTF8Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred); 

                                WriteText(data); 

                        catch (Exception ex) 

                                WriteText(ex.ToString()); 

                        // 繼續異步地從服務端 Socket 接收資料 

                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); 

                                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) 

                        // 按了Enter鍵就向服務端發送資料 

                        if (e.Key == Key.Enter) 

                                SendData(); 

OK

<a href="http://down.51cto.com/data/100302" target="_blank">[源碼下載下傳]</a>

     本文轉自webabcd 51CTO部落格,原文連結:http://blog.51cto.com/webabcd/343812,如需轉載請自行聯系原作者