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

2016-08-20 10:56:15来源:http://webabcd.blog.51cto.com/1787395/343812作者:webabcd人点击





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[源码下载]

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台