【实战】Unity3d实战之Unity3d网络游戏实战篇(4):网络基础之Socket套接字

Unity3d实战之Unity3d网络游戏实战篇(4):网络基础之Socket套接字

学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍
以下为个人的学习简记,与诸君共论 由于U3D的官方案例Tank Tutorial中对坦克的基本操作已经有了详尽的描述,因此本文着重于面板系统、服务端基本网络框架和客户端基本网络框架的搭建
如有疑问或者描述错误的地方,欢迎各位提出或修正,讨论交流是获取和巩固知识的重要途径之一!

Socket套接字
 Socket是支持TCP/IP协议网络通信的基本操作单元,可以将套接字看作不同主机间的进程双向通信的端点。举个栗子,小时候玩过的传声筒(如图),传声筒的一段的杯子就类似于Socket,两个人之间要进行通信首先都要有一个杯子(Socket),然后两个杯子之间要有一条线(TCP连接),如果不熟悉TCP连接可以翻看上一节内容。
 【实战】Unity3d实战之Unity3d网络游戏实战篇(4):网络基础之Socket套接字_第1张图片
 
 Socket的通信基本流程:
【实战】Unity3d实战之Unity3d网络游戏实战篇(4):网络基础之Socket套接字_第2张图片
  1. Server需要创建一个Socket,然后绑定一个端口(端口的基本概念请自行搜索)然后进行监听;
  2. Client通过Connect方法连接Server,Server的Socket通过Accept接收Client连接请求,在connect-accept过程中将会进行三次握手;
  3. 连接建立后,两端可以相互发送数据,直到一方发出关闭连接请求,四次挥手后,关闭连接。
  在整个流程中我们需要做的就是实现逻辑,具体的三次握手、四次挥手、数据收发的确认将会由操作系统完成。

同步Socket
 同步Socket能够实现Server和Client两端之间的通信,下面给出简单的实例:
在这里,Server和Client都使用Console Project实现,如果想更直观,可以自行制作GUI界面的程序。
 Server:
 

using System;
using System.Net;               // Remember add Net & Net.Sockets namespace;
using System.Net.Sockets;

namespace Server
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            // Console.WriteLine ("Hello World!");
            Socket servSk = new Socket (
                                  AddressFamily.InterNetwork,   // Address for IPv4
                                  SocketType.Stream,            // DataType for stream
                                  ProtocolType.Tcp);            // Tcp Protocol
            string servId = "127.0.0.1";
            int servPort = 2333;
            IPAddress ipAdr = IPAddress.Parse (servId);
            IPEndPoint ipEp = new IPEndPoint (ipAdr, servPort);
            servSk.Bind (ipEp);                                 // Bind
            servSk.Listen (0);                                  // Listen
            Console.WriteLine ("[Program.Main] Server start.");

            while (true) {
                Socket connSk = servSk.Accept ();               // Accept
                Console.WriteLine ("[Program.Main] Accept connect request from " + connSk.RemoteEndPoint.ToString ());

                byte[] readBuff = new byte[1024];               // Receive
                int count = connSk.Receive (readBuff);
                string readStr = System.Text.Encoding.UTF8.GetString (readBuff, 0, count);
                Console.WriteLine ("[Program.Main] Receive message: " + readStr + " from " + connSk.RemoteEndPoint.ToString ()); 

                byte[] sendBuff = new byte[1024];               // Send
                sendBuff = System.Text.Encoding.UTF8.GetBytes ("Server had receive your message: " + readStr);
                connSk.Send (sendBuff);
            }
        }
    }
}

 Client:
 

using System;
using System.Net;
using System.Net.Sockets;

namespace Client
{
    class Program
    {
        public static void Main (string[] args)
        {
            //Console.WriteLine ("Hello World!");
            Socket clientSk = new Socket (
                                  AddressFamily.InterNetwork,
                                  SocketType.Stream,
                                  ProtocolType.Tcp);

            clientSk.Connect ("127.0.0.1", 2333);               // Connect
            Console.WriteLine ("[Program.Main] Connect to server: 127.0.0.1:2333 success.");

            byte[] sendBuff = new byte[1024];                   // Send
            string sendStr = "Hello Server!";
            sendBuff = System.Text.Encoding.UTF8.GetBytes (sendStr);
            clientSk.Send (sendBuff);

            byte[] readBuff = new byte[1024];                   // Receive
            int count = clientSk.Receive (readBuff);
            string readStr = System.Text.Encoding.UTF8.GetString (readBuff, 0, count);
            Console.WriteLine (readStr);

            Console.ReadLine ();
        }
    }
}

几个要点:
 1. AddressFamily.InterNetwork 指的是IPv4地址;
 2. SocketType.Stream 指的是基于连接的字节流,这是游戏开发中最常用的类型;
 3. ProtocolType.Tcp 顾名思义指的就是TCP协议啦;
 更详细的参数可以参考C#官方文档

