原文:与众不同 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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace SocketServerTcp
  7. {
  8. /// <summary>
  9. /// 对客户端 Socket 及其他相关信息做一个封装
  10. /// </summary>
  11. public class ClientSocketPacket
  12. {
  13. /// <summary>
  14. /// 客户端 Socket
  15. /// </summary>
  16. public System.Net.Sockets.Socket Socket { get; set; }
  17.  
  18. private byte[] _buffer;
  19. /// <summary>
  20. /// 为该客户端 Socket 开辟的缓冲区
  21. /// </summary>
  22. public byte[] Buffer
  23. {
  24. get
  25. {
  26. if (_buffer == null)
  27. _buffer = new byte[];
  28.  
  29. return _buffer;
  30. }
  31. }
  32.  
  33. private List<byte> _receivedByte;
  34. /// <summary>
  35. /// 客户端 Socket 发过来的信息的字节集合
  36. /// </summary>
  37. public List<byte> ReceivedByte
  38. {
  39. get
  40. {
  41. if (_receivedByte == null)
  42. _receivedByte = new List<byte>();
  43.  
  44. return _receivedByte;
  45. }
  46. }
  47. }
  48. }

Main.cs

  1. /*
  2. * Socket TCP 聊天室的服务端
  3. */
  4.  
  5. using System;
  6. using System.Collections.Generic;
  7. using System.ComponentModel;
  8. using System.Data;
  9. using System.Drawing;
  10. using System.Linq;
  11. using System.Text;
  12. using System.Windows.Forms;
  13.  
  14. using System.Net.Sockets;
  15. using System.Net;
  16. using System.Threading;
  17. using System.IO;
  18.  
  19. namespace SocketServerTcp
  20. {
  21. public partial class Main : Form
  22. {
  23. SynchronizationContext _syncContext;
  24.  
  25. System.Timers.Timer _timer;
  26.  
  27. // 信息结束符,用于判断是否完整地读取了客户端发过来的信息,要与客户端的信息结束符相对应(本例只用于演示,实际项目中请用自定义协议)
  28. private string _endMarker = "^";
  29.  
  30. // 服务端监听的 socket
  31. private Socket _listener;
  32.  
  33. // 实例化 ManualResetEvent,设置其初始状态为无信号
  34. private ManualResetEvent _signal = new ManualResetEvent(false);
  35.  
  36. // 客户端 Socket 列表
  37. private List<ClientSocketPacket> _clientList = new List<ClientSocketPacket>();
  38.  
  39. public Main()
  40. {
  41. InitializeComponent();
  42.  
  43. // UI 线程
  44. _syncContext = SynchronizationContext.Current;
  45.  
  46. // 启动后台线程去运行 Socket 服务
  47. Thread thread = new Thread(new ThreadStart(LaunchSocketServer));
  48. thread.IsBackground = true;
  49. thread.Start();
  50. }
  51.  
  52. private void LaunchSocketServer()
  53. {
  54. // 每 10 秒运行一次计时器所指定的方法,群发信息
  55. _timer = new System.Timers.Timer();
  56. _timer.Interval = 10000d;
  57. _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
  58. _timer.Start();
  59.  
  60. // TCP 方式监听 3366 端口
  61. _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  62. _listener.Bind(new IPEndPoint(IPAddress.Any, ));
  63. // 指定等待连接队列中允许的最大数
  64. _listener.Listen();
  65.  
  66. while (true)
  67. {
  68. // 设置为无信号
  69. _signal.Reset();
  70.  
  71. // 开始接受客户端传入的连接
  72. _listener.BeginAccept(new AsyncCallback(OnClientConnect), null);
  73.  
  74. // 阻塞当前线程,直至有信号为止
  75. _signal.WaitOne();
  76. }
  77. }
  78.  
  79. private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
  80. {
  81. // 每 10 秒给所有连入的客户端发送一次消息
  82. SendData(string.Format("webabcd 对所有人说:大家好! 【信息来自服务端 {0}】", DateTime.Now.ToString("hh:mm:ss")));
  83. }
  84.  
  85. private void OnClientConnect(IAsyncResult async)
  86. {
  87. ClientSocketPacket client = new ClientSocketPacket();
  88. // 完成接受客户端传入的连接的这个异步操作,并返回客户端连入的 socket
  89. client.Socket = _listener.EndAccept(async);
  90.  
  91. // 将客户端连入的 Socket 放进客户端 Socket 列表
  92. _clientList.Add(client);
  93.  
  94. OutputMessage(((IPEndPoint)client.Socket.LocalEndPoint).Address + " 连入了服务器");
  95. SendData("一个新的客户端已经成功连入服务器。。。 【信息来自服务端】");
  96.  
  97. try
  98. {
  99. // 开始接收客户端传入的数据
  100. client.Socket.BeginReceive(client.Buffer, , client.Buffer.Length, SocketFlags.None, new AsyncCallback(OnDataReceived), client);
  101. }
  102. catch (SocketException ex)
  103. {
  104. // 处理异常
  105. HandleException(client, ex);
  106. }
  107.  
  108. // 设置为有信号
  109. _signal.Set();
  110. }
  111.  
  112. private void OnDataReceived(IAsyncResult async)
  113. {
  114. ClientSocketPacket client = async.AsyncState as ClientSocketPacket;
  115.  
  116. int count = ;
  117.  
  118. try
  119. {
  120. // 完成接收数据的这个异步操作,并返回接收的字节数
  121. if (client.Socket.Connected)
  122. count = client.Socket.EndReceive(async);
  123. }
  124. catch (SocketException ex)
  125. {
  126. HandleException(client, ex);
  127. }
  128.  
  129. // 把接收到的数据添加进收到的字节集合内
  130. // 本例采用 UTF8 编码,中文占用 3 字节,英文等字符与 ASCII 相同
  131. foreach (byte b in client.Buffer.Take(count))
  132. {
  133. if (b == ) continue; // 如果是空字节则不做处理('\0')
  134.  
  135. client.ReceivedByte.Add(b);
  136. }
  137.  
  138. // 把当前接收到的数据转换为字符串。用于判断是否包含自定义的结束符
  139. string receivedString = UTF8Encoding.UTF8.GetString(client.Buffer, , count);
  140.  
  141. // 如果该 Socket 在网络缓冲区中没有排队的数据 并且 接收到的数据中有自定义的结束符时
  142. if (client.Socket.Connected && client.Socket.Available == && receivedString.Contains(_endMarker))
  143. {
  144. // 把收到的字节集合转换成字符串(去掉自定义结束符)
  145. // 然后清除掉字节集合中的内容,以准备接收用户发送的下一条信息
  146. string content = UTF8Encoding.UTF8.GetString(client.ReceivedByte.ToArray());
  147. content = content.Replace(_endMarker, "");
  148. client.ReceivedByte.Clear();
  149.  
  150. // 发送数据到所有连入的客户端,并在服务端做记录
  151. SendData(content);
  152. OutputMessage(content);
  153. }
  154.  
  155. try
  156. {
  157. // 继续开始接收客户端传入的数据
  158. if (client.Socket.Connected)
  159. client.Socket.BeginReceive(client.Buffer, , client.Buffer.Length, , new AsyncCallback(OnDataReceived), client);
  160. }
  161. catch (SocketException ex)
  162. {
  163. HandleException(client, ex);
  164. }
  165. }
  166.  
  167. /// <summary>
  168. /// 发送数据到所有连入的客户端
  169. /// </summary>
  170. /// <param name="data">需要发送的数据</param>
  171. private void SendData(string data)
  172. {
  173. byte[] byteData = UTF8Encoding.UTF8.GetBytes(data);
  174.  
  175. foreach (ClientSocketPacket client in _clientList)
  176. {
  177. if (client.Socket.Connected)
  178. {
  179. try
  180. {
  181. // 如果某客户端 Socket 是连接状态,则向其发送数据
  182. client.Socket.BeginSend(byteData, , byteData.Length, SocketFlags.None, new AsyncCallback(OnDataSent), client);
  183. }
  184. catch (SocketException ex)
  185. {
  186. HandleException(client, ex);
  187. }
  188. }
  189. else
  190. {
  191. // 某 Socket 断开了连接的话则将其关闭,并将其清除出客户端 Socket 列表
  192. // 也就是说每次向所有客户端发送消息的时候,都会从客户端 Socket 集合中清除掉已经关闭了连接的 Socket
  193. client.Socket.Close();
  194. _clientList.Remove(client);
  195. }
  196. }
  197. }
  198.  
  199. private void OnDataSent(IAsyncResult async)
  200. {
  201. ClientSocketPacket client = async.AsyncState as ClientSocketPacket;
  202.  
  203. try
  204. {
  205. // 完成将信息发送到客户端的这个异步操作
  206. int sentBytesCount = client.Socket.EndSend(async);
  207. }
  208. catch (SocketException ex)
  209. {
  210. HandleException(client, ex);
  211. }
  212. }
  213.  
  214. /// <summary>
  215. /// 处理 SocketException 异常
  216. /// </summary>
  217. /// <param name="client">导致异常的 ClientSocketPacket</param>
  218. /// <param name="ex">SocketException</param>
  219. private void HandleException(ClientSocketPacket client, SocketException ex)
  220. {
  221. // 在服务端记录异常信息,关闭导致异常的 Socket,并将其清除出客户端 Socket 列表
  222. OutputMessage(client.Socket.RemoteEndPoint.ToString() + " - " + ex.Message);
  223. client.Socket.Close();
  224. _clientList.Remove(client);
  225. }
  226.  
  227. // 在 UI 上输出指定信息
  228. private void OutputMessage(string data)
  229. {
  230. _syncContext.Post((p) => { txtMsg.Text += p.ToString() + "\r\n"; }, data);
  231. }
  232. }
  233. }

