ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【学习总结】《Unity3D网络游戏》Part 1

2022-01-19 15:59:43  阅读:158  来源: 互联网

标签:Unity3D Socket listenfd System Part 网络游戏 socket ar 客户端


以前都是在客户端这边研究一些逻辑和画面效果,乘着大四最后一个寒假学习一些服务端和客户端交互的内容,正好买了这本书,边看边做边写笔记,希望能用到毕设里面

另外多提一句。微软居然把暴雪收购了!!我超,这下压力来到了索尼这边

目录

Socket相关

socket概念

网络上的两个程序通过一个双侠奴工的通信连接实现数据交换,这个连接的一端称为一个Socket。一个Socket包含了进行网络通信必需的五种信息:
连接使用的协议、本地主机的IP地址、本地的协议端口、远程主机的IP地址和远程协议端口
在这里插入图片描述

Socket通信的流程

  1. 开启一个连接之前,需要创建一个Socket对象(使用API Socket),然后绑定本地使用的端口(使用API Bind)。对服务端而言,绑定的步骤相当于给手机插上SIM卡,确定了“手机号”。对客户端而言,连接时(使用API Connect)会由系统分配端口,可以省去绑定的步骤
  2. 服务端开启监听(使用API Listen),等待客户端接入。相当于电话开机,等待别人呼叫
  3. 客户端连接服务器(使用API Connect),相当于手机拨号
  4. 服务器接收连接(使用API Accept),相当于接听电话并说出“喂”
  5. 客户端和服务端通过Send和Receive等API收发数据,操作系统会自动完成数据的确认、重传等步骤,确保传输的数据准确无误
  6. 某一方关闭连接(使用API Close),操作系统会执行“四次挥手”的步骤,关闭双方连接,相当于挂断电话

在这里插入图片描述
上图画出了整个通信流程,而后续的代码基本就是根据这个流程图来写的

TCP和UDP协议

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,与TCP相对应的UDP协议是无连接的、不可靠的、但传输效率较高的协议

Unity异步Echo程序

客户端部分

首先给使用UGUI搭建一个简单的界面
在这里插入图片描述
客户端代码如下

using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using System.Net.Sockets;
using UnityEngine.UI;

public class Echo : MonoBehaviour
{
    Socket socket;//定义套接字

    //UGUI 这里声明界面元素
    public InputField inputField;
    public Text text;

    //接收缓冲区
    byte[] readBuff = new byte[1024];
    string recvStr = "";

    //点击链接按钮
    public void Connection()
    {
        //Socket
        socket = new Socket(AddressFamily.InterNetwork,
            SocketType.Stream,ProtocolType.Tcp);
        socket.BeginConnect("127.0.0.1",8888,ConnectCallback,socket);//改为异步连接
        // socket.Connect("127.0.0.1",8888);//连接函数
    }

    //Connect回调函数
    public void ConnectCallback(IAsyncResult ar)
    {
        try{
            Socket socket = (Socket) ar.AsyncState;
            socket.EndConnect(ar);
            Debug.Log("Socket Connect Succ");
            socket.BeginReceive(readBuff,0,1024,0,ReceiveCallback,socket);//异步接收
        }
        catch(SocketException ex){
            Debug.Log("Socket Connect fail" + ex.ToString());
        }
    }

    public void ReceiveCallback(IAsyncResult ar)
    {
        try{
            Socket socket = (Socket) ar.AsyncState;
            int count = socket.EndReceive(ar);
            recvStr = System.Text.Encoding.Default.GetString(readBuff,0,count);
            socket.BeginReceive(readBuff,0,1024,0,ReceiveCallback,socket);//这里是一个递归的调用
        }
        catch(SocketException ex){
            Debug.Log("Socket Receive fail" + ex.ToString());
        }
    }

    //点击发送按钮
    public void Send()
    {
        //Send
        string sendStr = inputField.text;//得到发送的消息
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
        socket.BeginSend(sendBytes,0,sendBytes.Length,0,SendCallback,socket);
        // socket.Send(sendBytes);
    }

