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

简介: 原文:与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室[索引页][源码下载] 与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 作者:webabcd介绍与众不同 windows phone 7.
原文: 与众不同 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
[源码下载]

目录
相关文章
|
4月前
|
网络协议 安全 网络安全
网络编程:基于socket的TCP/IP通信。
网络编程:基于socket的TCP/IP通信。
279 0
|
6月前
|
网络协议 安全 Java
Java网络编程入门涉及TCP/IP协议理解与Socket通信。
【6月更文挑战第21天】Java网络编程入门涉及TCP/IP协议理解与Socket通信。TCP/IP协议包括应用层、传输层、网络层和数据链路层。使用Java的`ServerSocket`和`Socket`类,服务器监听端口,接受客户端连接,而客户端连接指定服务器并交换数据。基础示例展示如何创建服务器和发送消息。进阶可涉及多线程、NIO和安全传输。学习这些基础知识能助你构建网络应用。
53 1
|
6月前
|
开发框架 网络协议 Unix
【嵌入式软件工程师面经】Socket,TCP,HTTP之间的区别
【嵌入式软件工程师面经】Socket,TCP,HTTP之间的区别
69 1
|
2月前
|
网络协议 Linux 网络性能优化
Linux基础-socket详解、TCP/UDP
综上所述,Linux下的Socket编程是网络通信的重要组成部分,通过灵活运用TCP和UDP协议,开发者能够构建出满足不同需求的网络应用程序。掌握这些基础知识,是进行更复杂网络编程任务的基石。
171 1
|
7月前
|
网络协议 Java
Java的Socket编程:TCP/IP与UDP深入探索
Java的Socket编程:TCP/IP与UDP深入探索
112 0
|
4月前
|
网络协议 Java
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
这篇文章全面讲解了基于Socket的TCP网络编程,包括Socket基本概念、TCP编程步骤、客户端和服务端的通信过程,并通过具体代码示例展示了客户端与服务端之间的数据通信。同时,还提供了多个案例分析,如客户端发送信息给服务端、客户端发送文件给服务端以及服务端保存文件并返回确认信息给客户端的场景。
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
|
3月前
|
网络协议 Linux
TCP 和 UDP 的 Socket 调用
【9月更文挑战第6天】
|
4月前
|
负载均衡 网络协议 安全
【Azure 应用服务】Azure Web App的服务(基于Windows 操作系统部署)在被安全漏洞扫描时发现了TCP timestamps漏洞
【Azure 应用服务】Azure Web App的服务(基于Windows 操作系统部署)在被安全漏洞扫描时发现了TCP timestamps漏洞
|
4月前
|
网络协议 应用服务中间件 nginx
性能提升-如何设置Windows操作系统TIME_WAIT状态的TCP连接快速回收时间?
性能提升-如何设置Windows操作系统TIME_WAIT状态的TCP连接快速回收时间?
138 0
|
5月前
|
网络协议 Java
如何在Java中使用Socket编程实现TCP连接?
在Java中,通过Socket编程实现TCP连接非常常见。以下演示了基本的TCP通信流程,可根据具体需求进行扩展。
310 0