KCP牺牲流量,换取“低延时” 来解决TCP“相对”高延时弊端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
//等更新Unity2008就可以使用 thread save、least gc的C#版笨KCP https://github.com/KumoKyaku/KCP
namespace KcpProject.v1
{
// 模拟TCP客户端主动发送握手数据
// 服务器下发conv
public class UdpSocket
{
private static readonly DateTime utc_time = new DateTime(1970, 1, 1);
public static UInt32 iclock()
{
return (UInt32)(Convert.ToInt64(DateTime.UtcNow.Subtract(utc_time).TotalMilliseconds) & 0xffffffff);
}
public enum cliEvent
{
Connected = 0,
ConnectFailed = 1,
Disconnect = 2,
RcvMsg = 3,
}
private const UInt32 CONNECT_TIMEOUT = 5000;
private const UInt32 RESEND_CONNECT = 500;
private UdpClient mUdpClient;
private IPEndPoint mIPEndPoint;
private IPEndPoint mSvrEndPoint;
private Action<cliEvent, byte[], string> evHandler;
private KCP mKcp;
private bool mNeedUpdateFlag;
private UInt32 mNextUpdateTime;
/// <summary> true:正在Socket连接过程, false:Socket连接完成、或者没开始 </summary>
private bool mInConnectStage;
private bool mConnectSucceed;
/// <summary>Socket开始连接的时间点:用于判断Socket连接是否超时</summary>
private UInt32 mConnectStartTime;
private UInt32 mLastSendConnectTime;
private SwitchQueue<byte[]> mRecvQueue = new SwitchQueue<byte[]>(128);
public UdpSocket(Action<cliEvent, byte[], string> handler)
{
evHandler = handler;
}
public void Connect(string host, UInt16 port)
{
mSvrEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
mUdpClient = new UdpClient(host, port);
//连接成功是没有回调的,靠ReceiveCallback回调收到第一条消息,来作为连接成功回调
mUdpClient.Connect(mSvrEndPoint);
reset_state();
mInConnectStage = true;
mConnectStartTime = iclock();
mUdpClient.BeginReceive(ReceiveCallback, this);
}
void ReceiveCallback(IAsyncResult ar)
{
Byte[] data = (mIPEndPoint == null) ?
mUdpClient.Receive(ref mIPEndPoint) : //第一次收到消息调用这里
mUdpClient.EndReceive(ar, ref mIPEndPoint);//第二次开始收到消息调用这里:由mUdpClient.BeginReceive(ReceiveCallback, this);触发
if (null != data)
OnData(data);
if (mUdpClient != null)
{
// try to receive again.
mUdpClient.BeginReceive(ReceiveCallback, this);
}
}
/// <summary>
/// 网络层收到消息时,调用它
/// </summary>
void OnData(byte[] buf)
{
mRecvQueue.Push(buf);
}
void reset_state()
{
mNeedUpdateFlag = false;
mNextUpdateTime = 0;
mInConnectStage = false;
mConnectSucceed = false;
mConnectStartTime = 0;
mLastSendConnectTime = 0;
mRecvQueue.Clear();
mKcp = null;
}
string dump_bytes(byte[] buf, int size)
{
var sb = new StringBuilder(size * 2);
for (var i = 0; i < size; i++)
{
sb.Append(buf[i]);
sb.Append(" ");
}
return sb.ToString();
}
void init_kcp(UInt32 conv)
{
mKcp = new KCP(conv, (byte[] buf, int size) =>
{
//mKcp.Send(buf)时,不马上Send,时机到时,kcp才Send。kcp Send时会回调这个函数
mUdpClient.Send(buf, size);
});
mKcp.NoDelay(1, 10, 2, 1);
}
public void Send(byte[] buf)
{
mKcp.Send(buf);//不马上Send,时机到时,kcp才Send
mNeedUpdateFlag = true;//强制kcp提前update
}
public void Send(string str)
{
Send(System.Text.ASCIIEncoding.ASCII.GetBytes(str));
}
public void Update()
{
update(iclock());
}
public void Close()
{
mUdpClient.Close();
evHandler(cliEvent.Disconnect, null, "Closed");
}
void process_connect_packet()
{
mRecvQueue.Switch();
//socket连接成功(握手成功)
if (!mRecvQueue.Empty())
{
var buf = mRecvQueue.Pop();
//前端后端必须一致的udp标志
UInt32 conv = 0;
KCP.ikcp_decode32u(buf, 0, ref conv);//decode 32 bits unsigned int (lsb) -> (byte[] p, int offset, ref UInt32 c)
if (conv <= 0)
throw new Exception("inlvaid connect back packet");
init_kcp(conv);
mInConnectStage = false;
mConnectSucceed = true;
evHandler(cliEvent.Connected, null, null);
}
}
void process_recv_queue()
{
mRecvQueue.Switch();
//收到一个正常的数据包
while (!mRecvQueue.Empty())
{
var buf = mRecvQueue.Pop();
mKcp.Input(buf);
mNeedUpdateFlag = true;//强制kcp提前update
for (var size = mKcp.PeekSize(); size > 0; size = mKcp.PeekSize())
{
var buffer = new byte[size];
if (mKcp.Recv(buffer) > 0)
{
evHandler(cliEvent.RcvMsg, buffer, null);
}
}
}
}
bool connect_timeout(UInt32 current)
{
return current - mConnectStartTime > CONNECT_TIMEOUT;
}
bool need_send_connect_packet(UInt32 current)
{
return current - mLastSendConnectTime > RESEND_CONNECT;
}
void update(UInt32 current)
{
if (mInConnectStage)
{
if (connect_timeout(current))
{
evHandler(cliEvent.ConnectFailed, null, "Timeout");
mInConnectStage = false;
return;
}
//这里要发一个4字节的包给服务器 来 握手(ReceiveCallback一段时间没有收到,会继续发)
if (need_send_connect_packet(current))
{
mLastSendConnectTime = current;
mUdpClient.Send(new byte[4] { 0, 0, 0, 0 }, 4);
}
process_connect_packet();
return;
}
if (mConnectSucceed)
{
process_recv_queue();
if (mNeedUpdateFlag || current >= mNextUpdateTime)
{
//给kcp注入生命,让它自己决定什么时候发送、重发 数据包
mKcp.Update(current);
mNextUpdateTime = mKcp.Check(current);
mNeedUpdateFlag = false;
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//你自己写一个脚本类来测试
class Program
{
static void test_v1(string host, UInt16 port)
{
var wait_response = true;
KcpProject.v1.UdpSocket client = null;
// 创建一个实例
client = new KcpProject.v1.UdpSocket((KcpProject.v1.UdpSocket.cliEvent ev, byte[] buf, string err) =>
{
wait_response = false;
//事件回调
switch (ev)
{
case KcpProject.v1.UdpSocket.cliEvent.Connected:
Console.WriteLine("connected.");
client.Send("Hello KCP.");
break;
case KcpProject.v1.UdpSocket.cliEvent.ConnectFailed:
Console.WriteLine("connect failed. {0}", err);
break;
case KcpProject.v1.UdpSocket.cliEvent.Disconnect:
Console.WriteLine("disconnect. {0}", err);
break;
case KcpProject.v1.UdpSocket.cliEvent.RcvMsg:
Console.WriteLine("recv message: {0}", System.Text.ASCIIEncoding.ASCII.GetString(buf) );
break;
}
});
client.Connect(host, port);
while (wait_response)
{
client.Update();
System.Threading.Thread.Sleep(10);
}
}
static void test_v2(string host, UInt16 port)
{
var wait_response = true;
KcpProject.v2.UdpSocket client = null;
// 创建一个实例
client = new KcpProject.v2.UdpSocket((byte[] buf) =>
{
wait_response = false;
Console.WriteLine("recv message: {0}", System.Text.ASCIIEncoding.ASCII.GetString(buf));
});
// 绑定端口
client.Connect(host, port);
// 发送消息
client.Send("Hello KCP.");
// update.
while (wait_response)
{
client.Update();//你自己写一个脚本类,放到Update函数里测试
System.Threading.Thread.Sleep(10);
}
}
static void Main(string[] args)
{
// 测试v1版本,有握手过程,服务器决定conv的分配
test_v1("192.168.1.2", 4444);
}
}
网友评论