点击下载类库
点击下载demo(服务端+u3d)
开发需要自己封装了socket模块,共享给小伙伴。
作者邮箱:zh.360@qq.com
使用方法简单:
1、新建一个空物体把NetWork挂载上,2、填上ip和prot,3、调用Connet方法;
消息类型要自定义,协议的数据包格式在注释有说。如果有需要的可以评论告诉我,我做一个完整的demo上传。
直接上核心代码
/*
网络组件,它是单例模式。
通过Network开启和关闭网络;
消息订阅器MsgEvent管理服务端发来的消息
@author jzh 2018.05.14 email:zh.360@qq.com
*/
using UnityEngine;
using Assets.Scripts.Events;
using Assets.Scripts.Util;
using Assets.Scripts.Net;
using System;
public enum NetMsgType
{
/// <summary>
/// 新消息
/// </summary>
newMsg = -4,
/// <summary>
/// 连接服务中断
/// </summary>
interrupt = -3,
/// <summary>
/// 请求错误
/// </summary>
error = -2,
/// <summary>
/// 连接成功
/// </summary>
complete = -1,
/// <summary>
/// 服务端握手响应
/// </summary>
state = 1
}
public class Network : MonoBehaviour
{
/// <summary>
/// 网络消息广播,当服务端有消息发来是通过它广播
/// </summary>
public static readonly EventDispatch<NetMsgType> MsgEvent = new EventDispatch<NetMsgType>();
public static bool IsConnet { get; private set; }
public static bool IsActive { get; private set; }
/// <summary>
/// 网络组件实例
/// </summary>
public static Network Server { get; private set; }
private SocketTcp socket;
public string ip = "";
public int prot;
void Awake()
{
Server = this;
}
void Start()
{
}
/// <summary>
/// 向服务端发送数据
/// </summary>
/// <param name="type">协议接口类型</param>
/// <param name="error">预留</param>
/// <param name="data">要发送的数据</param>
public void outCall(ushort type, ushort error, ByteArray data)
{
if (IsConnet)
{
socket.Send(type, error, data);
}
}
public void Connet()
{
if (IsActive == false)
{
IsActive = true;
socket = new SocketTcp(ip, prot);
socket.AddListener(NetEvent.CONNETED, Conneted);
socket.AddListener(NetEvent.CONNET_IOERROT, ConnetIOError);
socket.AddListener(NetEvent.CONNET_ERROT, ConnetError);
socket.AddListener(NetEvent.CONNET_MSG, MsgHandler);
socket.Start();
}
}
private void Conneted(EventData data)
{
IsConnet = true;
MsgEvent.Dispatch(NetMsgType.complete);
}
void ConnetIOError(EventData data)
{
IsConnet = false;
IsActive = false;
MsgEvent.Dispatch(NetMsgType.error);
}
void ConnetError(EventData data)
{
IsConnet = false;
IsActive = false;
MsgEvent.Dispatch(NetMsgType.interrupt);
}
void MsgHandler(EventData data)
{
BucketItem msg = (BucketItem)data.data;
msg.ByteArray.Position = 0;
if (msg.Error > 0)
{
Debug.Log("网络错误:" + msg.Error);
return;
}
if (Enum.IsDefined(typeof(NetMsgType), msg.Type))
{
MsgEvent.Dispatch((NetMsgType)msg.Type, msg);
}
else
{
Debug.Log("未定义网络消息:" + msg.Type);
}
}
public void Disconnect()
{
IsConnet = false;
IsActive = false;
socket.RemoveListener(NetEvent.CONNETED, Conneted);
socket.RemoveListener(NetEvent.CONNET_IOERROT, ConnetIOError);
socket.RemoveListener(NetEvent.CONNET_ERROT, ConnetError);
socket.RemoveListener(NetEvent.CONNET_MSG, MsgHandler);
socket.Destroy();
socket = null;
}
void FixedUpdate()
{
}
void Update()
{
if (socket != null) socket.Updata();
}
//程序退出则关闭连接
void OnApplicationQuit()
{
if (socket != null) socket.Destroy();
}
}
/*
socketTcp封装了一个轻量通信协议,以8个字节为头部(整型):无符号4字节长度(包括头部8字节)、消息类型无符号2字节、预留无符号2字节。
使用异步方法兼容hololens的uwp平台。
处理了粘包
@author jzh 2018.04.27 email:zh.360@qq.com
*/
using Assets.Scripts.Events;
using System;
using System.Collections;
using Assets.Scripts.Util;
using UnityEngine;
using System.Collections.Generic;
#if !UWP
using System.Net;
using System.Net.Sockets;
#else
using Windows.Networking.Sockets;
using Windows.Networking.Connectivity;
using Windows.Networking;
#endif
namespace Assets.Scripts.Net
{
public enum NetEvent{
/// <summary>
/// 连接建立
/// </summary>
CONNETED,
/// <summary>
/// 请求连接服务器时发生错误
/// </summary>
CONNET_IOERROT,
/// <summary>
/// 连接中断
/// </summary>
CONNET_ERROT,
/// <summary>
/// 收到消息
/// </summary>
CONNET_MSG
}
public class SocketTcp : EventDispatch<NetEvent>
{
/// <summary>
/// 头部字节
/// </summary>
public const int head_size = 8;
/// <summary>
/// 是否使用小端
/// </summary>
public const bool IsLittleEndian = true;
bool _activity = false;
/// <summary>
/// 是否已启用
/// </summary>
public bool Activity
{
get { return _activity; }
}
/// <summary>
/// 接受的消息,队列
/// </summary>
private Queue<BucketItem> MsgList;
private Queue<NetEvent> codeMsg;
public int port { get; private set; }
public string ip { get; private set; }
Socket socket;
SocketAsyncEventArgs ReceiveSaea;
SocketAsyncEventArgs sendSaea;
byte[] sendData;
/// <summary>
/// 最大允许单个数据包30720字节
///
/// </summary>
const int RECV_LEN = 30720;
byte[] recv_buf = new byte[RECV_LEN];
Bucket bucket;
bool socketState=false;
public SocketTcp(string ip, int port)
{
this.port = port;
this.ip = ip;
bucket = new Bucket();
codeMsg = new Queue<NetEvent>();
MsgList = new Queue<BucketItem>();
}
private SocketAsyncEventArgs connetedSaea;
/// <summary>
/// 发起链接请求
/// 会调度CONNETED或CONNET_IOERROT事件
/// </summary>
public void Start()
{
if (socket==null)
{
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
connetedSaea = new SocketAsyncEventArgs();
connetedSaea.Completed += new EventHandler<SocketAsyncEventArgs>(connetedCall);//设置回调方法
connetedSaea.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); //设置远端连接节点,一般用于接收消息
sendSaea = new SocketAsyncEventArgs();
//连接到服务器
socket.ConnectAsync(connetedSaea);
}
catch (Exception e)
{
Debug.Log(e);
Dispatch(NetEvent.CONNET_IOERROT);
}
}
}
private void connetedCall(object sender, SocketAsyncEventArgs e)
{
_activity = true;
codeMsg.Enqueue(NetEvent.CONNETED);
ReceiveSaea = new SocketAsyncEventArgs();
ReceiveSaea.SetBuffer(recv_buf, 0, recv_buf.Length);//设置缓冲区
ReceiveSaea.Completed += new EventHandler<SocketAsyncEventArgs>(ReceiveCall);//设置回调方法
//codeMsg.Enqueue(1);
//连接后开始从服务器读取网络消息
socket.ReceiveAsync(ReceiveSaea);
}
int fragmentLen;
byte[] fragment;
/// <summary>
/// 回调读取网络数据、检查是否断线。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ReceiveCall(object sender, SocketAsyncEventArgs e)
{
fragmentLen = e.BytesTransferred;//调用这个函数来结束本次接收并返回接收到的数据长度。
if (fragmentLen > 0)
{
fragment = new byte[fragmentLen];
Array.Copy(recv_buf, 0, fragment, 0, fragmentLen);
Queue<BucketItem> arr = bucket.Infuse(fragment);
while (arr.Count > 0)
{
MsgList.Enqueue(arr.Dequeue());
}
socket.ReceiveAsync(ReceiveSaea);
}
else
{
ReceiveSaea.Dispose();
bucket.Reset();
socket.Shutdown(SocketShutdown.Receive);//这个函数用来关闭客户端连接
_activity = false;
socketState = true;
Debug.Log("中断,关闭连接");
return;
}
}
/// <summary>
/// 发送字节流
/// </summary>
/// <param name="data"></param>
public void Send(ushort type, ushort error, ByteArray data)
{
uint len = (uint)data.Length + head_size;
//清空发送缓存
sendData = new byte[len];
ByteHelp.WriteNumber(len, ref sendData, 0, IsLittleEndian);
ByteHelp.WriteNumber(type, ref sendData, 4, IsLittleEndian);
ByteHelp.WriteNumber(error, ref sendData, 6, IsLittleEndian);
if (data.Length > 0)
{
Array.Copy(data.Data, 0, sendData, head_size, data.Length);
}
//sendData.
//数据类型转换
//sendData = Encoding.ASCII.GetBytes(sendStr);
//发送
sendSaea.SetBuffer(sendData, 0, sendData.Length);
socket.SendAsync(sendSaea);
}
/// <summary>
/// 主线程每帧调用以拿取数据
/// </summary>
public void Updata()
{
while (codeMsg.Count > 0)
{
Dispatch(codeMsg.Dequeue());
}
while (MsgList.Count > 0)
{
Dispatch(NetEvent.CONNET_MSG, MsgList.Dequeue());
}
if (socketState)
{
//Debug.Log("连接丢失,是否要重连");
socketState = false;
Dispatch(NetEvent.CONNET_ERROT);
}
}
public void Destroy()
{
base.Dispose();
//最后关闭服务器
if (socket != null && socket.Connected)
{
//this.socket.Disconnect(true);
//this.socket.Shutdown(SocketShutdown.Both);
//socket.Dispose();
socket.Shutdown(SocketShutdown.Receive);
socket.Shutdown(SocketShutdown.Send);
ReceiveSaea.Dispose();
}
bucket.Reset();
MsgList.Clear();
codeMsg.Clear();
socketState = false;
if (sendSaea != null) connetedSaea = null;
if(sendSaea!=null) sendSaea.Dispose();
sendSaea = null;
if (_activity)
{
_activity = false;
}
}
}
}