zoukankan      html  css  js  c++  java
  • 与众不同 windows phone (30) Communication(通信)之基于 Socket TCP 开发一个多人聊天室

    [索引页]
    [源码下载]


    与众不同 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:Class="Demo.Communication.SocketClient.TcpDemo"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        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
    [源码下载]

  • 相关阅读:
    django model:auto_now_add 和 auto_now
    算法(2):数据结构
    Flask(2):登陆验证
    Flask(1):基本示例、配置文件、路由、请求和响应、模板渲染
    linux基础
    算法(1):查找&排序
    利用dispatch_once创建单例
    UIScrollView的属性总结
    ios开发 UITableViewController
    [深入浅出Cocoa]详解键值观察(KVO)及其实现机理
  • 原文地址:https://www.cnblogs.com/webabcd/p/2682764.html
Copyright © 2011-2022 走看看