一、工程结构
配置条件编译: [生成]->[配置管理器]
二、代码
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); } } }
运行测试