SocketHelper 类
主要的通信类,socket 的管理放在这里
下面说一下一些主要的方法
1.连接服务器,这个都写了比较详细的注释,一看就会明白
///
/// 连接服务器
///
///
The connect.
///
Server ip.
///
Server port.
///
Connect callback.
///
Connect failed callback.
public
Connect(string
serverIp,int
void
serverPort,ConnectCallback
connectCallback,ConnectCallback connectFailedCallback){
connectDelegate = connectCallback;
connectFailedDelegate = connectFailedCallback;
//采用 TCP 方式连接
socket
new
=
ProtocolType.Tcp);
Socket(AddressFamily.InterNetwork,
SocketType.Stream,
//服务器 IP 地址
IPAddress address = IPAddress.Parse(serverIp);
//服务器端口
IPEndPoint endpoint = new IPEndPoint(address,serverPort);
//异步连接,连接成功调用 connectCallback 方法
IAsyncResult
result
=
socket.BeginConnect(endpoint,
new
AsyncCallback(ConnectedCallback), socket);
//这里做一个超时的监测,当连接超过 5 秒还没成功表示超时
bool success = result.AsyncWaitHandle.WaitOne(5000, true);
if (!success)
{
//超时
Closed();
if(connectFailedDelegate != null){
connectFailedDelegate();
}
}
else
{
}
}
//与 socket 建立连接成功,开启线程接受服务端数据。
isStopReceive = false;
Thread thread = new Thread(new ThreadStart(ReceiveSorket));
thread.IsBackground = true;
thread.Start();
2.发送数据
首先从发送队列中取出要发送的数据,然后对数据进行序列化,转为 2 进制数据,然后在数
据最前端加上数据的长度,以便于服务器进行粘包的处理。
private void Send(){
if(socket == null){
return;
}
if (!socket.Connected)
{
Closed();
return;
}
try
{
Request req = sendDataQueue.Dequeue();
DataStream bufferWriter = new DataStream(true);
req.Serialize(bufferWriter);
byte[] msg = bufferWriter.ToByteArray();
byte[] buffer = new byte[msg.Length + 4];
DataStream writer = new DataStream(buffer, true);
writer.WriteInt32((uint)msg.Length);//增加数据长度
writer.WriteRaw(msg);
byte[] data = writer.ToByteArray();
IAsyncResult
asyncSend
=
socket.BeginSend(data,
0,
data.Length,
SocketFlags.None, new AsyncCallback(SendCallback), socket);
bool success = asyncSend.AsyncWaitHandle.WaitOne(5000, true);
if (!success)
{
}
Closed();
}
catch (Exception e)
{
Debug.Log("send error : " + e.ToString());
}
}
3.接收数据
接收数据是一个独立的线程,连接服务器成功后开启的。如果接收到数据,会把数据放进
DataHolder 进行处理,然后根据 DataHolder 判断是否接收到完整数据,以解决粘包等问题。
这里由于接收到数据时并没有在主线程中,所以我们先把数据放入接收队列,在主线程的
update 方法中,对数据进行解析以及分发工作。
private void ReceiveSorket(){
mDataHolder.Reset();
while(!isStopReceive){
if (!socket.Connected)
{
//与服务器断开连接跳出循环
Debug.Log("Failed to clientSocket server.");
socket.Close();
break;
}
try
{
//接受数据保存至 bytes 当中
byte[] bytes = new byte[4096];
//Receive 方法中会一直等待服务端回发消息
//如果没有回发会一直在这里等着。
int i = socket.Receive(bytes);
if (i <= 0)
{
socket.Close();
break;
}
mDataHolder.PushData(bytes, i);
while(mDataHolder.IsFinished()){
dataQueue.Enqueue(mDataHolder.mRecvData);
mDataHolder.RemoveFromHead();
}
}
catch (Exception e)
{
Debug.Log("Failed to clientSocket error." + e);
socket.Close();
break;
}
}
}
这里处理接收和发送队列的数据
//接收到数据放入数据队列,按顺序取出
void Update(){
if(dataQueue.Count > 0){
Resp resp = ProtoManager.Instance.TryDeserialize(dataQueue.Dequeue());
}
if(sendDataQueue.Count > 0){
Send();
}
}
DataHolder
此类用来管理接收到的数据,并处理粘包和丢包的情况,主要就是根据数据长度对数据进行
裁切。
通过此方法保存收到的服务器数据,放入数据缓存中
public void PushData(byte[] data, int length)
{
if (mRecvDataCache == null)
mRecvDataCache = new byte[length];
if (this.Count + length > this.Capacity)//current capacity is not enough, enlarge the
cache
{
}
byte[] newArr = new byte[this.Count + length];
mRecvDataCache.CopyTo(newArr, 0);
mRecvDataCache = newArr;
Array.Copy(data, 0, mRecvDataCache, mTail + 1, length);
mTail += length;
}
此方法判断接收到的数据是否完整,这里需要服务器在数据头 4 位装入数据长度,如果接收
到完整数据,则将数据复制出一份供外部获取。
public bool IsFinished()
{
}
if (this.Count == 0)
{
//skip if no data is currently in the cache
return false;
}
if (this.Count >= 4)
{
DataStream reader = new DataStream(mRecvDataCache, true);
packLen = (int)reader.ReadInt32();
if (packLen > 0)
{
if (this.Count - 4 >= packLen)
{
mRecvData = new byte[packLen];
Array.Copy(mRecvDataCache, 4, mRecvData, 0, packLen);
return true;
}
return false;
}
return false;
}
return false;
如果接收到了完整的数据,则调用此方法将完整数据从数据缓存移除
public void RemoveFromHead()
{
int countToRemove = packLen + 4;
if (countToRemove > 0 && this.Count - countToRemove > 0)
{
Array.Copy(mRecvDataCache, countToRemove, mRecvDataCache, 0, this.Count -
countToRemove);
}
mTail -= countToRemove;
}
ProtoManager
用来管理所有协议,进行消息的解析以及分发。
所有 Resp 需要在这里注册,以便接收到数据时进行解析
public void AddProtocol
(int protocol) where T: Resp, new()
{
if (mProtocolMapping.ContainsKey(protocol))
{
mProtocolMapping.Remove(protocol);
}
mProtocolMapping.Add(protocol,
(stream) => {
T data = new T();
data.Deserialize(stream);
return data;
});
}
这里注册相应协议的代理,在接收到服务器数据后,会调用这里注册过的相应代理
///
/// 添加代理,在接受到服务器数据时会下发数据
///
/// Protocol.
/// D.
public void AddRespDelegate(int protocol,responseDelegate d){
List dels ;
if (mDelegateMapping.ContainsKey(protocol))
{
dels = mDelegateMapping[protocol];
for(int i = 0 ; i < dels.Count ; i ++){
if(dels[i] == d){
return;
}
}
}else{
dels = new List();
mDelegateMapping.Add(protocol,dels);
}
dels.Add(d);
}
接收到服务器数据后,服务器会调用此方法对数据进行解析并调用相应的代理方法
[csharp] view plain copy
public Resp TryDeserialize(byte[] buffer)
{
DataStream stream = new DataStream(buffer, true);
int protocol = stream.ReadSInt32();
Resp ret = null;
if (mProtocolMapping.ContainsKey(protocol))
{
ret = mProtocolMapping[protocol](stream);
if(ret != null){
if(mDelegateMapping.ContainsKey(protocol)){
List dels = mDelegateMapping[protocol];
for(int i = 0 ; i < dels.Count ; i ++){
dels[i](ret);
}
}
}
}else{
Debug.Log("no register protocol : " + protocol +"!please reg to RegisterR
esp.");
}
return ret;
}
DataStream
此类进行发送以及接收的数据流的处理,包含了数据大小端的设置以及数据流的读取和写入
方法。
Request
基础的请求消息,所有要发送的请求消息继承此类
子类必须实现协议获取的方法,返回此请求所对应的协议
public virtual int GetProtocol(){
Debug.LogError("can't get Protocol");
return 0;
}
序列化方法
public virtual void Serialize(DataStream writer)
{
writer.WriteSInt32(GetProtocol());
writer.WriteByte(0);
}
Resp
基础的返回消息,所有服务器返回的消息继承此类
子类必须实现协议获取的方法,返回此请求所对应的协议
public virtual int GetProtocol(){
Debug.LogError("can't get Protocol");
return 0;
}
反序列化方法
public virtual void Deserialize(DataStream reader)
{
}
主要内容就这些,有哪些不明白的地方可以留言,有哪些不足或者可以改进的地方还请多多
指教
之后我会更新一片实际应用的文章,以及服务器端的相关内容。
---------------------
作者:刘峰 1011
来源:CSDN
原文:https://blog.csdn.net/zgjllf1011/article/details/79213861?utm_source=copy
版权声明:本文为博主原创文章,转载请附上博文链接!