2012年9月13日星期四

与众不同 windows phone (30)

与众不同 windows phone (30)

[源码下载]


与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室



作者:webabcd


介绍
与众不同 windows phone 7.5 (sdk 7.1) 之通信

  • 实例 - 基于 Socket TCP 开发一个多人聊天室



示例
1、服务端
ClientSocketPacket.cs

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace SocketServerTcp{    /// <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[64];                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

/* * Socket TCP 聊天室的服务端 */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 SocketServerTcp{    public partial class Main : Form    {        SynchronizationContext _syncContext;        System.Timers.Timer _timer;        // 信息结束符,用于判断是否完整地读取了客户端发过来的信息,要与客户端的信息结束符相对应(本例只用于演示,实际项目中请用自定义协议)        private string _endMarker = "^";        // 服务端监听的 socket        private Socket _listener;        // 实例化 ManualResetEvent,设置其初始状态为无信号        private ManualResetEvent _signal = 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(LaunchSocketServer));            thread.IsBackground = true;            thread.Start();        }        private void LaunchSocketServer()        {            // 每 10 秒运行一次计时器所指定的方法,群发信息            _timer = new System.Timers.Timer();            _timer.Interval = 10000d;            _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);            _timer.Start();            // TCP 方式监听 3366 端口            _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            _listener.Bind(new IPEndPoint(IPAddress.Any, 3366));            // 指定等待连接队列中允许的最大数            _listener.Listen(10);            while (true)            {                // 设置为无信号                _signal.Reset();                // 开始接受客户端传入的连接                _listener.BeginAccept(new AsyncCallback(OnClientConnect), null);                // 阻塞当前线程,直至有信号为止                _signal.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)        {            ClientSocketPacket client = new ClientSocketPacket();            // 完成接受客户端传入的连接的这个异步操作,并返回客户端连入的 socket            client.Socket = _listener.EndAccept(async);            // 将客户端连入的 Socket 放进客户端 Socket 列表            _clientList.Add(client);            OutputMessage(((IPEndPoint)client.Socket.LocalEndPoint).Address + " 连入了服务器");            SendData("一个新的客户端已经成功连入服务器。。。 【信息来自服务端】");            try            {                // 开始接收客户端传入的数据                client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(OnDataReceived), client);            }            catch (SocketException ex)            {                // 处理异常                HandleException(client, ex);            }            // 设置为有信号            _signal.Set();        }        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 字节,英文等字符与 ASCII 相同            foreach (byte b in client.Buffer.Take(count))            {                if (b == 0) continue; // 如果是空字节则不做处理('\0')                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);                OutputMessage(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            {                // 完成将信息发送到客户端的这个异步操作                int sentBytesCount = 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 列表            OutputMessage(client.Socket.RemoteEndPoint.ToString() + " - " + ex.Message);            client.Socket.Close();            _clientList.Remove(client);        }        // 在 UI 上输出指定信息        private void OutputMessage(string data)        {            _syncContext.Post((p) => { txtMsg.Text += p.ToString() + "\r\n"; }, data);        }    }}


2、客户端
TcpDemo.xaml

<phone:PhoneApplicationPage     x:    ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    ="http://schemas.microsoft.com/winfx/2006/xaml"    ="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"    ="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"    ="http://schemas.microsoft.com/expression/blend/2008"    ="http://schemas.open    FontFamily="{StaticResource PhoneFontFamilyNormal}"    FontSize="{StaticResource PhoneFontSizeNormal}"    Foreground="{StaticResource PhoneForegroundBrush}"    SupportedOrientations="Portrait" Orientation="Portrait"    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"    shell:SystemTray.IsVisible="True">    <Grid x:Name="LayoutRoot" Background="Transparent">        <StackPanel HorizontalAlignment="Left">            <ScrollViewer x:Name="svChat" Height="400">                <TextBlock x:Name="txtChat" TextWrapping="Wrap" />            </ScrollViewer>                        <TextBox x:Name="txtName" />            <TextBox x:Name="txtInput" KeyDown="txtInput_KeyDown" />            <Button x:Name="btnSend" Content="发送" Click="btnSend_Click" />        </StackPanel>    </Grid></phone:PhoneApplicationPage>

TcpDemo.xaml.cs

/* * Socket TCP 聊天室的客户端 */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 Microsoft.Phone.Controls;using System.Net.Sockets;using System.Text;// 此命名空间下有 Socket 的扩展方法 GetCurrentNetworkInterface()using Microsoft.Phone.Net.NetworkInformation; namespace Demo.Communication.SocketClient{    public partial class TcpDemo : PhoneApplicationPage    {        // 信息结束符,用于判断是否完整地读取了服务端发过来的信息,要与服务端的信息结束符相对应(本例只用于演示,实际项目中请用自定义协议)        private string _endMarker = "^";        // 客户端 Socket        private Socket _socket;        // 用于发送数据到服务端的 Socket 异步操作对象        private SocketAsyncEventArgs _socketAsyncSend;        // 用于接收数据的 Socket 异步操作对象        private SocketAsyncEventArgs _socketAsyncReceive;        public TcpDemo()        {            InitializeComponent();            this.Loaded += new RoutedEventHandler(TcpDemo_Loaded);        }        void TcpDemo_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);            // 设置 Socket 连接的首选网络类型 NetworkSelectionCharacteristics.Cellular 或 NetworkSelectionCharacteristics.NonCellular            _socket.SetNetworkPreference(NetworkSelectionCharacteristics.NonCellular);            // 强制 Socket 连接的网络类型 NetworkSelectionCharacteristics.Cellular 或 NetworkSelectionCharacteristics.NonCellular(不指定的话则均可)            // 如果无法使用强制要求的网络类型,则在 OnSocketConnectCompleted 会收到 SocketError.NetworkDown             _socket.SetNetworkRequirement(NetworkSelectionCharacteristics.NonCellular);            // 实例化 SocketAsyncEventArgs ,用于对 Socket 做异步操作,很方便            _socketAsyncReceive = new SocketAsyncEventArgs();            // 服务器的 EndPoint            _socketAsyncReceive.RemoteEndPoint = new DnsEndPoint("192.168.8.217", 3366);            // 异步操作完成后执行的事件            _socketAsyncReceive.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);            // 异步连接服务端            _socket.ConnectAsync(_socketAsyncReceive);        }        private void OnSocketConnectCompleted(object sender, SocketAsyncEventArgs e)        {            if (e.SocketError != SocketError.Success)            {                OutputMessage("Socket 连接错误:" + e.SocketError.ToString());                return;            }            // 设置数据缓冲区            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 对象,用于用户向服务端发送消息            _socketAsyncSend = new SocketAsyncEventArgs();            _socketAsyncSend.RemoteEndPoint = e.RemoteEndPoint;            if (_socket.Connected)            {                OutputMessage("成功地连接上了服务器。。。");                // Socket 有一个扩展方法 GetCurrentNetworkInterface(),需要引用命名空间 Microsoft.Phone.Net.NetworkInformation                // GetCurrentNetworkInterface() 会返回当前 Socket 连接的 NetworkInterfaceInfo 对象(NetworkInterfaceInfo 的详细说明参见:Device/Status/NetworkStatus.xaml.cs)                NetworkInterfaceInfo nii = _socket.GetCurrentNetworkInterface();                OutputMessage("网络接口的类型:" + nii.InterfaceType.ToString());            }            else            {                OutputMessage("无法连接到服务器。。。请刷新后再试。。。");            }        }        private void OnSocketReceiveCompleted(object sender, SocketAsyncEventArgs e)        {            try            {                // 将接收到的数据转换为字符串                string data = UTF8Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);                OutputMessage(data);            }            catch (Exception ex)            {                OutputMessage(ex.ToString());            }            // 继续异步地从服务端接收数据            _socket.ReceiveAsync(e);        }        private void OutputMessage(string data)        {            // 在聊天文本框中输出指定的信息,并将滚动条滚到底部            this.Dispatcher.BeginInvoke(                delegate                {                    txtChat.Text += data + "\r\n";                    svChat.ScrollToVerticalOffset(txtChat.ActualHeight - svChat.Height);                }            );        }        private void SendData()        {            if (_socket.Connected)            {                // 设置需要发送的数据的缓冲区                _socketAsyncSend.BufferList =                    new List<ArraySegment<byte>>()                     {                         new ArraySegment<byte>(UTF8Encoding.UTF8.GetBytes(txtName.Text + ":" + txtInput.Text + _endMarker))                     };                // 异步地向服务端发送消息                _socket.SendAsync(_socketAsyncSend);            }            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();                this.Focus();            }        }    }}



OK
[源码下载]


TAG: