稳扎稳打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
Main.cs
2、Socket服务端(聊天室的服务端)
ClientSocketPacket.cs
Main.cs
3、Socket客户端(聊天室的客户端)
SocketClient.xaml
SocketClient.xaml.cs
OK
[源码下载]
[源码下载]
稳扎稳打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>
<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";
}
}
}
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;
}
}
}
}
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";
}
}
}
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>
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();
}
}
}
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
[源码下载]