Просмотр исходного кода

KCP改进,不需要每帧update

tanghai 8 лет назад
Родитель
Сommit
b2e8934dab

+ 2 - 2
README.md

@@ -33,8 +33,8 @@ erlang语言一大优势就是位置透明的消息机制,用户完全不用
 因为ios的限制,之前unity热更新一般使用lua,导致unity3d开发人员要写两种代码,麻烦的要死。之后幸好出了ILRuntime库,利用ILRuntime库,unity3d可以利用C#语言加载热更新dll进行热更新。ILRuntime一个缺陷就是开发时候不支持VS debug,这有点不爽。ET框架使用了一个预编译指令ILRuntime,可以无缝切换。平常开发的时候不使用ILRuntime,而是使用Assembly.Load加载热更新动态库,这样可以方便用VS单步调试。在发布的时候,定义预编译指令ILRuntime就可以无缝切换成使用ILRuntime加载热更新动态库。这样开发起来及其方便,再也不用使用狗屎lua了
 ### 8.客户端服务端用同一种语言,并且共享代码  
 下载ET框架,打开服务端工程,可以看到服务端引用了客户端很多代码,通过引用客户端代码的方式实现了双端共享代码。例如客户端服务端之间的网络消息两边完全共用一个文件即可,添加一个消息只需要修改一遍。  
-### 9.UDP TCP协议无缝切换  
-ET框架不但支持TCP,而且支持可靠的UDP协议,UDP支持是封装了ENet库,ENet也是英雄联盟所使用的网络库,其特点是快速,并且网络丢包的情况下性能也非常好,这个我们做过测试TCP在丢包5%的情况下,moba游戏就卡的不行了,但是使用ENet,丢包20%仍然不会感到卡。非常强大。  
+### 9.KCP ENET TCP协议无缝切换  
+ET框架不但支持TCP,而且支持可靠的UDP协议(ENET跟KCP),ENet是英雄联盟所使用的网络库,其特点是快速,并且网络丢包的情况下性能也非常好,这个我们做过测试TCP在丢包5%的情况下,moba游戏就卡的不行了,但是使用ENet,丢包20%仍然不会感到卡。非常强大。框架还支持使用KCP协议,KCP也是可靠UDP协议,据说比ENET性能更好,使用kcp请注意,需要自己加心跳机制,否则20秒没收到包,服务端将断开连接。三种协议可以无缝切换
 ### 10.打包工具  
 ET框架带有一整套打包工具,完全傻瓜式。一键打包,自动分析共享资源。对比md5更新  
 

+ 2 - 2
Server/Hotfix/System/NetOuterComponentSystem.cs

@@ -25,14 +25,14 @@ namespace Hotfix
 	{
 		public static void Awake(this NetOuterComponent self)
 		{
-			self.Awake(NetworkProtocol.TCP);
+			self.Awake(NetworkProtocol.KCP);
 			self.MessagePacker = new MongoPacker();
 			self.MessageDispatcher = new OuterMessageDispatcher();
 		}
 
 		public static void Awake(this NetOuterComponent self, string host, int port)
 		{
-			self.Awake(NetworkProtocol.TCP, host, port);
+			self.Awake(NetworkProtocol.KCP, host, port);
 			self.MessagePacker = new MongoPacker();
 			self.MessageDispatcher = new OuterMessageDispatcher();
 		}

+ 3 - 2
Unity/Assets/Scripts/Base/Network/AChannel.cs

@@ -76,9 +76,10 @@ namespace Model
 				return;
 			}
 
-			this.service.Remove(this.Id);
-
+			long id = this.Id;
 			this.Id = 0;
+
+			this.service.Remove(id);
 		}
 	}
 }

+ 59 - 18
Unity/Assets/Scripts/Base/Network/KNet/KChannel.cs

@@ -26,14 +26,10 @@ namespace Model
 
 		private readonly byte[] cacheBytes = new byte[1400];
 
-		private bool isNeedUpdate;
-
 		public uint Conn;
 
 		public uint RemoteConn;
 
-		public uint lastUpdateTime;
-
 		// accept
 		public KChannel(uint conn, uint remoteConn, UdpClient socket, IPEndPoint remoteEndPoint, KService kService): base(kService, ChannelType.Accept)
 		{
@@ -48,7 +44,6 @@ namespace Model
 			kcp.NoDelay(1, 10, 2, 1);  //fast
 			this.isConnected = true;
 			this.lastRecvTime = kService.TimeNow;
-			this.lastUpdateTime = kService.TimeNow;
 		}
 
 		// connect
@@ -60,19 +55,26 @@ namespace Model
 			this.parser = new PacketParser(this.recvBuffer);
 			this.remoteEndPoint = remoteEndPoint;
 			this.lastRecvTime = kService.TimeNow;
-			this.lastUpdateTime = kService.TimeNow;
+			this.Connect(kService.TimeNow);
 		}
 
 		public override void Dispose()
 		{
-			if (this.socket == null)
+			if (this.Id == 0)
 			{
 				return;
 			}
+
 			base.Dispose();
+
 			this.socket = null;
 		}
 
