一个简易聊天室
作者:追风剑情 发布于:2019-9-16 15:39 分类:C#
一、工程结构
配置条件编译: [生成]->[配置管理器]
二、代码
UniNetwork.cs
using System; using System.Collections.Generic; using System.Net.Sockets; using System.IO; /// <summary> /// 网络通信 /// </summary> public class UniNetwork { protected TcpClient mTcpClient = new TcpClient(); protected PackageParser mPackageParser = null; protected ProtocolData mProtocolData = null; protected byte[] receiveBuffer; protected uint num = 1;//自维护 protected AsyncCallback mAsyncCallback; public AsyncCallback OnConnectedCallback; public Action OnDisconnectedCallback; public Action<ProtocolData> OnPackageCompleteCallback; public Action OnInvalidPackageCallback; public UniNetwork() { mPackageParser = new PackageParser(); mPackageParser.OnPackageComplete += OnPackageComplete; mPackageParser.OnInvalidPackage += OnInvalidPackage; receiveBuffer = new byte[ReceiveBufferSize]; mProtocolData = new ProtocolData(); } public void SetTcpClient(TcpClient tcpClient) { mTcpClient = tcpClient; } public string clientIP { get { if (mTcpClient == null || !mTcpClient.Connected) return ""; string IP = mTcpClient.Client.RemoteEndPoint.ToString(); return IP; } } // 缓冲区大小 public int ReceiveBufferSize { get { if (mTcpClient == null) return 0; return mTcpClient.ReceiveBufferSize; } } // 连接 public void Connect(string IP, int port) { if (mTcpClient == null || mTcpClient.Connected) return; mTcpClient.BeginConnect(IP, port, OnTcpConnected, mTcpClient); } // 连接返回 private void OnTcpConnected(IAsyncResult result) { try { mTcpClient.EndConnect(result); } catch (Exception ex) { Console.WriteLine(ex.ToString()); return; } OnConnected(result); BeginRead(); } protected void BeginRead() { if (mTcpClient == null || !mTcpClient.Connected) return; NetworkStream stream = mTcpClient.GetStream(); mAsyncCallback = new AsyncCallback(OnReadCallback); stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, mAsyncCallback, stream); } private void OnReadCallback(IAsyncResult result) { // 关闭变量未使用警告 #pragma warning disable 168 var stream = (NetworkStream)result.AsyncState; int length = -1; try { //1、TCP中断后length < 1 //2、TCP中断后产生IOException length = stream.EndRead(result); } catch (IOException ex) { } finally { } if (length < 1) { OnDisconnected(); return; } this.mPackageParser.Receive(receiveBuffer, 0, length); stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, mAsyncCallback, stream); #pragma warning restore 168 } protected virtual void OnConnected(IAsyncResult result) { if (this.OnConnectedCallback != null) this.OnConnectedCallback(result); } // TCP链接中断 protected virtual void OnDisconnected() { if (OnDisconnectedCallback != null) OnDisconnectedCallback(); } // 返回数据包 protected virtual void OnPackageComplete(ProtocolData pd) { if (OnPackageCompleteCallback != null) OnPackageCompleteCallback(pd); } // 返回无效数据包 protected virtual void OnInvalidPackage() { if (OnInvalidPackageCallback != null) OnInvalidPackageCallback(); } // 发消息 public void SendMessage(string msg) { WriteBuffer(msg); SendBuffer(1, 1); } // 发消息 public void SendMessage(byte[] bytes) { WriteBuffer(bytes); SendBuffer(1, 1); } public void WriteBuffer(byte[] bytes) { mProtocolData.WriteBytes(bytes); } public void WriteBuffer(uint i) { mProtocolData.WriteUInt32(i); } public void WriteBuffer(ushort i) { mProtocolData.WriteUShort(i); } public void WriteBuffer(string s) { mProtocolData.WriteUTF8String(s); } public void SendBuffer(uint type, uint id) { SendProtocol(type, id, null); } /// <summary> /// 发协议 /// </summary> /// <param name="type">协议类型,根据具体业务定义</param> /// <param name="id">协议ID,根据具体业务定义</param> /// <param name="bytes">内容</param> public void SendProtocol(uint type, uint id, byte[] bytes) { if (mTcpClient == null || !mTcpClient.Connected) return; NetworkStream stream = mTcpClient.GetStream(); if (stream == null || !stream.CanWrite) return; if (mProtocolData == null) this.mProtocolData = new ProtocolData(); this.mProtocolData.Num = num; this.mProtocolData.ProType = type; this.mProtocolData.ID = id; this.mProtocolData.WriteBytes(bytes); byte[] data = this.mProtocolData.EncapsulatePackage(); stream.Write(data, 0, data.Length); num++; if (num > ProtocolData.MAX_PROTOCOL_NUM) num = 0; } // 释放 public void Dispose() { //if (mTcpClient != null) //mTcpClient.Dispose(); if (mProtocolData != null) mProtocolData.Dispose(); if (mPackageParser != null) mPackageParser.Dispose(); mTcpClient = null; mProtocolData = null; mPackageParser = null; OnConnectedCallback = null; OnDisconnectedCallback = null; OnPackageCompleteCallback = null; OnInvalidPackageCallback = null; } } /// <summary> /// 数据包解析器 /// </summary> public class PackageParser { private ushort size = 0; private uint lastNum = 0; private Stack<byte> receiveBuffer = new Stack<byte>(); public Action<ProtocolData> OnPackageComplete; public Action OnInvalidPackage; public void Receive(byte[] bytes) { Receive(bytes, 0, bytes.Length); } public void Receive(byte[] bytes, int start, int length) { if (bytes == null || bytes.Length == 0) return; for (int i = start + length - 1; i >= start; i--) receiveBuffer.Push(bytes[i]); Parse(); } private void Parse() { if (size == 0) { //用2个字节表示协议长度 if (receiveBuffer.Count < 2) return; //Peek() 返回顶部元素,但不删除 //Pop() 返回顶部元素,并删除 byte[] size_bytes = new byte[] { receiveBuffer.Pop(), receiveBuffer.Pop() }; size = BitConverter.ToUInt16(size_bytes, 0); } if (receiveBuffer.Count >= size) { //从缓冲区中提取出一个数据包 byte[] bytes = new byte[size]; for (int i = 0; i < size; i++) { bytes[i] = receiveBuffer.Pop(); } size = 0; if (OnPackageComplete != null) { ProtocolData p = new ProtocolData(bytes); //验证协议序号是否合法 if (p.ValidateNum(lastNum)) { lastNum = p.Num; OnPackageComplete(p); } else { Console.WriteLine("Invalid protocol serial number. id={0}", p.ID); if (OnInvalidPackage != null) OnInvalidPackage(); } } } } public void Dispose() { if (receiveBuffer != null) receiveBuffer.Clear(); receiveBuffer = null; OnPackageComplete = null; OnInvalidPackage = null; } } /// <summary> /// 协议数据 /// </summary> public class ProtocolData { //最大协议序号 public const uint MAX_PROTOCOL_NUM = 100000; private BinaryReader reader; private BinaryWriter writer; private BinaryWriter tmpWriter; //数据包长度 private int length; //协议序号 private uint num = 0;//从1开始 //协议类型 private uint type = 0; //协议ID private uint id = 0; //内容数据 private byte[] bytes; public ProtocolData() { this.writer = new BinaryWriter( new MemoryStream() ); } public ProtocolData(byte[] data) { this.length = data.Length; this.reader = new BinaryReader( new MemoryStream(data) ); this.reader.BaseStream.Position = 0; this.Parse(); } /// <summary> /// 验证协议序号是否正确 /// 协议序号必须依次递增(也可以是其他规则),从而避免回放攻击 /// </summary> /// <param name="lastNum">上一条协议序号</param> /// <returns></returns> public bool ValidateNum(uint lastNum) { if (num > lastNum && num - lastNum == 1) return true; if (num == 0 && lastNum == MAX_PROTOCOL_NUM) return true; return false; } public uint Num { get { return num; } set { num = value; } } public uint ProType { get { return type; } set { type = value; } } public uint ID { get { return id; } set { id = value; } } public byte[] Bytes { get { return bytes; } set { bytes = value; } } public int ReadInt32() { return reader.ReadInt32(); } public uint ReadUInt32() { return reader.ReadUInt32(); } public string ReadUTF8String() { return reader.ReadString(); } public byte[] ReadBytes(int count) { return reader.ReadBytes(count); } //读取流中所有剩余字节 public byte[] ReadAllBytes() { return reader.ReadBytes(this.length); } public void WriteUShort(ushort i) { writer.Write(i); } public void WriteUInt32(uint i) { writer.Write(i); } public void WriteBytes(byte[] bytes) { if (bytes == null || bytes.Length == 0) return; writer.Write(bytes); } public void WriteUTF8String(string s) { writer.Write(s); } private void Parse() { //解析数据包字段 this.num = reader.ReadUInt32(); this.type = reader.ReadUInt32(); this.id = reader.ReadUInt32(); this.bytes = reader.ReadBytes(this.length); //回写 this.reader.BaseStream.Position = 0; this.reader.BaseStream.Write(this.bytes, 0, this.bytes.Length); this.reader.BaseStream.Flush(); this.reader.BaseStream.Position = 0; } // 取出writer缓冲区中的所有字节 private byte[] PopWriterBytes() { //内容长度 ushort size = (ushort)writer.BaseStream.Position; var data = new byte[size]; writer.BaseStream.Position = 0; writer.BaseStream.Read(data, 0, size); writer.BaseStream.SetLength(0); return data; } // 封装协议包 public byte[] EncapsulatePackage() { if (this.tmpWriter == null) this.tmpWriter = new BinaryWriter( new MemoryStream() ); tmpWriter.BaseStream.Position = 0; const ushort head_size = 12;//协议头所占字节数 ushort content_size = (ushort)writer.BaseStream.Length; ushort size = (ushort)(head_size + content_size); //写入协议头字段 this.tmpWriter.Write(size); this.tmpWriter.Write(num); this.tmpWriter.Write(type); this.tmpWriter.Write(id); //写入协议内容 byte[] content = PopWriterBytes(); this.tmpWriter.Write(content); //转字节数组 size = (ushort)tmpWriter.BaseStream.Length; var data = new byte[size]; tmpWriter.BaseStream.Position = 0; tmpWriter.BaseStream.Read(data, 0, size); tmpWriter.BaseStream.SetLength(0); return data; } public void Dispose() { this.bytes = null; if (this.reader != null) this.reader.Dispose(); if (this.writer != null) this.writer.Dispose(); } }
Server端:
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Threading; using System.Net; using System.Net.Sockets; namespace TestServer { class Program { static TcpListener listener = null; static ServerLobby defaultLobby; static void Main(string[] args) { Int32 port = 13000; IPAddress localAddr = IPAddress.Parse("127.0.0.1"); listener = new TcpListener(localAddr, port); //false: 允许多个程序监听同一个端口 //listener.ExclusiveAddressUse = false; //ReuseAddress: 允许将套接字绑定到已在使用中的地址 //listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); listener.ExclusiveAddressUse = true; listener.Start(); Console.WriteLine("Server started"); Console.WriteLine("Server IP: {0} Port: {1}", localAddr.ToString(), port); //建立默认大厅 defaultLobby = new ServerLobby(); BeginAcceptTcpClient(listener); Console.Read(); } // 开始监听客户端连接 static void BeginAcceptTcpClient(TcpListener listener) { try { listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAcceptTcpClient), listener); } catch (SocketException e) { Console.WriteLine("SocketException: {0}", e); } catch (ObjectDisposedException e) { //对已释放的对象执行操作时所引发的异常 Console.WriteLine("ObjectDisposedException: {0}", e); } } static void OnBeginAcceptTcpClient(IAsyncResult ar) { TcpListener listener = (TcpListener)ar.AsyncState; TcpClient client = listener.EndAcceptTcpClient(ar); Console.WriteLine("收到TCP连接: IP={0}", client.Client.RemoteEndPoint.ToString()); //进入大厅 ServerPlayer player = new ServerPlayer(client); defaultLobby.OnEnterServerPlayer(player); ThreadPoolInfo(); BeginAcceptTcpClient(listener); } static void Stop() { if (listener != null) listener.Stop(); } //显示线程池现状 static void ThreadPoolInfo() { int a, b; ThreadPool.GetAvailableThreads(out a, out b); string message = string.Format("CurrentThreadId is : {0}\n" + "WorkerThreads is : {1}\nCompletionPortThreads is : {2}\n", Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString()); Console.WriteLine(message); } } } // 大厅,管理所有ServerPlayer public class ServerLobby { private List<ServerPlayer> playerList = new List<ServerPlayer>(); // 有玩家进入大厅 public void OnEnterServerPlayer(ServerPlayer player) { player.OnEnterLobby(this); playerList.Add(player); } // 有玩家离开大厅 public void OnLeaveServerPlayer(ServerPlayer player) { playerList.Remove(player); player.OnLeaveLobby(); } // 向大厅中的所有玩家广播消息 public void BroadcastMessage(string msg) { for (int i = 0; i < playerList.Count; i++) { if (playerList[i] == null) continue; playerList[i].SendMessage(msg); } } } /// <summary> /// /// </summary> public class ServerPlayer : UniNetwork { private ServerLobby mServerLobby; public string IP { get { return clientIP; } } public ServerPlayer(TcpClient tcpClient) { this.SetTcpClient(tcpClient); this.BeginRead(); } protected override void OnDisconnected() { CloseConnect(); } protected override void OnPackageComplete(ProtocolData pd) { string message = pd.ReadUTF8String(); Console.WriteLine("Message: {0}", message); mServerLobby.BroadcastMessage("sync: " + message + "\r\n"); } protected override void OnInvalidPackage() { SendMessage("Kicked out by the server"); CloseConnect(); } // 本玩家进入大厅触发 public void OnEnterLobby(ServerLobby lobby) { mServerLobby = lobby; //通知大厅中的其他玩家 mServerLobby.BroadcastMessage(IP + " enter lobby\r\n"); } // 本玩家离开大厅触发 public void OnLeaveLobby() { //通知大厅中的其他玩家 mServerLobby.BroadcastMessage(IP + " leave lobby\r\n"); mServerLobby = null; } // 关闭TCP链接 private void CloseConnect() { //离开大厅 if (mServerLobby != null) mServerLobby.OnLeaveServerPlayer(this); this.Dispose(); } }
Client端:
MainWindow.xaml
<Window x:Class="TestClientSD.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Client" Height="320" Width="300" ResizeMode="NoResize" > <Grid> <TextBox Name="IPTextBox" HorizontalAlignment="Left" Height="23" Margin="9,10,0,0" TextWrapping="Wrap" Text="127.0.0.1:13000" VerticalAlignment="Top" Width="120"/> <Button Name="ConnectButton" Content="Connect" HorizontalAlignment="Left" Margin="136,11,0,0" VerticalAlignment="Top" Width="75" Click="ConnectButton_Click"/> <!--聊天内容区域--> <Border HorizontalAlignment="Center" VerticalAlignment="Center" Width="269" Height="190" CornerRadius="5" BorderBrush="Blue" BorderThickness="5" Margin="12,40,12.6,59.4"> <TextBox Name="ReceiveTextBlock" HorizontalAlignment="Left" Margin="3,3,3,3" TextWrapping="Wrap" VerticalAlignment="Top" Height="185" Width="269"/> </Border> <!--End--> <TextBox Name="InputTextBox" HorizontalAlignment="Left" Height="23" Margin="12,248,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="185"/> <Button Name="SendButton" Content="Send" HorizontalAlignment="Left" Margin="206,250,0,0" VerticalAlignment="Top" Width="75" Click="SendButton_Click"/> </Grid> </Window>
MainWindow.xaml.cs
/* * 由SharpDevelop创建。 * 用户: Admin * 日期: 2019/9/11 * 时间: 10:51 * * 要改变这种模板请点击 工具|选项|代码编写|编辑标准头文件 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.IO; using System.Threading; using System.Net; using System.Net.Sockets; using System.Windows.Threading;//DispatcherTimer namespace TestClientSD { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private readonly UniNetwork network = new UniNetwork(); private DispatcherTimer timer; public string ChatContent; public MainWindow() { InitializeComponent(); network.OnConnectedCallback += OnConnectedCallback; network.OnDisconnectedCallback += OnDisconnectedCallback; network.OnPackageCompleteCallback += OnPackageCompleteCallback; timer = new DispatcherTimer(); timer.Tick += new EventHandler(OnTick); timer.Interval = new TimeSpan(0, 0, 1); timer.Start(); } private void OnConnectedCallback(IAsyncResult result) { ChatContent += "connect server successed!\r\n"; } private void OnDisconnectedCallback() { ChatContent += "disconnected!\r\n"; } private void OnPackageCompleteCallback(ProtocolData pd) { string message = pd.ReadUTF8String(); Console.WriteLine("Message: {0}", message); ChatContent += message; } private void OnTick(object sender, EventArgs e) { this.ReceiveTextBlock.Text = ChatContent; this.ReceiveTextBlock.ScrollToEnd(); } private void ConnectButton_Click(object sender, RoutedEventArgs e) { string text = this.IPTextBox.Text; string[] arr = text.Split(new char[] { ':' }); string IP = arr[0]; int port = int.Parse(arr[1]); Console.WriteLine("IP={0}, port={1}", IP, port); Console.WriteLine("preparing to connect in main thread " + Thread.CurrentThread.ManagedThreadId); network.Connect(IP, port); } private void SendButton_Click(object sender, RoutedEventArgs e) { string msg = this.InputTextBox.Text; this.InputTextBox.Text = string.Empty; network.SendMessage(msg); } } }
运行测试
标签: C#
日历
最新文章
随机文章
热门文章
分类
存档
- 2024年11月(3)
- 2024年10月(5)
- 2024年9月(3)
- 2024年8月(3)
- 2024年7月(11)
- 2024年6月(3)
- 2024年5月(9)
- 2024年4月(10)
- 2024年3月(11)
- 2024年2月(24)
- 2024年1月(12)
- 2023年12月(3)
- 2023年11月(9)
- 2023年10月(7)
- 2023年9月(2)
- 2023年8月(7)
- 2023年7月(9)
- 2023年6月(6)
- 2023年5月(7)
- 2023年4月(11)
- 2023年3月(6)
- 2023年2月(11)
- 2023年1月(8)
- 2022年12月(2)
- 2022年11月(4)
- 2022年10月(10)
- 2022年9月(2)
- 2022年8月(13)
- 2022年7月(7)
- 2022年6月(11)
- 2022年5月(18)
- 2022年4月(29)
- 2022年3月(5)
- 2022年2月(6)
- 2022年1月(8)
- 2021年12月(5)
- 2021年11月(3)
- 2021年10月(4)
- 2021年9月(9)
- 2021年8月(14)
- 2021年7月(8)
- 2021年6月(5)
- 2021年5月(2)
- 2021年4月(3)
- 2021年3月(7)
- 2021年2月(2)
- 2021年1月(8)
- 2020年12月(7)
- 2020年11月(2)
- 2020年10月(6)
- 2020年9月(9)
- 2020年8月(10)
- 2020年7月(9)
- 2020年6月(18)
- 2020年5月(4)
- 2020年4月(25)
- 2020年3月(38)
- 2020年1月(21)
- 2019年12月(13)
- 2019年11月(29)
- 2019年10月(44)
- 2019年9月(17)
- 2019年8月(18)
- 2019年7月(25)
- 2019年6月(25)
- 2019年5月(17)
- 2019年4月(10)
- 2019年3月(36)
- 2019年2月(35)
- 2019年1月(28)
- 2018年12月(30)
- 2018年11月(22)
- 2018年10月(4)
- 2018年9月(7)
- 2018年8月(13)
- 2018年7月(13)
- 2018年6月(6)
- 2018年5月(5)
- 2018年4月(13)
- 2018年3月(5)
- 2018年2月(3)
- 2018年1月(8)
- 2017年12月(35)
- 2017年11月(17)
- 2017年10月(16)
- 2017年9月(17)
- 2017年8月(20)
- 2017年7月(34)
- 2017年6月(17)
- 2017年5月(15)
- 2017年4月(32)
- 2017年3月(8)
- 2017年2月(2)
- 2017年1月(5)
- 2016年12月(14)
- 2016年11月(26)
- 2016年10月(12)
- 2016年9月(25)
- 2016年8月(32)
- 2016年7月(14)
- 2016年6月(21)
- 2016年5月(17)
- 2016年4月(13)
- 2016年3月(8)
- 2016年2月(8)
- 2016年1月(18)
- 2015年12月(13)
- 2015年11月(15)
- 2015年10月(12)
- 2015年9月(18)
- 2015年8月(21)
- 2015年7月(35)
- 2015年6月(13)
- 2015年5月(9)
- 2015年4月(4)
- 2015年3月(5)
- 2015年2月(4)
- 2015年1月(13)
- 2014年12月(7)
- 2014年11月(5)
- 2014年10月(4)
- 2014年9月(8)
- 2014年8月(16)
- 2014年7月(26)
- 2014年6月(22)
- 2014年5月(28)
- 2014年4月(15)
友情链接
- Unity官网
- Unity圣典
- Unity在线手册
- Unity中文手册(圣典)
- Unity官方中文论坛
- Unity游戏蛮牛用户文档
- Unity下载存档
- Unity引擎源码下载
- Unity服务
- Unity Ads
- wiki.unity3d
- Visual Studio Code官网
- SenseAR开发文档
- MSDN
- C# 参考
- C# 编程指南
- .NET Framework类库
- .NET 文档
- .NET 开发
- WPF官方文档
- uLua
- xLua
- SharpZipLib
- Protobuf-net
- Protobuf.js
- OpenSSL
- OPEN CASCADE
- JSON
- MessagePack
- C在线工具
- 游戏蛮牛
- GreenVPN
- 聚合数据
- 热云
- 融云
- 腾讯云
- 腾讯开放平台
- 腾讯游戏服务
- 腾讯游戏开发者平台
- 腾讯课堂
- 微信开放平台
- 腾讯实时音视频
- 腾讯即时通信IM
- 微信公众平台技术文档
- 白鹭引擎官网
- 白鹭引擎开放平台
- 白鹭引擎开发文档
- FairyGUI编辑器
- PureMVC-TypeScript
- 讯飞开放平台
- 亲加通讯云
- Cygwin
- Mono开发者联盟
- Scut游戏服务器引擎
- KBEngine游戏服务器引擎
- Photon游戏服务器引擎
- 码云
- SharpSvn
- 腾讯bugly
- 4399原创平台
- 开源中国
- Firebase
- Firebase-Admob-Unity
- google-services-unity
- Firebase SDK for Unity
- Google-Firebase-SDK
- AppsFlyer SDK
- android-repository
- CQASO
- Facebook开发者平台
- gradle下载
- GradleBuildTool下载
- Android Developers
- Google中国开发者
- AndroidDevTools
- Android社区
- Android开发工具
- Google Play Games Services
- Google商店
- Google APIs for Android
- 金钱豹VPN
- TouchSense SDK
- MakeHuman
- Online RSA Key Converter
- Windows UWP应用
- Visual Studio For Unity
- Open CASCADE Technology
- 慕课网
- 阿里云服务器ECS
- 在线免费文字转语音系统
- AI Studio
- 网云穿
- 百度网盘开放平台
- 迅捷画图
- 菜鸟工具
- [CSDN] 程序员研修院
- 华为人脸识别
- 百度AR导航导览SDK
- 海康威视官网
- 海康开放平台
- 海康SDK下载
- git download
交流QQ群
-
Flash游戏设计: 86184192
Unity游戏设计: 171855449
游戏设计订阅号