2、客户端
TcpDemo.xaml

  1. <phone:PhoneApplicationPage
  2. x:Class="Demo.Communication.SocketClient.TcpDemo"
  3. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5. xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
  6. xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
  7. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  8. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  9. FontFamily="{StaticResource PhoneFontFamilyNormal}"
  10. FontSize="{StaticResource PhoneFontSizeNormal}"
  11. Foreground="{StaticResource PhoneForegroundBrush}"
  12. SupportedOrientations="Portrait" Orientation="Portrait"
  13. mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
  14. shell:SystemTray.IsVisible="True">
  15.  
  16. <Grid x:Name="LayoutRoot" Background="Transparent">
  17. <StackPanel HorizontalAlignment="Left">
  18.  
  19. <ScrollViewer x:Name="svChat" Height="400">
  20. <TextBlock x:Name="txtChat" TextWrapping="Wrap" />
  21. </ScrollViewer>
  22.  
  23. <TextBox x:Name="txtName" />
  24. <TextBox x:Name="txtInput" KeyDown="txtInput_KeyDown" />
  25. <Button x:Name="btnSend" Content="发送" Click="btnSend_Click" />
  26.  
  27. </StackPanel>
  28. </Grid>
  29.  
  30. </phone:PhoneApplicationPage>

TcpDemo.xaml.cs

  1. /*
  2. * Socket TCP 聊天室的客户端
  3. */
  4.  
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Windows;
  10. using System.Windows.Controls;
  11. using System.Windows.Documents;
  12. using System.Windows.Input;
  13. using System.Windows.Media;
  14. using System.Windows.Media.Animation;
  15. using System.Windows.Shapes;
  16. using Microsoft.Phone.Controls;
  17.  
  18. using System.Net.Sockets;
  19. using System.Text;
  20.  
  21. // 此命名空间下有 Socket 的扩展方法 GetCurrentNetworkInterface()
  22. using Microsoft.Phone.Net.NetworkInformation;
  23.  
  24. namespace Demo.Communication.SocketClient
  25. {
  26. public partial class TcpDemo : PhoneApplicationPage
  27. {
  28. // 信息结束符,用于判断是否完整地读取了服务端发过来的信息,要与服务端的信息结束符相对应(本例只用于演示,实际项目中请用自定义协议)
  29. private string _endMarker = "^";
  30.  
  31. // 客户端 Socket
  32. private Socket _socket;
  33.  
  34. // 用于发送数据到服务端的 Socket 异步操作对象
  35. private SocketAsyncEventArgs _socketAsyncSend;
  36.  
  37. // 用于接收数据的 Socket 异步操作对象
  38. private SocketAsyncEventArgs _socketAsyncReceive;
  39.  
  40. public TcpDemo()
  41. {
  42. InitializeComponent();
  43.  
  44. this.Loaded += new RoutedEventHandler(TcpDemo_Loaded);
  45. }
  46.  
  47. void TcpDemo_Loaded(object sender, RoutedEventArgs e)
  48. {
  49. // 初始化姓名和需要发送的默认文字
  50. txtName.Text = "匿名用户" + new Random().Next(, ).ToString().PadLeft(, '');
  51. txtInput.Text = "hi";
  52.  
  53. // 实例化 Socket
  54. _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  55.  
  56. // 设置 Socket 连接的首选网络类型 NetworkSelectionCharacteristics.Cellular 或 NetworkSelectionCharacteristics.NonCellular
  57. _socket.SetNetworkPreference(NetworkSelectionCharacteristics.NonCellular);
  58.  
  59. // 强制 Socket 连接的网络类型 NetworkSelectionCharacteristics.Cellular 或 NetworkSelectionCharacteristics.NonCellular(不指定的话则均可)
  60. // 如果无法使用强制要求的网络类型,则在 OnSocketConnectCompleted 会收到 SocketError.NetworkDown
  61. _socket.SetNetworkRequirement(NetworkSelectionCharacteristics.NonCellular);
  62.  
  63. // 实例化 SocketAsyncEventArgs ,用于对 Socket 做异步操作,很方便
  64. _socketAsyncReceive = new SocketAsyncEventArgs();
  65. // 服务器的 EndPoint
  66. _socketAsyncReceive.RemoteEndPoint = new DnsEndPoint("192.168.8.217", );
  67. // 异步操作完成后执行的事件
  68. _socketAsyncReceive.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
  69.  
  70. // 异步连接服务端
  71. _socket.ConnectAsync(_socketAsyncReceive);
  72. }
  73.  
  74. private void OnSocketConnectCompleted(object sender, SocketAsyncEventArgs e)
  75. {
  76. if (e.SocketError != SocketError.Success)
  77. {
  78. OutputMessage("Socket 连接错误:" + e.SocketError.ToString());
  79. return;
  80. }
  81.  
  82. // 设置数据缓冲区
  83. byte[] response = new byte[];
  84. e.SetBuffer(response, , response.Length);
  85.  
  86. // 修改 SocketAsyncEventArgs 对象的异步操作完成后需要执行的事件
  87. e.Completed -= new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
  88. e.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketReceiveCompleted);
  89.  
  90. // 异步地从服务端 Socket 接收数据
  91. _socket.ReceiveAsync(e);
  92.  
  93. // 构造一个 SocketAsyncEventArgs 对象,用于用户向服务端发送消息
  94. _socketAsyncSend = new SocketAsyncEventArgs();
  95. _socketAsyncSend.RemoteEndPoint = e.RemoteEndPoint;
  96.  
  97. if (_socket.Connected)
  98. {
  99. OutputMessage("成功地连接上了服务器。。。");
  100.  
  101. // Socket 有一个扩展方法 GetCurrentNetworkInterface(),需要引用命名空间 Microsoft.Phone.Net.NetworkInformation
  102. // GetCurrentNetworkInterface() 会返回当前 Socket 连接的 NetworkInterfaceInfo 对象(NetworkInterfaceInfo 的详细说明参见:Device/Status/NetworkStatus.xaml.cs)
  103. NetworkInterfaceInfo nii = _socket.GetCurrentNetworkInterface();
  104. OutputMessage("网络接口的类型:" + nii.InterfaceType.ToString());
  105. }
  106. else
  107. {
  108. OutputMessage("无法连接到服务器。。。请刷新后再试。。。");
  109. }
  110. }
  111.  
  112. private void OnSocketReceiveCompleted(object sender, SocketAsyncEventArgs e)
  113. {
  114. try
  115. {
  116. // 将接收到的数据转换为字符串
  117. string data = UTF8Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
  118.  
  119. OutputMessage(data);
  120. }
  121. catch (Exception ex)
  122. {
  123. OutputMessage(ex.ToString());
  124. }
  125.  
  126. // 继续异步地从服务端接收数据
  127. _socket.ReceiveAsync(e);
  128. }
  129.  
  130. private void OutputMessage(string data)
  131. {
  132. // 在聊天文本框中输出指定的信息,并将滚动条滚到底部
  133. this.Dispatcher.BeginInvoke(
  134. delegate
  135. {
  136. txtChat.Text += data + "\r\n";
  137. svChat.ScrollToVerticalOffset(txtChat.ActualHeight - svChat.Height);
  138. }
  139. );
  140. }
  141.  
  142. private void SendData()
  143. {
  144. if (_socket.Connected)
  145. {
  146. // 设置需要发送的数据的缓冲区
  147. _socketAsyncSend.BufferList =
  148. new List<ArraySegment<byte>>()
  149. {
  150. new ArraySegment<byte>(UTF8Encoding.UTF8.GetBytes(txtName.Text + ":" + txtInput.Text + _endMarker))
  151. };
  152.  
  153. // 异步地向服务端发送消息
  154. _socket.SendAsync(_socketAsyncSend);
  155. }
  156. else
  157. {
  158. txtChat.Text += "无法连接到服务器。。。请刷新后再试。。。\r\n";
  159. _socket.Close();
  160. }
  161.  
  162. txtInput.Focus();
  163. txtInput.Text = "";
  164. }
  165.  
  166. private void btnSend_Click(object sender, RoutedEventArgs e)
  167. {
  168. SendData();
  169. }
  170.  
  171. private void txtInput_KeyDown(object sender, KeyEventArgs e)
  172. {
  173. if (e.Key == Key.Enter)
  174. {
  175. SendData();
  176. this.Focus();
  177. }
  178. }
  179. }
  180. }