+		private KService GetService()
+		{
+			return (KService)this.service;
+		}
+
 		public void HandleConnnect(uint responseConn)
 		{
 			if (this.isConnected)
@@ -84,37 +86,51 @@ namespace Model
 			this.kcp = new Kcp(responseConn, this.Output);
 			kcp.SetMtu(512);
 			kcp.NoDelay(1, 10, 2, 1);  //fast
+
+			HandleSend();
 		}
 
 		public void HandleAccept(uint requestConn)
 		{
-			cacheBytes.WriteTo(0, 2);
+			cacheBytes.WriteTo(0, KcpProtocalType.ACK);
 			cacheBytes.WriteTo(4, requestConn);
 			cacheBytes.WriteTo(8, this.Conn);
 			this.socket.Send(cacheBytes, 12, remoteEndPoint);
 		}
 
+		/// <summary>
+		/// 发送请求连接消息
+		/// </summary>
+		private void Connect(uint timeNow)
+		{
+			cacheBytes.WriteTo(0, KcpProtocalType.SYN);
+			cacheBytes.WriteTo(4, this.Conn);
+			this.socket.Send(cacheBytes, 8, remoteEndPoint);
+
+			// 200毫秒后再次update发送connect请求
+			this.GetService().AddToNextTimeUpdate(timeNow + 200, this.Id);
+		}
+
 		public void Update(uint timeNow)
 		{
 			// 如果还没连接上,发送连接请求
 			if (!this.isConnected)
 			{
-				cacheBytes[0] = 1;
-				cacheBytes.WriteTo(4, this.Conn);
-				this.socket.Send(cacheBytes, 8, remoteEndPoint);
+				Connect(timeNow);
 				return;
 			}
 
 			// 超时断开连接
-			if (timeNow - this.lastRecvTime > 10 * 1000)
+			if (timeNow - this.lastRecvTime > 20 * 1000)
 			{
 				this.OnError(this, SocketError.Disconnecting);
 				return;
 			}
 
-			HandleSend();
-
 			this.kcp.Update(timeNow);
+
+			uint nextUpdateTime = this.kcp.Check(timeNow);
+			this.GetService().AddToNextTimeUpdate(nextUpdateTime, this.Id);
 		}
 
 		private void HandleSend()
@@ -126,13 +142,15 @@ namespace Model
 					break;
 				}
 				byte[] buffer = this.sendBuffer.Dequeue();
-				this.kcp.Send(buffer);
+				this.KcpSend(buffer);
 			}
 		}
 
 		public void HandleRecv(byte[] date, uint timeNow)
 		{
 			this.kcp.Input(date);
+			// 加入update队列
+			this.GetService().AddToUpdate(this.Id);
 
 			lastRecvTime = timeNow;
 
@@ -149,6 +167,7 @@ namespace Model
 				{
 					return;
 				}
+				
 				// 收到的数据放入缓冲区
 				this.recvBuffer.SendTo(this.cacheBytes, 0, count);
 
@@ -169,23 +188,45 @@ namespace Model
 		{
 			this.socket.Send(bytes, count, this.remoteEndPoint);
 		}
+
+		private void KcpSend(byte[] buffers)
+		{
+			this.kcp.Send(buffers);
+			this.GetService().AddToUpdate(this.Id);
+		}
 		
 		public override void Send(byte[] buffer)
 		{
+			if (isConnected)
+			{
+				this.KcpSend(buffer);
+				return;
+			}
 			this.sendBuffer.Enqueue(buffer);
-			this.isNeedUpdate = true;
 		}
 
 		public override void Send(List<byte[]> buffers)
 		{
 			ushort size = (ushort)buffers.Select(b => b.Length).Sum();
 			byte[] sizeBuffer = BitConverter.GetBytes(size);
-			this.sendBuffer.Enqueue(sizeBuffer);
+			if (isConnected)
+			{
+				this.KcpSend(sizeBuffer);
+			}
+			else
+			{
+				this.sendBuffer.Enqueue(sizeBuffer);
+			}
+
 			foreach (byte[] buffer in buffers)
 			{
+				if (isConnected)
+				{
+					this.KcpSend(buffer);
+					continue;
+				}
 				this.sendBuffer.Enqueue(buffer);
 			}
-			this.isNeedUpdate = true;
 		}
 
 		public override Task<byte[]> Recv()

+ 66 - 22
Unity/Assets/Scripts/Base/Network/KNet/KService.cs

@@ -6,8 +6,18 @@ using System.Threading.Tasks;
 
 namespace Model
 {
+	public static class KcpProtocalType
+	{
+		public const uint SYN = 1;
+		public const uint ACK = 2;
+	}
+
 	public sealed class KService: AService
 	{
+		private uint IdGenerater = 1000;
+
+		public uint TimeNow { get; set; }
+
 		private UdpClient socket;
 		
 		private readonly Dictionary<long, KChannel> idChannels = new Dictionary<long, KChannel>();
@@ -16,27 +26,25 @@ namespace Model
 
 		private TaskCompletionSource<AChannel> acceptTcs;
 
-		private uint IdGenerater = 1000;
-
-		public uint TimeNow;
+		private readonly Queue<long> removedChannels = new Queue<long>();
 
-		private uint lastUpdateTime;
+		// 下帧要更新的channel
+		private readonly HashSet<long> updateChannels = new HashSet<long>();
 
-		private readonly Queue<long> removedChannels = new Queue<long>();
+		// 下次时间更新的channel
+		private readonly MultiMap<long, long> timerMap = new MultiMap<long, long>();
+		private readonly Queue<long> timeoutTimer = new Queue<long>();
 
-		/// <summary>
-		/// 即可做client也可做server
-		/// </summary>
 		public KService(string host, int port)
 		{
-			this.TimeNow = (uint)(TimeHelper.Now() & 0x00000000ffffffff);
+			this.TimeNow = (uint)TimeHelper.Now();
 			this.socket = new UdpClient(new IPEndPoint(IPAddress.Parse(host), port));
 			this.StartRecv();
 		}
 
 		public KService()
 		{
-			this.TimeNow = (uint)(TimeHelper.Now() & 0x00000000ffffffff);
+			this.TimeNow = (uint)TimeHelper.Now();
 			this.socket = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
 			this.StartRecv();
 		}
@@ -64,20 +72,27 @@ namespace Model
 
 				// accept
 				uint conn = BitConverter.ToUInt32(udpReceiveResult.Buffer, 0);
+				
 				// conn从1000开始,如果为1,2则是特殊包
-				if (conn == 1)
+				if (conn == KcpProtocalType.SYN)
 				{
 					this.HandleAccept(udpReceiveResult);
 					continue;
 				}
 				
 				// connect response
-				if (conn == 2)
+				if (conn == KcpProtocalType.ACK)
 				{
 					this.HandleConnect(udpReceiveResult);
 					continue;
 				}
 
+				if (conn < 1000)
+				{
+					Log.Error($"error conn: {conn} {udpReceiveResult.RemoteEndPoint}");
+					continue;
+				}
+
 				this.HandleRecv(udpReceiveResult);
 			}
 		}
@@ -118,6 +133,7 @@ namespace Model
 			}
 
 			uint requestConn = BitConverter.ToUInt32(udpReceiveResult.Buffer, 4);