    //Send回调
    public void SendCallback(IAsyncResult ar)
    {
        try{
            Socket socket = (Socket) ar.AsyncState;
            int count = socket.EndSend(ar);
            Debug.Log("Socket Send Succ" + count);
        }
        catch(SocketException ex){
            Debug.Log("Socket Send fail" + ex.ToString());
        }
    }

    private void Update() 
    {
        text.text = recvStr;
    }
}

注释部分

  1. Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
    用于创建一个Socket对象,它的三个参数分别代表地址族,套接字类型和协议。地址族知名使用的是IPV4还是IPV6,
    InterNetwork代表IPV4,InterNetworkV6代表IPV6。
    SocketType是套接字类型,游戏中最常用的是字节流套接字,即Stream。ProtocolType指明协议

  2. 客户端通过socket.Connect(远程IP地址,远程端口)连接服务端。
    Connect是一个阻塞方法,程序会卡住直到服务端回应(接受、拒绝或者超时)

  3. 客户端通过socket.send发送数据。GetBytes(字符串)把字符串转换成byte这也是一个阻塞方法。该方法接受一个byte[]类型的参数指明要发送的内容。Send的返回值指明发送数据的长度程序用System.Text.Encoding.Default.[]数组,然后发送给服务端

  4. 客户端使用socket.Receive接受服务端数据。
    Receive也是阻塞方法,没有收到服务端的数据时,程序将卡在Receive不会往下执行
    Receive带有一个byte[]类型的参数,它存储接收到的数据
    Receive的返回值指明接收到的数据的长度。之后使用System.Text.Encoding.Default.GetString(readBuff,0,count)将byte[]

  5. 通过socket.close()关闭连接

  6. 通过BeginConnect和EndConnect来让客户端代码变成异步进行,防止程序卡死
    IAsyncResult是.NET提供的一种异步操作,通过名为BeginXXX和EndXXX的两个方法来为实现原本同步方法的异步调用
    BeginXXX方法中包含同步方法中所需的参数,此外还包含两个参数:一个AsyncCallback委托和一个用户定义的状态对象委托用来调用回调方法,状态对象用来向回调方法传递状态信息,且BeginXXX方法返回一个实现IAsyncResult接口的对象,EndXXX方法用于结束异步操作并且返回结果
    EndXXX方法含有一个IAsyncResult参数,用于获取异步操作是否完成的信息,它的返回值与同步方法相同

  7. BeginReceive的参数为(readBuff,0,1024,ReceiveCallback,socket)
    第一个参数readBuff表示接收缓冲区,第二个参数0表示从readBuff第0位开始接收数据,这个参数和TCP粘包问题有关
    第三个参数1024代表每次最多接收1024个字节,假如服务端回应一串长长的数据,那一次也只会收到1024个字节

  8. BeginReceive的调用位置
    程序在两个地方调用了BeginReceive,一个是ConnectCallback,在连接成功后,就开始接收数据,接收到数据之后,回调函数ReceiveCallback被调用
    另一个是BeginReceive内部,接受完一串数据之后,等待下一串数据的到来

  9. Update和recvStr
    在Unity中,只有主线程可以操作UI组件。由于异步回调是在其他线程执行的,如果在BeginReceive给text.text赋值,Unity会弹出异常信息,所以只能在主线程中

  10. 异步BeginSend参数说明
    buffer Byte类型的数组,包含需要发送的数据
    offset 从Buffer中的offset位置开始发送
    size 将要发送的字节数
    socketFlags SocetFlags值的按位组合,这里设置为0
    callback 回调函数,一个AsyncCallbakc委托
    state 一个用户定义对象,其中包含发送操作的相关信息。当操作完成时,此对象会传递给EndSend委托

服务器部分

服务器这边使用Visual Studio建立一个C#控制台程序,代码如下

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

