Unity 本地服务器 (异步)源码 登录环节测试

初学服务器 对本地服务器的使用理解 (异步)


       通过异步开启线程 利用 Socket 来进行连接服务器和客户端 服务器需要绑定ip地址 端口 客户端通过端口连接服务器 在数据传递中只能进行字节数组传递

    异步连接 在异步中有开始就有结束 开启异步连接 在回调函数中endAccept() 递归进行下一个用户的连接 通过 IPEndPoint 获取终端信息 连接成功开始异步接收消息

    在进行接发消息的时候 是通过异步进行接收 在接收消息中进行递归接收 进行消息解码 byte[] 数组中前四位是消息号(是我们自己定义的,后期传递消息估计是任何类型 )通过解码 去除前四位是接收的消息 通过消息中心、传来的消息号 进行发送给侦听该消息号的位置 需要传递 消息 和 传来消息的客户端 这样在回消息的时候知道发给哪个客户端

    服务端发送消息也是一样 需要将消息号提前编码在发送的字节数组中 让客户端进行解码 发消息

   服务器代码 (利用C# 控制台应用 创建项目)

   单例模式                

public class Singleton<T> where T : class, new()
{
    static T instance;
    public static T Ins
    {
        get
        {
            if (instance == null)
            {
                instance = new T();
            }
            return instance;
        }
    }
}

服务器管理类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

/// 服务器 网络管理类
/// 管理网络 所有连接你服务器的网络用户)
/// 1 统计连接了服务器的客户端 等待客户端连接的方法
/// 2 收取客户端发来的消息
/// 3 发送消息给客户端
/// </summary>
public class NetMgr : Singleton<NetMgr>
{
    Socket socket;

    List<Client> sockets = new List<Client>();

    public void InitServe()
    {
        Console.WriteLine("服务器开启");
        //地址族       inter net   数据流:一套用于传输数据的类型   协议
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        // 继承关系 ip地址访问权限 端口
        socket.Bind(new IPEndPoint(IPAddress.Any, 9999));
        socket.Listen(10);  //最大连接数


        开启线程  同步等待连接   避免多开线程
        //Thread thread = new Thread(OnAccept);   //委托类型
        开启线程
        //thread.Start();

        //异步等待连接
        socket.BeginAccept(AsyOnAccept, null);
    }

    /// <summary>
    /// 异步连接
    /// </summary>
    /// <param name="ar"></param>
    private void AsyOnAccept(IAsyncResult ar) //异步的结果
    {
        try    //try 报错 避免服务器宕机 保持原有功能的实现 
        {
            //创建客户端数据
            Socket sc_Client = socket.EndAccept(ar);  //最终执行给谁

            IPEndPoint iPEndPoint = sc_Client.RemoteEndPoint as IPEndPoint;

            Client client = new Client();
            client.CliSocket = sc_Client;
            client.name = iPEndPoint.Port.ToString();
            sockets.Add(client);

            Console.WriteLine(client.name + "上线");

            //开始异步接收
            sc_Client.BeginReceive(client.data, 0, client.data.Length, SocketFlags.None, AsyOnReceive, client);

            //AsyOnSend(UTF8Encoding.UTF8.GetBytes("Hello Client"), client);

            //递归下一个上线
            socket.BeginAccept(AsyOnAccept, null);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

    /// <summary>
    /// 异步发送消息
    /// </summary>
    /// <param name="data"></param>
    /// <param name="client"></param>
    public void AsyOnSend(int msgId, byte[] data, Client client)
    {
        byte[] endData = new byte[0];

        endData = endData.Concat(BitConverter.GetBytes(msgId).Concat(data)).ToArray();

        client.CliSocket.BeginSend(endData, 0, endData.Length, SocketFlags.None, AsyOnSend, client);
    }

    /// <summary>
    /// 异步发送数据
    /// </summary>
    /// <param name="ar"></param>
    private void AsyOnSend(IAsyncResult ar)
    {
        try
        {
            Client client = ar.AsyncState as Client;

            int len = client.CliSocket.EndSend(ar);

            Console.WriteLine("服务端发送字节长度为:" + len);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

    /// <summary>
    /// 异步接收数据
    /// </summary>
    /// <param name="ar"></param>
    private void AsyOnReceive(IAsyncResult ar)
    {
        try
        {
            Client cli = ar.AsyncState as Client;   //此api可以获取 BeginReceive 传来的 object

            int len = cli.CliSocket.EndReceive(ar);
            Console.WriteLine("服务器接收:" + len);

            //             前四个是消息号  后面字节全是内容  需要转类型
            //这里的消息一定是由 消息号 和 内容 组成的
            //拆除消息号和内容
            //消息号  int32 4个字节

            //获取前四位的字节 是消息号
            int msgId = BitConverter.ToInt32(cli.data, 0);         //获取全部消息
            byte[] info = new byte[len - 4];                       //去掉消息号的字节数组
            Buffer.BlockCopy(cli.data, 4, info, 0, info.Length);   //字节数组复制

            MsgData msgData = new MsgData();
            msgData.data = info;            //内容 
            msgData.client = cli;           //发给谁

            //消息发送
            MessageCenter<MsgData>.Ins.Dispatch(msgId, msgData);

            //重复接收
            cli.CliSocket.BeginReceive(cli.data, 0, cli.data.Length, SocketFlags.None, AsyOnReceive, cli);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }


    /// <summary>
    /// 同步接收
    /// </summary>
    private void OnAccept()
    {
        while (true)
        {
            //创建一个客户端接收者
            Socket socket_clinet = socket.Accept();        //分出 给客户端连接    在套接字执行操作时while不执行 停在上一句话的等待
                                                           //获取终端信息
            IPEndPoint iPEndPoint = socket_clinet.RemoteEndPoint as IPEndPoint;  //转换终端ip

            //上线的入集合   赋初始值
            Client client = new Client();
            client.CliSocket = socket_clinet;
            client.name = iPEndPoint.Port.ToString();
            sockets.Add(client);
            //上线成功
            Console.WriteLine("客户端{0}链接成功{1}", iPEndPoint.Address, iPEndPoint.Port);
            Console.WriteLine(sockets.Count);
            //客户端接收消息
            while (true)
            {
                //客户端和服务器之间只能通过字节发送 
                int len = socket_clinet.Receive(client.data);   //套接字的API 等待客户端发送消息才往下走

                Console.WriteLine("收到消息:" + UTF8Encoding.UTF8.GetString(client.data));

                //发送给客户端  单个客户端 
                OnSendClinet(UTF8Encoding.UTF8.GetBytes("Hello Client"), client);
            }
        }
    }
    /// <summary>
    /// 发送给客户端  单个客户端 
    /// </summary>
    /// <param name="data"></param>
    /// <param name="client"></param>
    public void OnSendClinet(byte[] data, Client client)
    {
        client.CliSocket.Send(data);
    }

}

    消息类型

public class MsgData
{
    public byte[] data;     //储存发送的内容
    public Client client;   //客户端   (发给谁)
}

    消息中心

public class MessageCenter<T> : Singleton<MessageCenter<T>>
{
    Dictionary<int, Action<T>> msgs_Dic = new Dictionary<int, Action<T>>();

    public void AddListener(int msgId, Action<T> action)
    {
        if (msgs_Dic.ContainsKey(msgId))
        {
            msgs_Dic[msgId] += action;
        }
        else
        {
            msgs_Dic.Add(msgId, action);
        }
    }
    public void RemoveListener(int msgId, Action<T> action)
    {
        if (msgs_Dic.ContainsKey(msgId))
        {
            msgs_Dic[msgId] -= action;
            if (msgs_Dic[msgId] == null)
            {
                msgs_Dic.Remove(msgId);
            }
        }
    }

    public void Dispatch(int msgId, T t)
    {
        if (msgs_Dic.ContainsKey(msgId))
        {
            msgs_Dic[msgId]?.Invoke(t);
        }
    }
}

///消息id

public class MessageId
{
    public const int CS_LOGIN = 1001;        //上行协议
    public const int SC_LOGIN_CALL = 1002;   //下行协议
}

 客户端代码(unity代码)

        接下来就是客户端代码了 下面文字是介绍主要主要是怎么连接本地服务器的

    异步连接服务器 持有 socket 通过socket进行连接 开始连接 客户端ip地址 服务器端口 回调函数

    在回调函数中 可检测是否连接成功 开始接收数据

    在此接收数据和服务器接收数据无二致 区别在于 服务器需要管理多个账户 用于接受的字节数组不固定 因为在客户端只有本身 所以数据声明一个总字节数组

    消息号要和服务端的消息号一致避免解码消息号不对应 

客户端管理类

using System.Net.Sockets;
using UnityEngine;
using System.Text;
using System.Linq;
using System.Threading;
using System;
/// <summary>
/// 网络管理类 
/// </summary>
public class NetMgr : Singleton<NetMgr>
{
    Socket socket;   //相当于链接的两个线  客户端 服务 端都有一个
    byte[] data = new byte[1024];    //需要存东西 没有空间存不了

    public void ContentNet()     //连接服务器
    {
        //                     地址族                    套接字类型 数据流     协议类型 Tcp
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //socket.Connect("10.161.54.108", 815);   //客户端链接服务端的接口

        //开始线程  在线程中接收发送消息
        //Thread thread = new Thread(OnAccept);
        //thread.Start();

        //客户端改成异步线程  开始终端连接
        socket.BeginConnect("10.161.54.108", 9999, AsyOnConnect, null);

        //还有终端异步接发消息
    }
    /// <summary>
    /// 异步开始发送消息
    /// </summary>
    /// <param name="msgId"></param>
    /// <param name="data"></param>
    public void AsyOnSend(int msgId, byte[] data)
    {
        byte[] endData = new byte[0];
        endData = endData.Concat(BitConverter.GetBytes(msgId)).Concat(data).ToArray();
        Debug.Log(socket);
        socket.BeginSend(endData, 0, endData.Length, SocketFlags.None, AsyOnSend, null);
    }

    /// <summary>
    /// 异步发送消息   只需发一次
    /// </summary>
    /// <param name="ar"></param>
    private void AsyOnSend(IAsyncResult ar)
    {
        try
        {
            int len = socket.EndSend(ar);
            Debug.Log("client send message that :" + len);
        }
        catch (Exception ex)
        {
            Debug.Log(ex);

        }
    }

    /// <summary>
    /// 异步连接
    /// </summary>
    /// <param name="ar"></param>
    private void AsyOnConnect(IAsyncResult ar)
    {
        try
        {
            //终端连接
            socket.EndConnect(ar);
            Debug.Log("连接成功!!");

            socket.BeginReceive(data, 0, data.Length, SocketFlags.None, AsyOnReceive, null);
        }
        catch (Exception ex)
        {
            Debug.Log(ex);
        }

    }
    /// <summary>
    /// 异步接收消息
    /// </summary>
    /// <param name="ar"></param>
    private void AsyOnReceive(IAsyncResult ar)
    {
        try
        {
            int len = socket.EndReceive(ar);
            int megId = BitConverter.ToInt32(data, 0);  //消息号
            byte[] info = new byte[len - 4];            //真实消息内容
            Buffer.BlockCopy(data, 4, info, 0, info.Length);

            MessageCenter<byte[]>.Ins.Dispatch(megId, info);
            socket.BeginReceive(data, 0, data.Length, SocketFlags.None, AsyOnReceive, null);
        }
        catch (Exception)
        {
            throw;
        }
    }

    private void OnAccept(object obj)
    {
        while (true)
        {
            int len = socket.Receive(data);   //收消息
            Debug.Log("服务器回消息:" + UTF8Encoding.UTF8.GetString(data));
        }
    }

    public void OnSend(byte[] data)
    {
        int len = socket.Send(data);   //发消息
        Debug.Log("发送消息字节长度为" + len);
    }
}

最后测试是否连接成功以及是否发送消息成功(拿登录举例)

客户端

using System;
using System.Text;
using UnityEngine;
using UnityEngine.UI;

public class LoginUI : MonoBehaviour
{
    public Button login_Btn;
    private void Start()
    {
        MessageCenter<byte[]>.Ins.AddListener(MessageId.SC_LOGIN_CALL, Login);

        login_Btn.onClick.AddListener(() =>
        {
            string acc = "123";
            string pwd = "123";
            string all = acc + "|" + pwd;
            NetMgr.Ins.AsyOnSend(MessageId.CS_LOGIN, UTF8Encoding.UTF8.GetBytes(all));
        });
    }
    private void Login(byte[] obj)
    {
        bool flag = BitConverter.ToBoolean(obj, 0);
        if (flag)
        {
            Debug.Log("登录成功");
        }
        else
        {
            Debug.Log("登录失败");
        }
    }
}

服务端

using System;
using System.Collections.Generic;
using System.Text;

public class LoginManager : Singleton<LoginManager>
{
    Dictionary<string, string> account_Dic = new Dictionary<string, string>();

    public void Init()
    {
        MessageCenter<MsgData>.Ins.AddListener(MessageId.CS_LOGIN, CheckLogin);
    }

    private void CheckLogin(MsgData msg)
    {
        //未发送成功  字符串为数字串 字符串转换为题
        string content = UTF8Encoding.UTF8.GetString(msg.data);

        string acc = content.Split('|')[0];
        string pwd = content.Split('|')[1];

        bool flag = false;

        if (account_Dic.ContainsKey(acc))
        {
            if (account_Dic[acc] == pwd)
            {
                flag = true;
            }
        }
        NetMgr.Ins.AsyOnSend(MessageId.SC_LOGIN_CALL, BitConverter.GetBytes(flag), msg.client);
    }
}

 开启服务器  客户端连接

 点击按钮发送消息  以及服务端发送消息

最后

        客户端的消息中心以及消息号没有展示代码 主要是和服务端一样的 这里不进行重复说明

由此 便连接unity 和 本地服务器

简单说明了两者是如何建立联系的 如何传递数据的 这个方式在进行多个消息处理时 会出现粘包现象 这篇文章不进行讲解 可在我主页查询相关代码

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值