前言
最近和幾個同學(xué)一起做一個手游項目,雖然是單機(jī),但是有檢測新版本的需求,因此還是要搭建一個服務(wù)器來實現(xiàn)客戶端檢測更新的功能。在朋友的推薦下,我選擇使用Netty的服務(wù)器架構(gòu),關(guān)于Netty的優(yōu)點可以百度下,這里不再累贅。從沒聽過Netty到實現(xiàn)與Unity客戶端的通信總共花了兩天,其中遇到了很多坑,我將在這系列文章中復(fù)盤這兩天的開發(fā)流程,給想要入門Unity客戶端+Netty服務(wù)端通信的同學(xué)帶下路,少走一些彎路。
一、開發(fā)環(huán)境搭建
我的開發(fā)環(huán)境
- Mac OS 10.11.6
- Unity 5.3.5 P6
- 編程語言:C#
二、Unity客戶端代碼
Unity工程

工程截圖.png
工程包含兩個C#文件
-
nettyComponent.cs
將nettyComponent.cs拖拽到Unity場景中的Gameobject即可
-
nettyClient.cs
nettyClient.cs實現(xiàn)了:
1.連接服務(wù)器
2.網(wǎng)絡(luò)檢測
3.失敗重連
nettyComponent.cs
using UnityEngine;
using System.Collections;
public class nettyComponent : MonoBehaviour {
public string IP = "127.0.0.1";
public int Port = 7397;
nettyClient client;
mySocket mysocket;
// Use this for initialization
void Start () {
//獲得nettyClient實例
client = nettyClient.GetInstance (IP,Port);
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown (KeyCode.Escape))
client.Closed ();
}
}
nettyClient.cs
using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
public class nettyClient {
public string IP = "127.0.0.1";
public int Port = 7397;
public bool isConnected;
//信息接收進(jìn)程
private Thread _ReceiveThread = null;
//網(wǎng)絡(luò)檢測進(jìn)程
private Thread _connectionDetectorThread = null;
private Socket clientSocket = null;
private static byte[] result = new byte[1024];
//單例模式
private static nettyClient instance;
public static nettyClient GetInstance()
{
if (instance == null)
{
instance = new nettyClient();
}
return instance;
}
public static nettyClient GetInstance(string ip,int port)
{
if (instance == null)
{
instance = new nettyClient(ip,port);
}
return instance;
}
//默認(rèn)服務(wù)器IP地址構(gòu)造函數(shù)
public nettyClient()
{
startConnect ();
//初始化網(wǎng)絡(luò)檢測線程
_connectionDetectorThread = new Thread (new ThreadStart (connectionDetector));
//開啟網(wǎng)絡(luò)檢測線程[用于檢測是否正在連接,否則重新連接]
_connectionDetectorThread.Start ();
}
//自定義服務(wù)器IP地址構(gòu)造函數(shù)
public nettyClient(string ip,int port)
{
IP = ip;
Port = port;
startConnect ();
//初始化網(wǎng)絡(luò)檢測線程
_connectionDetectorThread = new Thread (new ThreadStart (connectionDetector));
//開啟網(wǎng)絡(luò)檢測線程[用于檢測是否正在連接,否則重新連接]
_connectionDetectorThread.Start ();
}
private void startConnect()
{
//創(chuàng)建Socket對象, 這里我的連接類型是TCP
clientSocket = new Socket (AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//服務(wù)器IP地址
IPAddress ipAddress = IPAddress.Parse (IP);
//服務(wù)器端口
IPEndPoint ipEndpoint = new IPEndPoint (ipAddress, Port);
//這是一個異步的建立連接,當(dāng)連接建立成功時調(diào)用connectCallback方法
IAsyncResult result = clientSocket.BeginConnect (ipEndpoint,new AsyncCallback (connectCallback),clientSocket);
//這里做一個超時的監(jiān)測,當(dāng)連接超過5秒還沒成功表示超時
bool success = result.AsyncWaitHandle.WaitOne( 5000, true );
if ( !success )
{
//超時
clientSocket.Close();
Debug.Log("connect Time Out");
if (_ReceiveThread != null)
_ReceiveThread.Abort();
// Closed();
}else
{
//如果連接成功則開啟接受進(jìn)程,發(fā)送信息
if (clientSocket.Connected) {
this.isConnected = true;
//初始化線程
_ReceiveThread = new Thread (new ThreadStart (Receive));
//開啟線程[用于接收數(shù)據(jù)]
_ReceiveThread.Start ();
//發(fā)送數(shù)據(jù)
Send ();
}
}
}
/// <summary>
/// 發(fā)送數(shù)據(jù)
/// </summary>
public void Send()
{
for (int i = 0; i < 2; i++)
{
Thread.Sleep(1000);
//UTF8編碼
clientSocket.Send(System.Text.Encoding.UTF8.GetBytes((i+1)+"=> Netty服務(wù)端您好\r\n"));
}
}
//向服務(wù)端發(fā)送一條字符串
//一般不會發(fā)送字符串 應(yīng)該是發(fā)送數(shù)據(jù)包
public void SendMessage(string str)
{
byte[] msg = System.Text.Encoding.UTF8.GetBytes(str);
if(!clientSocket.Connected)
{
clientSocket.Close();
return;
}
try
{
IAsyncResult asyncSend = clientSocket.BeginSend (msg,0,msg.Length,SocketFlags.None,new AsyncCallback (sendCallback),clientSocket);
bool success = asyncSend.AsyncWaitHandle.WaitOne( 5000, true );
if ( !success )
{
clientSocket.Close();
Debug.Log("Failed to SendMessage server.");
}else
Debug.Log("Message has been sent!");
}
catch
{
Debug.Log("send message error" );
}
}
/// <summary>
/// 接收數(shù)據(jù)線程
/// </summary>
public void Receive()
{
int receiveLength = 0;
try
{
while (true)
{
if(!clientSocket.Connected)
{
//與服務(wù)器斷開連接跳出循環(huán)
Debug.Log("Failed to clientSocket server.");
clientSocket.Close();
break;
}
try
{
//Receive方法中會一直等待服務(wù)端回發(fā)消息
//如果沒有回發(fā)會一直在這里等著。
int i = clientSocket.Receive(result);
if(i <= 0)
{
clientSocket.Close();
_ReceiveThread.Abort();
Debug.Log("斷開連接");
break;
}
if((receiveLength = clientSocket.Receive(result)) > 0)
{
//UTF8解碼
Console.WriteLine("接收服務(wù)器消息:{0}", Encoding.UTF8.GetString(result, 0, receiveLength));
Debug.Log(Encoding.UTF8.GetString(result, 0, receiveLength));
}
}
catch (Exception ex)
{
Debug.Log("Failed to clientSocket error." + ex);
clientSocket.Close();
}
}
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// 重新連接線程
/// </summary>
public void connectionDetector()
{
try
{
int connectTime = 0;
while (true)
{
try
{
if(clientSocket.Connected)
{
Debug.Log("網(wǎng)絡(luò)檢測中,連接狀態(tài)為:" + clientSocket.Connected);
connectTime = 0;
}
else if(!clientSocket.Connected)
{
Debug.Log("網(wǎng)絡(luò)檢測中,連接狀態(tài)為:False");
this.isConnected = false;
//嘗試重連
Debug.Log("正在嘗試第"+ connectTime.ToString() +"次重連");
//連接
startConnect ();
//每5秒執(zhí)行一次重連
Thread.Sleep(5000);
connectTime +=1 ;
}
}
catch (Exception ex)
{
Debug.Log(ex);
}
}
}
catch (Exception)
{
throw;
}
}
static void Main(string[] args)
{
new nettyClient();
}
//發(fā)送信息-回調(diào)
private void sendCallback (IAsyncResult asyncSend)
{
Debug.Log (asyncSend.AsyncState);
}
//連接-回調(diào)
private void connectCallback(IAsyncResult asyncConnect)
{
}
//關(guān)閉Socket
public void Closed()
{
try{
if(clientSocket != null && clientSocket.Connected)
{
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
clientSocket = null;
//關(guān)閉線程
_ReceiveThread.Abort ();
_connectionDetectorThread.Abort ();
Debug.Log ("已關(guān)閉Socket");
}catch(Exception e) {
throw;
}
}
}
三、本地測試
1.連接成功,收發(fā)信息正常

Unity控制臺

IntelliJ IDEA控制臺
2.連接失敗,嘗試重新連接

失敗重連