异步Socket
 同步Socket适用于单个C/S之间的通信,但通常我们需要Server与多个Client之间进行通信,我们可以通过创建Conn类来管理各个与Client连接的Socket,在Server中初始化一个连接池,每当有新的Client接入时,遍激活连接池里的一个连接进行管理,由于Server在一开始就初始化了连接池,因此会占用更多内存,这是个用空间换时间的策略。
 Conn:
 

using System;
using System.Net;
using System.Net.Sockets;

namespace Server
{
    public class Conn
    {
        private const int BUFFER_SIZE = 1024;

        public Socket socket;                               // The socket connect to client.
        public bool isUse = false;                          // mark this conn's type
        public byte[] readBuff = new byte[BUFFER_SIZE];     // use to store data receive from client/
        public int buffCount = 0;                           // the total length of data which store in readBuff

        public Conn()
        {
            readBuff = new byte[BUFFER_SIZE];
        }

        public void Init(Socket socket)                     // Init conn
        {
            this.socket = socket;
            isUse = true;
            buffCount = 0;
        }

        public int BufferRemain()                           // Return the useful length of readBuff
        {
            return BUFFER_SIZE - buffCount;
        }

        public string GetAddress()                          // Return the client address.
        {
            return socket.RemoteEndPoint.ToString ();
        }

        public void Close()                                 // Use to close conn
        {
            if (isUse == false)
                return;

            Console.WriteLine ("[Conn.Close] Disconnect with " + GetAddress ());
            socket.Close ();
            isUse = false;
        }
    }
}

 Server:
 

using System;
using System.Net;
using System.Net.Sockets;

namespace Server
{
    public class Server
    {
        public Socket servSk;               // Server socket
        public Conn[] conns;                // Connect pool
        public int maxCount = 50;           // Max client count that server accept.

        int NewIndex()                      // get a conns index
        {
            if (conns == null)
                return -1;
            for (int i = 0; i < maxCount; i++) {
                Conn conn = conns [i];
                if (conn == null) {
                    conn = new Conn ();
                    return i;
                }

                if (conn.isUse == false) {
                    return i;
                }
            }

            return -1;
        }


        public void Start(string ip, int port)
        {
            conns = new Conn[maxCount];                 // Init conn
            for (int i = 0; i < maxCount; i++) {
                conns [i] = new Conn ();
            }

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

            IPAddress ipAdr = IPAddress.Parse (ip);
            IPEndPoint ipEp = new IPEndPoint (ipAdr, port);
            servSk.Bind (ipEp);                         // Bind
            servSk.Listen (maxCount);                   // Listen

            servSk.BeginAccept (                        // Begin Accept
                AcceptCb,
                null);
            Console.WriteLine ("[Server.Start] Server start.");
        }

        void AcceptCb(IAsyncResult ar)
        {
            try {
                Socket connSk = servSk.EndAccept(ar);   // End Accept
                int index = NewIndex();

                if(index < 0)
                {
                    Console.WriteLine("[Server.AcceptCb] conns is full.");
                    connSk.Close();
                } else {
                    Conn conn = conns[index];
                    conn.Init(connSk);

                    conn.socket.BeginReceive(           // Begin Receive
                        conn.readBuff,
                        conn.buffCount,
                        conn.BufferRemain(),
                        SocketFlags.None,
                        ReceiveCb,
                        conn);
                }

                servSk.BeginAccept(                     // Begin listen next new client's connect request.
                    AcceptCb,
                    null);
            } catch (Exception ex) {
                Console.WriteLine ("[Server.AcceptCb] Accept connect request fail. " + ex.Message);
            }
        }

        void ReceiveCb(IAsyncResult ar)
        {
            Conn conn = (Conn)ar.AsyncState;

            try {
                int count = conn.socket.EndReceive(ar); // End Receive

                if(count > 0)
                {
                    string recvStr = System.Text.Encoding.UTF8.GetString(conn.readBuff,0,count);
                    Console.WriteLine("[Server.ReceiveCb] Receive message: " + recvStr + " from " + conn.GetAddress());

                    recvStr = conn.GetAddress() + ": " + recvStr;
                    byte[] sendBuff = System.Text.Encoding.UTF8.GetBytes(recvStr);
                    for (int i = 0; i < conns.Length; i++) {
                        Conn tconn = conns[i];
                        if(tconn == null || tconn.isUse == false)
                            continue;

                        Console.WriteLine("[Server.ReceiveCb] Transmit message to " + tconn.GetAddress());
                        tconn.socket.Send(sendBuff);
                    }
                }

                conn.socket.BeginReceive(               // Begin receive next message.
                    conn.readBuff,
                    conn.buffCount,
                    conn.BufferRemain(),
                    SocketFlags.None,
                    ReceiveCb,
                    conn);
            } catch (Exception ex) {
                Console.WriteLine ("[Server.ReceiveCb] Receive message fail. " + ex.Message);
                conn.Close ();
            }
        }
    }
}

Client端可以使用同步Socket中Client的脚本,或者仿照Server脚本中BeginReceive->ReceiveCb的流程实现持续监听Server,随时接收Server发来的数据。

下一节 MySQL的基本操作

你可能感兴趣的:(实战集,Unity3d)