美文网首页Unity技术分享
Unity如何连接服务器: 一个简单的例子

Unity如何连接服务器: 一个简单的例子

作者: 陈码工 | 来源:发表于2018-05-20 10:14 被阅读75次

Unity3D本身是用来做客户端的通用游戏引擎, 要建立网络连接的话, 其实需要使用的是C#本身的网络和线程模块, 即System.Net.Sockets & System.Threading. 本文中我做了一个简单的例子, 适合那些需要做Unity客户端连接服务器功能的人入门.

整体项目

客户端项目地址: https://share.weiyun.com/5M9jp6c
服务器项目下载: https://share.weiyun.com/5TMCQYP

客户端: 我做的项目主要是一个简单的Demo, 画面上只有三个按钮和两个输入框, 通过点击按钮可以实现相应的操作.

服务端: 服务端是一个Python写的服务器. 这个部分不是我本文的重点, 大家可以参考别的网上文章, 了解如何写一个C++, Python或者Java服务器, 无论什么语言写的服务器都是可以与Unity进行交互的.

Unity Network Demo login点击后, console上显示了发出的消息
server显示成功登陆

下载项目后, 使用Unity导入, 可以看到Scripts文件夹中有六个脚本, 其中NetworkCore和UIManager是主要的脚本, Json开头的脚本不是重点, 他们只是Json编码解码相关的一个库(我是直接使用的https://github.com/gering/Tiny-JSON这个老外写的纯C#版本Json Parser), Json的编码和解析也不是本文重点, 只要找到一个库能用即可.

脚本一览

学习步骤

下载客户端和服务端, 运行起来. 之后主要学习NetworkCore.cs和UIManager.cs这两个脚本的内容(两个脚本并不复杂), 最关键的部分是如何建立连接, 建立后台线程, 发送和接收数据, 以及Json相关的字典操作.

脚本1: NetworkCore.cs

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using Tiny;

public class NetworkCore : MonoBehaviour {
    public string serverAddress = "127.0.0.1";
    public int serverPort = 5000;
    public string username = "chen";
    public string password = "123";

    private TcpClient _client;
    private NetworkStream _stream;  // C#中采用NetworkStream的方式, 可以类比于python网络编程中的socket
    private Thread _thread;
    private byte[] _buffer = new byte[1024];  // 接收消息的buffer
    private string receiveMsg = "";
    private bool isConnected = false;


    void Start() {
    }

    public void OnApplicationQuit() {
        Dictionary<string, string> dict = new Dictionary<string, string>()
        {
            {"code", "exit"}
        };
        SendData(Encode(dict));  // 退出的时候先发一个退出的信号给服务器, 使得连接被正确关闭
        Debug.Log("exit sent!");
        CloseConnection ();
    }

    // --------------------public--------------------
    public void Login() {
        SetupConnection();
        Dictionary<string, string> dict = new Dictionary<string, string>()
        {
            {"code", "login"},
            {"username", username},
            {"password", password}
        };
        SendData(Encode(dict));
        Debug.Log("start!");
    }

    public void SendGameData(int score, int health) {
        Dictionary<string, string> dict = new Dictionary<string, string>()
        {
            {"code", "gds"},
            {"score", score.ToString()},
            {"health", health.ToString()}
        };

        SendData(Encode(dict));
    }

    // -----------------------private---------------------
    private void SetupConnection() {
        try {
            _thread = new Thread(ReceiveData);  // 传入函数ReceiveData作为thread的任务
            _thread.IsBackground = true;
            _client = new TcpClient(serverAddress, serverPort);
            _stream = _client.GetStream();
            _thread.Start();  // background thread starts working while loop
            isConnected = true;

        } catch (Exception e) {
            Debug.Log (e.ToString());
            CloseConnection ();
        }
    }

    private void ReceiveData() {  // 这个函数被后台线程执行, 不断地在while循环中跑着
        Debug.Log ("Entered ReceiveData function...");
        if (!isConnected)  // stop the thread
            return;
        int numberOfBytesRead = 0;
        while (isConnected && _stream.CanRead) {
            try {
                numberOfBytesRead = _stream.Read(_buffer, 0, _buffer.Length);
                receiveMsg = Encoding.ASCII.GetString(_buffer, 0, numberOfBytesRead);
                _stream.Flush();
                Debug.Log(receiveMsg);
                receiveMsg = "";
            } catch (Exception e) {
                Debug.Log (e.ToString ());
                CloseConnection ();
            }
        }
    }

    private void SendData(String msgToSend)
    {
        byte[] bytesToSend = Encoding.ASCII.GetBytes(msgToSend);
        if (_stream.CanWrite)
        {
            _stream.Write(bytesToSend, 0, bytesToSend.Length);
        }
    }

    private void CloseConnection() {
        if (isConnected) {
            _thread.Interrupt ();  // 这个其实是多余的, 因为isConnected = false后, 线程while条件为假自动停止
            _stream.Close ();
            _client.Close ();
            isConnected = false;
            receiveMsg = "";
        }
    }

    // ---------------------util----------------------
    // encode dict to to json and wrap it with \r\n as delimiter
    string Encode(Dictionary<string, string> dict)
    {
        string json = Json.Encode(dict);
        string header = "\r\n" + json.Length.ToString() + "\r\n";
        string result = header + json;
        Debug.Log("encode result:" + result);
        return result;

    }
    
    // decode data, 注意要解决粘包的问题, 这个程序写法同GameLobby中的相应模块一模一样
    // 参考 https://github.com/imcheney/GameLobby/blob/master/server/util.py
    Dictionary<string, string> Decode(string raw)
    {
        string payload_str = "";
        string raw_leftover = raw;
        if (raw.Substring(0, 2).Equals("\r\n"))
        {
            int index = raw.IndexOf("\r\n", 2);
            int payload_length = int.Parse(raw.Substring(2, index - 2 + 1));  // 注意, C#'s substring takes start and length as args
            if (raw.Length >= index + 2 + payload_length)
            {
                payload_str = raw.Substring(index + 2, payload_length);
                raw_leftover = raw.Substring(index + 2 + payload_length);
            }
        }
        return Json.Decode<Dictionary<string, string>>(payload_str);
    }

}

脚本2: UIManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;  //using 关键字用于在程序中包含命名空间。一个程序可以包含多个 using 语句。

public class UIManager : MonoBehaviour {
    public InputField scoreInputField;
    public InputField healthInputField;

    NetworkCore networkCore;
    // Use this for initialization
    void Start () {
        networkCore = GetComponent<NetworkCore>();
    }
    
    // Update is called once per frame
    void Update () {
        
    }

    public void OnLoginButton() {
        networkCore.Login();
    }

    public void OnSendButton() {
        int score = int.Parse(scoreInputField.text);
        int health = int.Parse(healthInputField.text);
        networkCore.SendGameData(score, health);
    }

    public void OnQuitButton()
    {
        int score = int.Parse(scoreInputField.text);
        int health = int.Parse(healthInputField.text);
        networkCore.SendGameData(score, health);
        Application.Quit();
    }
}

相关文章

网友评论

    本文标题:Unity如何连接服务器: 一个简单的例子

    本文链接:https://www.haomeiwen.com/subject/atwwdftx.html