鸟语天空
一个简易聊天室
post by:追风剑情 2019-9-16 15:39

一、工程结构

2222.png

配置条件编译: [生成]->[配置管理器]

111111.png

二、代码

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);
        }
    }
}


运行测试

1111.png

评论:
发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容