与众不同 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 服务端文件,主进程中,创建图形化界面,询问地址(主机名,端口),点击 ...
随机推荐
- [置顶] JNI之java传递数据给c语言
1.首先创建 DataProvider类: package com.pl.ndkpassdata; public class DataProvider { static{ System.loadLib ...
- net use \\192.168.54.145 /user:administrator "12345qwert"无法连接,错误码1326
1.在远程机的"控制面板-文件夹选项-查看-简单的文件共享",去掉选取,然后再尝试连接 2.控制面板\所有控制面板项\管理工具 下-->本地安全策略-->安全设置--& ...
- 纠错记录(Could not open the editor: Android XML Editor cannot process this input.)
Eclipse无法打开xml文件 preference->general->edit->file association->file types->选择.xml,然后在a ...
- Android学习笔记:Activity生命周期详解
进行android的开发,必须深入了解Activity的生命周期.而对这个讲述最权威.最好的莫过于google的开发文档了. 本文的讲述主要是对 http://developer.android.co ...
- CH BR13数学(啥?-a^b≡a^b mod phi(p)+phi(p)(mod p)(b>=phi(p))公式)
啥? Beta Round #13 (数学专场) 背景 有人写了一个RSA加密给我玩. 描述 我赌5毛前面两题的内容也就开头几句话平时会用到. 还是做点具体的东西吧. 求c^d Mod N 输入格式 ...
- 用 PS 复制权限
用 PS 复制权限 我们要把源计算机上的文件权限复制到目的计算机上. get-acl .\s.txt | Export-Clixml sddl.xml 把 s.txt 文件的权限保存到 sddl.xm ...
- Python,PIL压缩裁剪图片
自己写了用来压缩 DC 照片的,批量处理整目录文件,非常方便.需要安装 PIL #!/usr/bin/env python import Image import os import os.path ...
- VC 无标题栏对话框移动
操作系统:Windows 7软件环境:Visual C++ 2008 SP1本次目的:实现无框移动 所谓的无标题栏对话框,是基于对话框的工程,对话框属性Border设置为None,对话框如下所示: 为 ...
- ZOJ 3723 (浙大月赛)状压DP
A了一整天~~~终于搞掉了. 真是血都A出来了. 题目意思很清楚,肯定是状压DP. 我们可以联系一下POJ 1185 炮兵阵地,经典的状压DP. 两道题的区别就在于,这道题的攻击是可以被X挡住的,而 ...
- [linux]linux命令学习-netstat
linux非常多服务都与网络相关.当服务调不通或者是启动port被占用,或者是又是被防火墙挡住的时候,就须要查询网络相关的问题,netstat命令之前仅仅会用一两个參数这里.好好学习一番. 经常使用的 ...