+			
 			// 如果已经连接上,则重新响应请求
 			KChannel kChannel;
 			if (this.idChannels.TryGetValue(requestConn, out kChannel))
@@ -125,7 +141,7 @@ namespace Model
 				kChannel.HandleAccept(requestConn);
 				return;
 			}
-
+			
 			TaskCompletionSource<AChannel> t = this.acceptTcs;
 			this.acceptTcs = null;
 			kChannel = this.CreateAcceptChannel(udpReceiveResult.RemoteEndPoint, requestConn);
@@ -147,6 +163,16 @@ namespace Model
 			return channel;
 		}
 
+		public void AddToUpdate(long id)
+		{
+			this.updateChannels.Add(id);
+		}
+
+		public void AddToNextTimeUpdate(long time, long id)
+		{
+			this.timerMap.Add(time, id);
+		}
+
 		public override void Add(Action action)
 		{
 		}
@@ -169,7 +195,6 @@ namespace Model
 			}
 
 			acceptTcs = new TaskCompletionSource<AChannel>();
-
 			return this.acceptTcs.Task;
 		}
 
@@ -198,22 +223,41 @@ namespace Model
 		
 		public override void Update()
 		{
-			this.TimeNow = (uint)(TimeHelper.Now() & 0x00000000ffffffff);
-			if (this.TimeNow - lastUpdateTime < 5)
+			this.TimeNow = (uint)TimeHelper.Now();
+			
+			foreach (long time in this.timerMap.Keys)
 			{
-				return;
+				if (time > this.TimeNow)
+				{
+					break;
+				}
+				this.timeoutTimer.Enqueue(time);
+			}
+			while (this.timeoutTimer.Count > 0)
+			{
+				long key = this.timeoutTimer.Dequeue();
+				List<long> timeOutId = this.timerMap[key];
+				foreach (long id in timeOutId)
+				{
+					this.updateChannels.Add(id);
+				}
+				this.timerMap.Remove(key);
 			}
 
-			this.lastUpdateTime = this.TimeNow;
-
-			foreach (KChannel channel in this.idChannels.Values)
+			foreach (long id in updateChannels)
 			{
-				if (channel.Id == 0)
+				KChannel kChannel;
+				if (!this.idChannels.TryGetValue(id, out kChannel))
+				{
+					continue;
+				}
+				if (kChannel.Id == 0)
 				{
 					continue;
 				}
-				channel.Update(this.TimeNow);
+				kChannel.Update(this.TimeNow);
 			}
+			this.updateChannels.Clear();
 
 			while (true)
 			{