穩紮穩打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
[源碼下載]
發佈了6 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章