namespace EchoServer
{
    //该类用于保存客户端的信息
    class ClientState
    {
        public Socket socket;
        public byte[] readBuff = new byte[1024];
    }

    class Program
    {
        //监听Socket
        static Socket listenfd;

        //定义一个字典集合保存所有客户端的信息,这样的结构可以通过clientState = clients[Socket]进行快速访问
        static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();

        static void Main(string[] args)
        {
            Console.WriteLine("Hello world");

            //Socket
            listenfd = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

            //Bind
            IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
            IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
            listenfd.Bind(ipEp);

            //Listen
            listenfd.Listen(0);
            Console.WriteLine("[服务器]启动成功");

            //Accept
            listenfd.BeginAccept(AccpetCallback,listenfd);

            //waiting
            Console.ReadLine();
        }

        //Accept回调
        //处理三件事情:
        //1.给新的连接分配ClientState,并且把它添加到clients列表之中
        //2.异步接收客户端数据
        //3.再次调用BeginAccept实现循环
        public static void AccpetCallback(IAsyncResult ar)
        {
            try
            {
                Console.WriteLine("[服务器]Accept");
                Socket listenfd = (Socket)ar.AsyncState;
                Socket clientfd = listenfd.EndAccept(ar);

                //clients列表,将连接上的客户端Socket加入列表
                ClientState state = new ClientState();
                state.socket = clientfd;
                clients.Add(clientfd,state);

                //接收数据BeginReceive
                clientfd.BeginReceive(state.readBuff,0,1024,0,ReceiveCallback,state);

                //继续接收Accept
                listenfd.BeginAccept(AccpetCallback,listenfd);
            }
            catch(SocketException ex)
            {
                Console.WriteLine("Socket Accept fail" + ex.ToString());
            }
        }

        //ReceiveCallBack是BeginReceive的回调函数,它也处理了三件事情
        //1.服务端收到消息之后,回应客户端
        //2.如果收到客户端关闭连接的信号“if(count==0)”,断开连接
        //3.继续调用BeginReceive接收下一个数据
        public static void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                ClientState state = (ClientState)ar.AsyncState;
                Socket clientfd = state.socket;
                int count = clientfd.EndReceive(ar);

                //客户端关闭
                if(count == 0)
                {
                    clientfd.Close();
                    clients.Remove(clientfd);
                    Console.WriteLine("Socket Close");
                    return;
                }

                string recvStr = System.Text.Encoding.Default.GetString(state.readBuff,0,count);
                byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo"+ recvStr);

                clientfd.Send(sendBytes);//减少代码量,不适用异步
                clientfd.BeginReceive(state.readBuff,0,1024,0,ReceiveCallback,state);
            }
            catch(SocketException ex)
            {
                Console.WriteLine("Socket Receive fail" + ex.ToString());
            }
        }
    }
}

//注释部分
//1.绑定Bind
//listenfd.Bind(ipEp)将给listenfd套接字绑定Ip和端口,这里也可以更改为真实的IP地址,一样有效

//2.监听Listen
//服务端通过listenfd.Listen(backlog)开启监听,等待客户端连接。参数backlog指定队列中最多可以容纳等待接受的连接数,0代表不限制

//3.应答Accept
//开启监听后,服务器调用listenfd.Accept()接受客户端连接。Accept()返回了一个新客户端的Socket对象,对于服务器来说,它有一个监听Socket,用来监听和应答客户端的连接
//对每一个客户端还有一个专门的Socket用来处理该客户端的数据

//4.IPAddress和IPEndPoint
//使用IPAddress指定IP地址,使用IPEndPoint指定IP和端口

//5.Receive方法将接收到的字节流保存到readBuff上

测试

在这里插入图片描述

标签:Unity3D,Socket,listenfd,System,Part,网络游戏,socket,ar,客户端
来源: https://blog.csdn.net/weixin_43890220/article/details/122580132

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有