原文:与众不同 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[]; 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, ));
// 指定等待连接队列中允许的最大数
_listener.Listen(); 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, , 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 = ; 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 == ) continue; // 如果是空字节则不做处理('\0') client.ReceivedByte.Add(b);
} // 把当前接收到的数据转换为字符串。用于判断是否包含自定义的结束符
string receivedString = UTF8Encoding.UTF8.GetString(client.Buffer, , count); // 如果该 Socket 在网络缓冲区中没有排队的数据 并且 接收到的数据中有自定义的结束符时
if (client.Socket.Connected && client.Socket.Available == && 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, , client.Buffer.Length, , 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, , 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(, ).ToString().PadLeft(, '');
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", );
// 异步操作完成后执行的事件
_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[];
e.SetBuffer(response, , 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
[源码下载]

04-28 08:11