与众不同 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
[源码下载]
与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室的更多相关文章
- 与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室
原文:与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...
- Python编写基于socket的非阻塞多人聊天室程序(单线程&多线程)
前置知识:socket非阻塞函数(socket.setblocking(False)),简单多线程编程 代码示例: 1. 单线程非阻塞版本: 服务端: #!/usr/bin/env python # ...
- 基于websocket实现的一个简单的聊天室
本文是基于websocket写的一个简单的聊天室的例子,可以实现简单的群聊和私聊.是基于websocket的注解方式编写的.(有一个小的缺陷,如果用户名是中文,会乱码,不知如何处理,如有人知道,请告知 ...
- 与众不同 windows phone (33) - Communication(通信)之源特定组播 SSM(Source Specific Multicast)
原文:与众不同 windows phone (33) - Communication(通信)之源特定组播 SSM(Source Specific Multicast) [索引页][源码下载] 与众不同 ...
- 与众不同 windows phone (32) - Communication(通信)之任意源组播 ASM(Any Source Multicast)
原文:与众不同 windows phone (32) - Communication(通信)之任意源组播 ASM(Any Source Multicast) [索引页][源码下载] 与众不同 wind ...
- 与众不同 windows phone (29) - Communication(通信)之与 OData 服务通信
原文:与众不同 windows phone (29) - Communication(通信)之与 OData 服务通信 [索引页][源码下载] 与众不同 windows phone (29) - Co ...
- 与众不同 windows phone (38) - 8.0 关联启动: 使用外部程序打开一个文件或URI, 关联指定的文件类型或协议
[源码下载] 与众不同 windows phone (38) - 8.0 关联启动: 使用外部程序打开一个文件或URI, 关联指定的文件类型或协议 作者:webabcd 介绍与众不同 windows ...
- 基于tcp和多线程的多人聊天室-C语言
之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室. 具体的实现过程: 服务器端:绑定socket对象->设置监听数-> ...
- Python实现网络图形化界面多人聊天室 - Windows
Python实现网络图形化界面多人聊天室 - Windows 项目名称:网络多人聊天室图形界面版本 项目思路: server.py 服务端文件,主进程中,创建图形化界面,询问地址(主机名,端口),点击 ...
随机推荐
- 分享非常有用的Java程序 (关键代码) (二)---列出文件和目录
原文:分享非常有用的Java程序 (关键代码) (二)---列出文件和目录 File dir = new File("directoryName"); String[] child ...
- Python 文本解析器
Python 文本解析器 一.课程介绍 本课程讲解一个使用 Python 来解析纯文本生成一个 HTML 页面的小程序. 二.相关技术 Python:一种面向对象.解释型计算机程序设计语言,用它可以做 ...
- rackup工具
gem包rack提供了rackup工具来启动webapplication 下面是一个入门范例,使用 bundler 管理库的一个sinatra应用 在begin文件夹下有三个文件 begin.ru ...
- 基于visual Studio2013解决面试题之1102合并字符串
题目
- jQuery.mobile.changePage() | jQuery Mobile API Documentation
jQuery.mobile.changePage() | jQuery Mobile API Documentation <script> $.mobile.changePage( &qu ...
- 跟我一起写 Makefile(一)
跟我一起写 Makefile 陈皓 概述—— 什么是makefile?也许非常多Winodws的程序猿都不知道这个东西,由于那些Windows的IDE都为你做了这个工作,但我认为要作一个好的和pro ...
- 重载new delete操作符是怎么调用的
自定义的new操作符是怎么对英语new 一个对象的?自定义的delete操作符什么情况下得到调用?new一个对象时出现异常需要我操心内存泄露吗?下面的一个例子帮我们解开所有的疑惑. 1. 调用规则 ...
- hive udaf 用maven打包运行create temporary function 时报错
用maven打包写好的jar,在放到hive中作暂时函数时报错. 错误信息例如以下: hive> create temporary function maxvalue as "com. ...
- Codeforces 484B Maximum Value(排序+二分)
题目链接: http://codeforces.com/problemset/problem/484/B 题意: 求a[i]%a[j] (a[i]>a[j])的余数的最大值 分析: 要求余数的最 ...
- 关于在打包Jar文件时遇到的资源路径问题(一)
当我们将程序写好,并进行打包成Jar文件时,通常都带有各种资源,这些资源可以是图像或者声音文件,也可以是别的如文本文件或二进制文件等,这些资源都和代码密切相关.例如在一个JPanel类上显示一些可能变 ...