using System;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
/// <summary>
/// Socket通信 与后台交互
/// </summary>
public class SocketCommunications : MonoBehaviour
{
private Socket clientSocket;//Socket定义
private bool timeInterval;//时间间隔
private int timeCount = 0;//时间计数
private float mSynchronous;//时间计数 2
private bool isReconnect = false;//是否是断线重连
private void Awake()
{
}
private void Start()
{
}
/// <summary>
/// 监控客户端与服务器端是否在链接中
/// </summary>
private void FixedUpdate()
{
HearBeatCount();
}
private void OnDisable()
{
DisConnet();
}
//------------外部接口------------//
#region 公共接口端
/// <summary>
/// 公共接口 Socket连接
/// </summary>
/// <param name="serverIp">Ip地址</param>
/// <param name="serverPort">端口号</param>
public void PublicSocketConnect(string serverIp, int serverPort)
{
SocketConnect(serverIp, serverPort);
}
#region 心跳 证明 客户端跟服务器是在链接状态中
#region 心跳 方法一
/// <summary>
/// 发送信号,说明自己还活着 心跳 功能
/// </summary>
private void SendHearBeat()
{
if (clientSocket.Connected)
{
Send("客户端、服务器 正在 甜蜜 牵手......");
}
}
/// <summary>
/// 心跳时间 计数
/// </summary>
private void HearBeatCount()
{
if (timeInterval)
{
timeCount += 1;
if (timeCount % 3000 == 0)
{
SendHearBeat();
timeCount = 0;
if (!clientSocket.Connected)
{
if (Application.isPlaying)
{
//当客户端与服务器失去联系时,进行提示 并 重新链接
}
}
}
}
}
#endregion
#region 方法二 心跳计数
/// <summary>
/// 在Update中每120s的时候同步一次
/// </summary>
private void HearBeatCounting()
{
if (timeInterval)
{
mSynchronous += Time.deltaTime;
//在Update中每120s的时候同步一次
if (mSynchronous > 120.0f)
{
SendHearBeat();
mSynchronous = 0;
}
}
}
/// <summary>
/// 心跳 停止计数
/// </summary>
private void HearBeatCountStopping()
{
if (timeInterval)
{
timeInterval = false;
mSynchronous = 0;
}
}
#endregion 方法二 心跳计数
/// <summary>
/// 心跳 停止计数
/// </summary>
public void HearBeatCountStop()
{
if (timeInterval)
{
timeInterval = false;
timeCount = 0;
}
}
#endregion 心跳 证明 客户端跟服务器是在链接状态中
#endregion 公共接口端
//------------Socket------------//
#region Socket 代码区域
/// <summary>
/// Socket连接
/// </summary>
/// <param name="serverIP">IP</param>
/// <param name="serverPort">Port</param>
private void SocketConnect(string serverIP, int serverPort)
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipe = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);
try
{
clientSocket.Connect(ipe);
if (clientSocket.Connected)
{
//重连成功后,取消异常显示
if (isReconnect)
{
isReconnect = false;
}
ThreadReceiveSocket();//开启线程
ThreadHearBeat();//开启证明自己还活着
}
}
catch (Exception e)
{
Debug.LogError("发送错误:" + e.Message);
}
}
private Thread thread;
/// <summary>
/// 开启接收socket线程
/// </summary>
private void ThreadReceiveSocket()
{
if (clientSocket.Connected)
{
thread = new Thread(new ThreadStart(ReceiveSocket));
thread.IsBackground = true;
thread.Start();
}
}
/// <summary>
/// 发送信号,说明自己还活着 心跳 功能
/// </summary>
private void ThreadHearBeat()
{
if (clientSocket.Connected)
{
timeInterval = true;
}
}
/// <summary>
/// 断开连接
/// </summary>
public void DisConnet()
{
HearBeatCountStop();//心跳停止 计数
if (clientSocket != null && clientSocket.Connected)
{
clientSocket.Close();
thread.Abort();
}
StopAllCoroutines();
}
#if UNITY_EDITOR
/// <summary>
/// Unity3D 在编辑状态下 要断掉 socket链接 不然Unity3D 会卡死
/// </summary>
public void ApplicationStop()
{
HearBeatCountStop();//心跳停止 计数
if (clientSocket != null && clientSocket.Connected)
{
clientSocket.Close();
}
if (thread != null)
thread.Abort();
StopAllCoroutines();
}
#endif
/// <summary>
/// 发送数据
/// </summary>
/// <param name="str">数据 字符串</param>
private void Send(string str)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
if (clientSocket == null)
return;
if (!clientSocket.Connected)
return;
if (clientSocket.Poll(0, SelectMode.SelectWrite))
{
try
{
clientSocket.Send(bytes);
}
catch (Exception e)
{
Debug.LogError("发送错误:" + e.Message);
}
}
}
/// <summary>
/// 接受服务器返回的数据
/// </summary>
private void ReceiveSocket()
{
//在这个线程中接受服务器返回的数据
while (true)
{
if (!clientSocket.Connected)
{
//与服务器断开连接跳出循环
Debug.Log("Failed to clientSocket server.");
clientSocket.Close();
break;
}
try
{
//接受数据保存至bytes当中
byte[] bytes = new byte[4096 * 10];
//Receive方法中会一直等待服务端回发消息
//如果没有回发会一直在这里等着。
int receiveLength = clientSocket.Receive(bytes, SocketFlags.None);
if (receiveLength <= 0)
{
clientSocket.Close();
break;
}
/*
* 这里条件可根据你的情况来判断。
* 因为我目前的项目先要监测包头长度,
* 我的包头长度是4,所以我这里有一个判断
*/
byte[] receiveByte = new byte[receiveLength];
Buffer.BlockCopy(bytes, 0, receiveByte, 0, receiveLength);
if (receiveByte.Length > 4)
{
OnReceive(receiveByte);
}
else
{
Debug.Log("长度不大于2");
}
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}
}
/// <summary>
/// 重新连接服务器
/// </summary>
/// <returns></returns>
IEnumerator ReConnect()
{
yield return new WaitForSeconds(30f);
if (Application.isPlaying)
{
isReconnect = true;
}
}
/// <summary>
/// 解析Socket得到的字符串
/// </summary>
/// <param name="str"></param>
void DeserializeSocketInfo(string str)
{
Loom.RunAsync(() =>
{
Loom.QueueOnMainThread(() =>
{
});
});
}
/// <summary>
/// 将byte[] 直接转换成UTF-8类型的string
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
private string GetUTF_8String(byte[] buffer)
{
string str = Encoding.GetEncoding("UTF-8").GetString(buffer);
return str;
}
#region 粘包逻辑处理
int headSize = 4;//包头长度 固定4
byte[] surplusBuffer = null;//不完整的数据包,即用户自定义缓冲区
/// <summary>
/// 接收客户端发来的数据
/// </summary>
/// <param name="connId">每个客户的会话ID</param>
/// <param name="bytes">缓冲区数据</param>
/// <returns></returns>
private void OnReceive(byte[] bytes)
{
//bytes 为系统缓冲区数据
//bytesRead为系统缓冲区长度
int bytesRead = bytes.Length;
if (bytesRead > 0)
{
if (surplusBuffer == null)//判断是不是第一次接收,为空说是第一次
surplusBuffer = bytes;//把系统缓冲区数据放在自定义缓冲区里面
else
{
byte[] tempBytes = new byte[surplusBuffer.Length + bytes.Length];
Buffer.BlockCopy(surplusBuffer, 0, tempBytes, 0, surplusBuffer.Length);
Buffer.BlockCopy(bytes, 0, tempBytes, surplusBuffer.Length, bytes.Length);
surplusBuffer = tempBytes;
}
//已经完成读取每个数据包长度
int haveRead = 0;
//这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
int totalLen = surplusBuffer.Length;
while (haveRead <= totalLen)
{
//如果在N此拆解后剩余的数据包连一个包头的长度都不够
//说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包
if (totalLen - haveRead < headSize)
{
byte[] byteSub = new byte[totalLen - haveRead];
//把剩下不够一个完整的数据包存起来
Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
surplusBuffer = byteSub;
totalLen = 0;
break;
}
//如果够了一个完整包,则读取包头的数据
byte[] headByte = new byte[headSize];
Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节
//string bodySizeStr = GetUTF_8String(headByte);
//int bodySize = int.Parse(bodySizeStr);
//Debug.Log(bodySize);
int bodySize = BitConverter.ToInt32(headByte, 0);//从包头里面分析出包体的长度
//这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N
//如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存
if (haveRead + headSize + bodySize > totalLen)
{
byte[] byteSub = new byte[totalLen - haveRead];
Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
surplusBuffer = byteSub;
break;
}
else
{
//挨个分解每个包,解析成实际文字
string strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
DeserializeSocketInfo(strc);//解析数据
//依次累加当前的数据包的长度
haveRead = haveRead + headSize + bodySize;
if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
{
surplusBuffer = null;//设置空 回到原始状态
totalLen = 0;//清0
}
}
}
}
}
#endregion
#endregion Socket 代码区域
}
//---------------Loom脚本----------------//
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
public class Loom : MonoBehaviour
{
public static int maxThreads = 8;
static int numThreads;
private static Loom _current;
private int _count;
public static Loom Current
{
get
{
Initialize();
return _current;
}
}
void Awake()
{
_current = this;
initialized = true;
}
static bool initialized;
static void Initialize()
{
if (!initialized)
{
if (!Application.isPlaying)
return;
initialized = true;
var g = new GameObject("Loom");
_current = g.AddComponent<Loom>();
}
}
private List<System.Action> _actions = new List<System.Action>();
public struct DelayedQueueItem
{
public float time;
public System.Action action;
}
private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();
public static void QueueOnMainThread(System.Action action)
{
QueueOnMainThread(action, 0f);
}
public static void QueueOnMainThread(System.Action action, float time)
{
if (time != 0)
{
lock (Current._delayed)
{
Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action });
}
}
else
{
lock (Current._actions)
{
Current._actions.Add(action);
}
}
}
public static Thread RunAsync(System.Action a)
{
Initialize();
while (numThreads >= maxThreads)
{
Thread.Sleep(1);
}
Interlocked.Increment(ref numThreads);
ThreadPool.QueueUserWorkItem(RunAction, a);
return null;
}
private static void RunAction(object action)
{
try
{
((System.Action)action)();
}
catch
{
}
finally
{
Interlocked.Decrement(ref numThreads);
}
}
void OnDisable()
{
if (_current == this)
{
_current = null;
}
}
// Use this for initialization
void Start()
{
}
List<System.Action> _currentActions = new List<System.Action>();
// Update is called once per frame
void Update()
{
lock (_actions)
{
_currentActions.Clear();
_currentActions.AddRange(_actions);
_actions.Clear();
}
foreach (var a in _currentActions)
{
a();
}
lock (_delayed)
{
_currentDelayed.Clear();
_currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
foreach (var item in _currentDelayed)
_delayed.Remove(item);
}
foreach (var delayed in _currentDelayed)
{
delayed.action();
}
}
}
/*
* 调用案例
*/
//public class LoomUse
//{
// void ScaleMesh(Mesh mesh, float scale)
// {
//
// var vertices = mesh.vertices;
// //Run the action on a new thread
// Loom.RunAsync(() =>
// {
// //Loop through the vertices
// for (var i = 0; i < vertices.Length; i++)
// {
// //Scale the vertex
// vertices[i] = vertices[i] * scale;
// }
// //Run some code on the main thread
// //to update the mesh
// Loom.QueueOnMainThread(() =>
// {
// //Set the vertices
// mesh.vertices = vertices;
// //Recalculate the bounds
// mesh.RecalculateBounds();
// });
// });
// }
//}
Unity MMO客户端服务端技术架构设计图 unity3d客户端
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
unity 客户端架构 unity 服务端框架
大概是这样,还有细节根据具体需求扩展。
unity 客户端架构 Unity Client Server 服务器 -
unity连接硬件
一、VS连接测试 1. MySql.Data插件下载 Visual Studio中下载 打开Visual Studio_项目_管理NuGet程序包
unity连接硬件 数据库 mysql unity sql