您的位置:首页 > 其它

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

2011-11-28 15:37 483 查看
[索引页]

[源码下载]

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

作者:webabcd

介绍

Silverlight
2.0 Socket通信。开发一个多人聊天室

服务端:实例化Socket, 绑定, 监听, 连接, 接收数据, 发送数据

客户端:实例化Socket, 指定服务端地址, 连接, 接收数据, 发送数据

在线DEMO

/article/4589581.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 { get; set; }




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


}




try


{


// 继续开始接收客户端传入的数据


if (client.Socket.Connected)


client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, 0, new 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(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)


{


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

[源码下载]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