找回密码
 立即注册
查看: 293|回复: 1

Unity实现服务器:1、Socket通信

[复制链接]
发表于 2022-7-10 12:54 | 显示全部楼层 |阅读模式
最近研究了一下Unity的网络框架,踩了很多的坑,所以想在这里写篇文章作为总结,文章只针对实现,不讲过多的原理,后续会陆续更新完整的服务器框架。
要想实现服务器框架,首先就是要使用Socket进行通信,Socket通信大致逻辑:服务端暴露端口号和IP地址,客户端通过访问这个IP地址,进行数据传输,传输的数据为二进制数据。接下来为大家讲讲怎么实现。文章最后放出源码。
需要工具:unity、vs2019
1、首先在服务器端创建服务端脚本Server,我这直接用本地:
大致内容:暴露服务器ip地址、端口号、最大连接数,等待客户端连接后发送给客户端信息,接收客户端发来的信息。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Server
{
    class Program
    {
        private static string ipAdress = "127.0.0.1";  //ip地址
        private static int port = 5000;  //端口号
        private static int maxConnect = 20;  //最大连接数
        private static Socket _serverSocket;
        private static Socket _clientSocket;
        static void Main(string[] args)
        {
            //1、创建一个服务端socket对象
            _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //2、绑定一个ip和端口
            IPAddress ipaddress = IPAddress.Parse(ipAdress);//将一个字符串类型的ip地址,转换成一个IPAdress对象
            EndPoint endPoint = new IPEndPoint(ipaddress, port);
            _serverSocket.Bind(endPoint);//向操作系统申请一个可用的ip和端口号来通讯
            //3、开始监听客户端的连接请求
            _serverSocket.Listen(maxConnect);//设置最大连接数
            Console.WriteLine(string.Format("开启服务端{0}...", _serverSocket.LocalEndPoint));
            Socket clientSocket = _serverSocket.Accept();//阻塞当前线程,直到有一个客户端连接服务器
            Console.WriteLine("接受到客户端的连接请求!");

            //4、发送、接收消息
            string message = "hello client";
            byte[] data = Encoding.UTF8.GetBytes(message);//将字符串转成字节数组
            _clientSocket.Send(data);
            Console.WriteLine("服务器向客户端发送了一条消息:" + message);
            //5、接收客户端发来的信息
            byte[] data2 = new byte[1024];//存放消息的字节数容器 1M大小
            int length = _clientSocket.Receive(data2);
            string messgae2 = Encoding.UTF8.GetString(data2, 0, length);//bytes转换成string
            Console.WriteLine("服务器接受到客户端发来的信息" + messgae2);
        }  
    }
}
2、创建Unity项目,并添加Client脚本。
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class Client : MonoBehaviour
{
    private Socket tcpClient;
    private string serverIP = "127.0.0.1";//服务器ip地址
    private int serverPort = 5000;//端口号
    void Start()
    {
        //1、创建socket
        tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        //2、建立一个连接请求
        IPAddress iPAddress = IPAddress.Parse(serverIP);
        EndPoint endPoint = new IPEndPoint(iPAddress, serverPort);
        tcpClient.Connect(endPoint);
        Debug.Log("请求服务器连接");

        //3、接受、发送消息
        byte[] data = new byte[1024];
        int length = tcpClient.Receive(data);
        var message = Encoding.UTF8.GetString(data, 0, length);
        Debug.Log("客户端收到来自服务器发来的信息" + message);

        //发送消息
        string message2 = "Client Say To Hello";
        tcpClient.Send(Encoding.UTF8.GetBytes(message2));
        Debug.Log("客户端向服务器发送消息" + message2);
    }
}
3、开启服务器




4、开启客户端
将写好的脚本挂载在场景中


5、效果



服务端效果



客户端效果