OK
[源码下载]

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

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

    原文:与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...

  2. Python编写基于socket的非阻塞多人聊天室程序(单线程&多线程)

    前置知识:socket非阻塞函数(socket.setblocking(False)),简单多线程编程 代码示例: 1. 单线程非阻塞版本: 服务端: #!/usr/bin/env python # ...

  3. 基于websocket实现的一个简单的聊天室

    本文是基于websocket写的一个简单的聊天室的例子,可以实现简单的群聊和私聊.是基于websocket的注解方式编写的.(有一个小的缺陷,如果用户名是中文,会乱码,不知如何处理,如有人知道,请告知 ...

  4. 与众不同 windows phone (33) - Communication(通信)之源特定组播 SSM(Source Specific Multicast)

    原文:与众不同 windows phone (33) - Communication(通信)之源特定组播 SSM(Source Specific Multicast) [索引页][源码下载] 与众不同 ...

  5. 与众不同 windows phone (32) - Communication(通信)之任意源组播 ASM(Any Source Multicast)

    原文:与众不同 windows phone (32) - Communication(通信)之任意源组播 ASM(Any Source Multicast) [索引页][源码下载] 与众不同 wind ...

  6. 与众不同 windows phone (29) - Communication(通信)之与 OData 服务通信

    原文:与众不同 windows phone (29) - Communication(通信)之与 OData 服务通信 [索引页][源码下载] 与众不同 windows phone (29) - Co ...

  7. 与众不同 windows phone (38) - 8.0 关联启动: 使用外部程序打开一个文件或URI, 关联指定的文件类型或协议

    [源码下载] 与众不同 windows phone (38) - 8.0 关联启动: 使用外部程序打开一个文件或URI, 关联指定的文件类型或协议 作者:webabcd 介绍与众不同 windows ...

  8. 基于tcp和多线程的多人聊天室-C语言

    之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室. 具体的实现过程: 服务器端:绑定socket对象->设置监听数-> ...

  9. Python实现网络图形化界面多人聊天室 - Windows

    Python实现网络图形化界面多人聊天室 - Windows 项目名称:网络多人聊天室图形界面版本 项目思路: server.py 服务端文件,主进程中,创建图形化界面,询问地址(主机名,端口),点击 ...

随机推荐

  1. 分享非常有用的Java程序 (关键代码) (二)---列出文件和目录

    原文:分享非常有用的Java程序 (关键代码) (二)---列出文件和目录 File dir = new File("directoryName"); String[] child ...

  2. Python 文本解析器

    Python 文本解析器 一.课程介绍 本课程讲解一个使用 Python 来解析纯文本生成一个 HTML 页面的小程序. 二.相关技术 Python:一种面向对象.解释型计算机程序设计语言,用它可以做 ...

  3. rackup工具

    gem包rack提供了rackup工具来启动webapplication 下面是一个入门范例,使用 bundler 管理库的一个sinatra应用   在begin文件夹下有三个文件 begin.ru ...

  4. 基于visual Studio2013解决面试题之1102合并字符串

     题目

  5. jQuery.mobile.changePage() | jQuery Mobile API Documentation

    jQuery.mobile.changePage() | jQuery Mobile API Documentation <script> $.mobile.changePage( &qu ...

  6. 跟我一起写 Makefile(一)

    跟我一起写 Makefile  陈皓 概述—— 什么是makefile?也许非常多Winodws的程序猿都不知道这个东西,由于那些Windows的IDE都为你做了这个工作,但我认为要作一个好的和pro ...

  7. 重载new delete操作符是怎么调用的

    自定义的new操作符是怎么对英语new 一个对象的?自定义的delete操作符什么情况下得到调用?new一个对象时出现异常需要我操心内存泄露吗?下面的一个例子帮我们解开所有的疑惑. 1. 调用规则   ...

  8. hive udaf 用maven打包运行create temporary function 时报错

    用maven打包写好的jar,在放到hive中作暂时函数时报错. 错误信息例如以下: hive> create temporary function maxvalue as "com. ...

  9. Codeforces 484B Maximum Value(排序+二分)

    题目链接: http://codeforces.com/problemset/problem/484/B 题意: 求a[i]%a[j] (a[i]>a[j])的余数的最大值 分析: 要求余数的最 ...

  10. 关于在打包Jar文件时遇到的资源路径问题(一)

    当我们将程序写好,并进行打包成Jar文件时,通常都带有各种资源,这些资源可以是图像或者声音文件,也可以是别的如文本文件或二进制文件等,这些资源都和代码密切相关.例如在一个JPanel类上显示一些可能变 ...