最基本的通信已经实现,现在来开始重构一下代码。
服务器端:
将Client封装成一个类,要有初始化、开始接收、接收回调等方法
将Server封装成一个类,要有启动服务器,关闭服务器、接收信息等方法,并且还要有管理Client的方法。
客户端:
将Client封装成一个类,包括初始化、开始接收、接收回调等方法。
接下来实现:
服务器端:Server类
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Server
{
    class Server
    {
        private Socket _serverSocket;
        private int _prot;
        private int _maxClient;
        private string ipAdress;

        public List<Client> ClientList = new List<Client>();//客户端列表

        public Server(string ipAdress, int prot, int maxClient)
        {
            this.ipAdress = ipAdress;
            this._prot = prot;
            this._maxClient = maxClient;
            Start();//启动
        }

        //启动服务器
        public void Start()
        {
            try
            {
                //1、创建一个socket对象
                _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //2、绑定一个ip和端口
                IPAddress ipaddress = IPAddress.Parse(ipAdress);//将一个字符串类型的ip地址,转换成一个IPAdress对象
                EndPoint endPoint = new IPEndPoint(ipaddress, _prot);
                _serverSocket.Bind(endPoint);//向操作系统申请一个可用的ip和端口号来通讯
                _serverSocket.Listen(_maxClient);//设置最大连接数
                Console.WriteLine(string.Format("开启服务端{0}...", _serverSocket.LocalEndPoint));
                StartAccept();
            }
            catch (Exception)//出现异常则关闭服务器
            {
                //Close();
                throw;
            }
        }
        //开始接收信息
        private void StartAccept()
        {
            OnAccept();
        }

        //接收信息中
        private void OnAccept()
        {
            while (true)
            {
                Socket clientSocket = _serverSocket.Accept();
                Console.WriteLine(string.Format("客户端 {0} 连接", clientSocket.RemoteEndPoint));
                Client client = new Client(clientSocket);
                client.OnSend(Encoding.Default.GetBytes("登录成功"));
            }
        }

        //关闭所有服务器
        public void Close()
        {
            foreach (var client in ClientList)
            {
                client.Close();
            }
            _serverSocket.Close();
        }
    }
}
Client类:
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Server
{
    class Client
    {
        private Thread _recvThread;
        public Socket ClientSocket;

        public Client(Socket socket)
        {
            ClientSocket = socket;
            StartRecv();
        }

        //开始接收
        private void StartRecv()
        {
            _recvThread = new Thread(OnRecv);
            _recvThread.IsBackground = true;
            _recvThread.Start();
        }

        //接收中
        private void OnRecv()
        {
            try
            {
                while (true)
                {
                    byte[] buff = new byte[1024];
                    ClientSocket.Receive(buff);
                    Console.WriteLine(Encoding.Default.GetString(buff));
                }
            }
            catch (Exception)
            {
                Close();
                throw;
            }
        }

        //发送信息
        public void OnSend(byte[] data)
        {
            ClientSocket.Send(data);
        }
        //关闭
        public void Close()
        {
            _recvThread.Abort();
            ClientSocket.Shutdown(SocketShutdown.Both);
            ClientSocket.Close();
        }
    }
}
Program:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Server
{
    class Program
    {
        private static Server server;
        private static void Start()
        {
            server = new Server("127.0.0.1", 5000, 20);
        }

        static void Main(string[] args)
        {
            Start();//开启服务器
        }
    }
}
客户端 SocketClient类:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

public class ClientSocket
{
    private Socket _clientSocket;
    private Thread _recvThread;
    private string _rIP;  //ip地址
    private int _rProt;  //端口

    public ClientSocket(string _rIP, int _rProt)
    {
        this._rIP = _rIP;
        this._rProt = _rProt;
        Connect();
    }

    //连接
    private void Connect()
    {
        try
        {
            _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _clientSocket.Connect(new IPEndPoint(IPAddress.Parse(_rIP), _rProt));
            OnSend(Encoding.Default.GetBytes("hello,server"));
            StartRecv();
        }
        catch
        {
            throw;
        }
    }
    //开始接收消息
    private void StartRecv()
    {
        _recvThread = new Thread(OnRecv);
        _recvThread.IsBackground = true;
        Debug.Log("开始接收消息");
        _recvThread.Start();
    }

    //接收中
    private void OnRecv()
    {
        while (true)
        {
            byte[] buff = new byte[256];
            _clientSocket.Receive(buff);
            Debug.Log(Encoding.Default.GetString(buff));
        }
    }

    //发送信息
    public void OnSend(byte[] data)
    {
        try
        {
            _clientSocket.Send(data);
        }
        catch (Exception)
        {
            Close();
            throw;
        }
    }

    //关闭
    public void Close()
    {
        _recvThread.Abort();
        _clientSocket.Shutdown(SocketShutdown.Both);
        _clientSocket.Close();
    }
}

调用:
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class Client : MonoBehaviour
{
    private Socket tcpClient;
    private string serverIP = "127.0.0.1";//服务器ip地址
    private int serverPort = 5000;//端口号
    private ClientSocket c1;
    void Start()
    {
        c1 = new ClientSocket(serverIP, serverPort);
    }
}
效果:



开启服务端



开启客户端



服务端响应

注:目前客户端若是强制退出,则会导致服务端异常。
看到这里已经能实现基本的通信了,不过存在一个问题,服务器端和客户端是通过二进制数据进行通信的,而不能够传递具体的方法,要想实现这个问题,就要使用到通信协议,也就是两边设置一样的字段,接收到的二进制信息转换成字段,就调用相应字段的方法,这种方法我会在下篇文章给出。

源码链接:https://pan.baidu.com/s/1lU3f_5aYHnt1h5AjuBitlQ
提取码:e1cm

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-7-10 12:56 | 显示全部楼层
大佬,爱了爱了[赞同]
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-15 16:28 , Processed in 0.093439 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表