/// <summary> /// Start the sending process. /// </summary> BinaryWriter BeginSend(Packet packet) { mBuffer = Buffer.Create(); BinaryWriter writer = mBuffer.BeginPacket(packet); return(writer); }
void OnEnable() { if (mRequest == null) { mRequest = Buffer.Create(); mRequest.BeginPacket(Packet.RequestServerList).Write(GameServer.gameID); mRequest.EndPacket(); } if (mRemoteAddress == null) { if (string.IsNullOrEmpty(remoteAddress)) { mRemoteAddress = new IPEndPoint(IPAddress.Broadcast, remotePort); } else { mRemoteAddress = Tools.ResolveEndPoint(remoteAddress, remotePort); } if (mRemoteAddress == null) { mUdp.Error(new IPEndPoint(IPAddress.Loopback, mUdp.listeningPort), "Invalid address: " + remoteAddress + ":" + remotePort); } } // Twice just in case the first try falls on a taken port if (!mUdp.Start(Tools.randomPort)) mUdp.Start(Tools.randomPort); }
/// <summary> /// Close the connection. /// </summary> public void Close(bool notify) { stage = Stage.NotConnected; name = "Guest"; data = null; if (mReceiveBuffer != null) { mReceiveBuffer.Recycle(); mReceiveBuffer = null; } if (mSocket != null) { try { if (mSocket.Connected) { mSocket.Shutdown(SocketShutdown.Both); } mSocket.Close(); } catch (System.Exception) {} mSocket = null; if (notify) { Buffer buffer = Buffer.Create(); buffer.BeginPacket(Packet.Disconnect); buffer.EndTcpPacketWithOffset(4); lock (mIn) mIn.Enqueue(buffer); } } }
void Start() { if (mRequest == null) { mRequest = Buffer.Create(); mRequest.BeginPacket(Packet.RequestServerList).Write(GameServer.gameID); mRequest.EndPacket(); } if (mRemoteAddress == null) { mRemoteAddress = string.IsNullOrEmpty(remoteAddress) ? new IPEndPoint(IPAddress.Broadcast, remotePort) : Tools.ResolveEndPoint(remoteAddress, remotePort); if (mRemoteAddress == null) { mUdp.Error(new IPEndPoint(IPAddress.Loopback, mUdp.listeningPort), "Invalid address: " + remoteAddress + ":" + remotePort); } } // Twice just in case the first try falls on a taken port if (!mUdp.Start(Tools.randomPort, UdpProtocol.defaultBroadcastInterface)) { mUdp.Start(Tools.randomPort, UdpProtocol.defaultBroadcastInterface); } }
/// <summary> /// Send an empty packet to the target destination. /// Can be used for NAT punch-through, or just to keep a UDP connection alive. /// Empty packets are simply ignored. /// </summary> public void SendEmptyPacket(IPEndPoint ip) { Buffer buffer = Buffer.Create(false); buffer.BeginPacket(Packet.Empty); buffer.EndPacket(); Send(buffer, ip); }
/// <summary> /// Add an error packet to the incoming queue. /// </summary> void RespondWithError(Buffer buffer, string error) { #if UNITY_EDITOR Debug.LogError(error); #endif buffer.BeginPacket(Packet.Error).Write(error); buffer.EndTcpPacketWithOffset(4); lock (mIn) mIn.Enqueue(buffer); }
/// <summary> /// Close the connection. /// </summary> void CloseNotThreadSafe(bool notify) { Buffer.Recycle(mOut); stage = Stage.NotConnected; if (mSocket != null) { try { if (mSocket.Connected) { mSocket.Shutdown(SocketShutdown.Both); } mSocket.Close(); } catch (System.Exception) {} mSocket = null; if (notify) { Buffer buffer = Buffer.Create(); buffer.BeginPacket(Packet.Disconnect); buffer.EndTcpPacketWithOffset(4); lock (mIn) { Buffer.Recycle(mIn); mIn.Enqueue(buffer); } } else { lock (mIn) Buffer.Recycle(mIn); } } else if (notify && sendQueue != null) { sendQueue = null; Buffer buffer = Buffer.Create(); buffer.BeginPacket(Packet.Disconnect); buffer.EndTcpPacketWithOffset(4); lock (mIn) { Buffer.Recycle(mIn); mIn.Enqueue(buffer); } } if (mReceiveBuffer != null) { mReceiveBuffer.Recycle(); mReceiveBuffer = null; } }
/// <summary> /// Add an error packet to the incoming queue. /// </summary> public void Error(IPEndPoint ip, string error) { Buffer buffer = Buffer.Create(); buffer.BeginPacket(Packet.Error).Write(error); buffer.EndTcpPacketWithOffset(4); Datagram dg = new Datagram(); dg.buffer = buffer; dg.ip = ip; lock (mIn) mIn.Enqueue(dg); }
/// <summary> /// Write a complete ForwardToOthers packet to the specified buffer. /// </summary> public int WritePacket(int channelID, Buffer buffer, int offset) { BinaryWriter writer = buffer.BeginPacket(Packet.ForwardToOthers, offset); writer.Write(0); writer.Write(channelID); writer.Write(uid); if (functionID == 0) { writer.Write(functionName); } writer.Write(data.buffer, 0, data.size); return(buffer.EndTcpPacketStartingAt(offset)); }
/// <summary> /// Add an error packet to the incoming queue. /// </summary> public void Error(IPEndPoint ip, string error) { #if UNITY_EDITOR UnityEngine.Debug.LogError(error); #endif Buffer buffer = Buffer.Create(); buffer.BeginPacket(Packet.Error).Write(error); buffer.EndTcpPacketWithOffset(4); Datagram dg = new Datagram(); dg.buffer = buffer; dg.ip = ip; lock (mIn) mIn.Enqueue(dg); }
/// <summary> /// Send periodic updates. /// </summary> void ThreadFunction() { mInternal = new IPEndPoint(Tools.localAddress, mGameServer.tcpPort); mExternal = new IPEndPoint(Tools.externalAddress, mGameServer.tcpPort); for (; ;) { #if !STANDALONE if (TNManager.isPaused) { Thread.Sleep(500); continue; } #endif long time = DateTime.UtcNow.Ticks / 10000; if (mShutdown) { Buffer buffer = Buffer.Create(); BinaryWriter writer = buffer.BeginPacket(Packet.RequestRemoveServer); writer.Write(GameServer.gameID); Tools.Serialize(writer, mInternal); Tools.Serialize(writer, mExternal); buffer.EndPacket(); mUdp.Send(buffer, mRemoteAddress); buffer.Recycle(); mThread = null; break; } if (mNextSend < time && mGameServer != null) { mNextSend = time + 3000; Buffer buffer = Buffer.Create(); var writer = buffer.BeginPacket(Packet.RequestAddServer); writer.Write(GameServer.gameID); writer.Write(mGameServer.name); writer.Write((short)mGameServer.playerCount); Tools.Serialize(writer, mInternal); Tools.Serialize(writer, mExternal); buffer.EndPacket(); mUdp.Send(buffer, mRemoteAddress); buffer.Recycle(); } try { Thread.Sleep(10); } catch (System.Threading.ThreadInterruptedException) { return; } } }
/// <summary> /// Send periodic updates. /// </summary> void ThreadFunction() { mInternal = new IPEndPoint(Tools.localAddress, mGameServer.tcpPort); mExternal = new IPEndPoint(Tools.externalAddress, mGameServer.tcpPort); for (; ;) { long time = DateTime.Now.Ticks / 10000; if (mShutdown) { Buffer buffer = Buffer.Create(); BinaryWriter writer = buffer.BeginPacket(Packet.RequestRemoveServer); writer.Write(GameServer.gameID); Tools.Serialize(writer, mInternal); Tools.Serialize(writer, mExternal); buffer.EndPacket(); mUdp.Send(buffer, mRemoteAddress); buffer.Recycle(); mThread = null; break; } if (mNextSend < time && mGameServer != null) { mNextSend = time + 3000; Buffer buffer = Buffer.Create(); BinaryWriter writer = buffer.BeginPacket(Packet.RequestAddServer); writer.Write(GameServer.gameID); writer.Write(mGameServer.name); writer.Write((short)mGameServer.playerCount); Tools.Serialize(writer, mInternal); Tools.Serialize(writer, mExternal); buffer.EndPacket(); mUdp.Send(buffer, mRemoteAddress); buffer.Recycle(); } Thread.Sleep(10); } }
/// <summary> /// Start the sending process. /// </summary> BinaryWriter BeginSend(Packet type) { mBuffer = Buffer.Create(); BinaryWriter writer = mBuffer.BeginPacket(type); return writer; }
/// <summary> /// Channel joining process involves multiple steps. It's faster to perform them all at once. /// </summary> public void FinishJoiningChannel() { Buffer buffer = Buffer.Create(); // Step 2: Tell the player who else is in the channel BinaryWriter writer = buffer.BeginPacket(Packet.ResponseJoiningChannel); { writer.Write(channel.id); writer.Write((short)channel.players.size); for (int i = 0; i < channel.players.size; ++i) { TcpPlayer tp = channel.players[i]; writer.Write(tp.id); writer.Write(string.IsNullOrEmpty(tp.name) ? "Guest" : tp.name); } } // End the first packet, but remember where it ended int offset = buffer.EndPacket(); // Step 3: Inform the player of who is hosting if (channel.host == null) { channel.host = this; } buffer.BeginPacket(Packet.ResponseSetHost, offset); writer.Write(channel.host.id); offset = buffer.EndTcpPacketStartingAt(offset); // Step 4: Send the channel's data if (!string.IsNullOrEmpty(channel.data)) { buffer.BeginPacket(Packet.ResponseSetChannelData, offset); writer.Write(channel.data); offset = buffer.EndTcpPacketStartingAt(offset); } // Step 5: Inform the player of what level we're on buffer.BeginPacket(Packet.ResponseLoadLevel, offset); writer.Write(string.IsNullOrEmpty(channel.level) ? "" : channel.level); offset = buffer.EndTcpPacketStartingAt(offset); // Step 6: Send the list of objects that have been created for (int i = 0; i < channel.created.size; ++i) { Channel.CreatedObject obj = channel.created.buffer[i]; buffer.BeginPacket(Packet.ResponseCreate, offset); writer.Write(obj.playerID); writer.Write(obj.objectID); writer.Write(obj.uniqueID); writer.Write(obj.buffer.buffer, obj.buffer.position, obj.buffer.size); offset = buffer.EndTcpPacketStartingAt(offset); } // Step 7: Send the list of objects that have been destroyed if (channel.destroyed.size != 0) { buffer.BeginPacket(Packet.ResponseDestroy, offset); writer.Write((ushort)channel.destroyed.size); for (int i = 0; i < channel.destroyed.size; ++i) { writer.Write(channel.destroyed.buffer[i]); } offset = buffer.EndTcpPacketStartingAt(offset); } // Step 8: Send all buffered RFCs to the new player for (int i = 0; i < channel.rfcs.size; ++i) { Buffer rfcBuff = channel.rfcs[i].buffer; rfcBuff.BeginReading(); buffer.BeginWriting(offset); writer.Write(rfcBuff.buffer, rfcBuff.position, rfcBuff.size); offset = buffer.EndWriting(); } // Step 9: The join process is now complete buffer.BeginPacket(Packet.ResponseJoinChannel, offset); writer.Write(true); offset = buffer.EndTcpPacketStartingAt(offset); // Send the entire buffer SendTcpPacket(buffer); buffer.Recycle(); }
/// <summary> /// Process a single incoming packet. Returns whether we should keep processing packets or not. /// </summary> bool ProcessPacket(Buffer buffer, IPEndPoint ip) { mPacketSource = ip; BinaryReader reader = buffer.BeginReading(); if (buffer.size == 0) { return(true); } int packetID = reader.ReadByte(); Packet response = (Packet)packetID; // Verification step must be passed first if (response == Packet.ResponseID || mTcp.stage == TcpProtocol.Stage.Verifying) { if (mTcp.VerifyResponseID(response, reader)) { mTimeDifference = reader.ReadInt64() - (System.DateTime.UtcNow.Ticks / 10000); #if !UNITY_WEBPLAYER if (mUdp.isActive) { // If we have a UDP listener active, tell the server BeginSend(Packet.RequestSetUDP).Write((ushort)mUdp.listeningPort); EndSend(); } #endif mCanPing = true; if (onConnect != null) { onConnect(true, null); } } return(true); } //#if !UNITY_EDITOR // DEBUG // if (response != Packet.ResponsePing) Console.WriteLine("Client: " + response + " " + buffer.position + " of " + buffer.size + ((ip == null) ? " (TCP)" : " (UDP)")); //#else // if (response != Packet.ResponsePing) UnityEngine.Debug.Log("Client: " + response + " " + buffer.position + " of " + buffer.size + ((ip == null) ? " (TCP)" : " (UDP)")); //#endif OnPacket callback; if (packetHandlers.TryGetValue((byte)response, out callback) && callback != null) { callback(response, reader, ip); return(true); } switch (response) { case Packet.Empty: break; case Packet.ForwardToAll: case Packet.ForwardToOthers: case Packet.ForwardToAllSaved: case Packet.ForwardToOthersSaved: case Packet.ForwardToHost: case Packet.Broadcast: { if (onForwardedPacket != null) { onForwardedPacket(reader); } break; } case Packet.ForwardToPlayer: { // Skip the player ID reader.ReadInt32(); if (onForwardedPacket != null) { onForwardedPacket(reader); } break; } case Packet.ForwardByName: { // Skip the player name reader.ReadString(); if (onForwardedPacket != null) { onForwardedPacket(reader); } break; } case Packet.SyncPlayerData: { Player target = GetPlayer(reader.ReadInt32()); if (target != null) { target.data = reader.ReadObject(); if (onPlayerSync != null) { onPlayerSync(target); } } break; } case Packet.ResponsePing: { int ping = (int)(mMyTime - mPingTime); if (ip != null) { if (onPing != null && ip != null) { onPing(ip, ping); } } else { mCanPing = true; mPing = ping; } break; } case Packet.ResponseSetUDP: { #if !UNITY_WEBPLAYER // The server has a new port for UDP traffic ushort port = reader.ReadUInt16(); if (port != 0) { IPAddress ipa = new IPAddress(mTcp.tcpEndPoint.Address.GetAddressBytes()); mServerUdpEndPoint = new IPEndPoint(ipa, port); // Send the first UDP packet to the server if (mUdp.isActive) { mBuffer = Buffer.Create(); mBuffer.BeginPacket(Packet.RequestActivateUDP).Write(playerID); mBuffer.EndPacket(); mUdp.Send(mBuffer, mServerUdpEndPoint); mBuffer.Recycle(); mBuffer = null; } } else { mServerUdpEndPoint = null; } #endif break; } case Packet.ResponseJoiningChannel: { mIsInChannel = true; mDictionary.Clear(); players.Clear(); mChannelID = reader.ReadInt32(); int count = reader.ReadInt16(); for (int i = 0; i < count; ++i) { Player p = new Player(); p.id = reader.ReadInt32(); p.name = reader.ReadString(); p.data = reader.ReadObject(); mDictionary.Add(p.id, p); players.Add(p); } break; } case Packet.ResponseLoadLevel: { // Purposely return after loading a level, ensuring that all future callbacks happen after loading if (onLoadLevel != null) { onLoadLevel(reader.ReadString()); } return(false); } case Packet.ResponsePlayerLeft: { Player p = GetPlayer(reader.ReadInt32()); if (p != null) { mDictionary.Remove(p.id); } players.Remove(p); if (onPlayerLeft != null) { onPlayerLeft(p); } break; } case Packet.ResponsePlayerJoined: { Player p = new Player(); p.id = reader.ReadInt32(); p.name = reader.ReadString(); p.data = reader.ReadObject(); mDictionary.Add(p.id, p); players.Add(p); if (onPlayerJoined != null) { onPlayerJoined(p); } break; } case Packet.ResponseSetHost: { mHost = reader.ReadInt32(); if (onSetHost != null) { onSetHost(isHosting); } break; } case Packet.ResponseSetChannelData: { mData = reader.ReadString(); if (onSetChannelData != null) { onSetChannelData(mData); } break; } case Packet.ResponseJoinChannel: { mIsInChannel = reader.ReadBoolean(); if (onJoinChannel != null) { onJoinChannel(mIsInChannel, mIsInChannel ? null : reader.ReadString()); } break; } case Packet.ResponseLeaveChannel: { mData = ""; mChannelID = 0; mIsInChannel = false; mDictionary.Clear(); players.Clear(); if (onLeftChannel != null) { onLeftChannel(); } break; } case Packet.ResponseRenamePlayer: { Player p = GetPlayer(reader.ReadInt32()); string oldName = p.name; if (p != null) { p.name = reader.ReadString(); } if (onRenamePlayer != null) { onRenamePlayer(p, oldName); } break; } case Packet.ResponseCreate: { if (onCreate != null) { int playerID = reader.ReadInt32(); ushort index = reader.ReadUInt16(); uint objID = reader.ReadUInt32(); onCreate(playerID, index, objID, reader); } break; } case Packet.ResponseDestroy: { if (onDestroy != null) { int count = reader.ReadUInt16(); for (int i = 0; i < count; ++i) { onDestroy(reader.ReadUInt32()); } } break; } case Packet.Error: { if (mTcp.stage != TcpProtocol.Stage.Connected && onConnect != null) { onConnect(false, reader.ReadString()); } else if (onError != null) { onError(reader.ReadString()); } break; } case Packet.Disconnect: { mData = ""; if (isInChannel && onLeftChannel != null) { onLeftChannel(); } players.Clear(); mDictionary.Clear(); mTcp.Close(false); mLoadFiles.Clear(); if (onDisconnect != null) { onDisconnect(); } break; } case Packet.ResponseLoadFile: { string filename = reader.ReadString(); int size = reader.ReadInt32(); byte[] data = reader.ReadBytes(size); OnLoadFile lfc = mLoadFiles.Pop(); if (lfc != null) { lfc(filename, data); } break; } } return(true); }
/// <summary> /// Begin sending a new packet to the server. /// </summary> public BinaryWriter BeginSend(byte packetID) { mBuffer = Buffer.Create(false); return mBuffer.BeginPacket(packetID); }
/// <summary> /// Add an error packet to the incoming queue. /// </summary> void Error(Buffer buffer, string error) { buffer.BeginPacket(Packet.Error).Write(error); buffer.EndTcpPacketWithOffset(4); lock (mIn) mIn.Enqueue(buffer); }
/// <summary> /// Process a single incoming packet. Returns whether we should keep processing packets or not. /// </summary> bool ProcessPacket(Buffer buffer, IPEndPoint ip) { mPacketSource = ip; BinaryReader reader = buffer.BeginReading(); if (buffer.size == 0) { return(true); } int packetID = reader.ReadByte(); Packet response = (Packet)packetID; #if DEBUG_PACKETS && !STANDALONE if (response != Packet.ResponsePing && response != Packet.Broadcast) { UnityEngine.Debug.Log("Client: " + response + " (" + buffer.size + " bytes) " + ((ip == null) ? "(TCP)" : "(UDP)")); } #endif // Verification step must be passed first if (response == Packet.ResponseID || mTcp.stage == TcpProtocol.Stage.Verifying) { if (mTcp.VerifyResponseID(response, reader)) { mTimeDifference = reader.ReadInt64() - (System.DateTime.UtcNow.Ticks / 10000); #if !UNITY_WEBPLAYER if (mUdp.isActive) { // If we have a UDP listener active, tell the server BeginSend(Packet.RequestSetUDP).Write((ushort)mUdp.listeningPort); EndSend(); } #endif mCanPing = true; if (onConnect != null) { onConnect(true, null); } } return(true); } OnPacket callback; if (packetHandlers.TryGetValue((byte)response, out callback) && callback != null) { callback(response, reader, ip); return(true); } switch (response) { case Packet.Empty: break; case Packet.ForwardToAll: case Packet.ForwardToOthers: case Packet.ForwardToAllSaved: case Packet.ForwardToOthersSaved: case Packet.ForwardToHost: case Packet.BroadcastAdmin: case Packet.Broadcast: { packetSourceID = reader.ReadInt32(); int channelID = reader.ReadInt32(); if (onForwardedPacket != null) { onForwardedPacket(channelID, reader); } break; } case Packet.ForwardToPlayer: { packetSourceID = reader.ReadInt32(); reader.ReadInt32(); // Skip the target player ID int channelID = reader.ReadInt32(); if (onForwardedPacket != null) { onForwardedPacket(channelID, reader); } break; } case Packet.ForwardByName: { packetSourceID = reader.ReadInt32(); reader.ReadString(); // Skip the player name int channelID = reader.ReadInt32(); if (onForwardedPacket != null) { onForwardedPacket(channelID, reader); } break; } case Packet.ResponseSetPlayerData: { int pid = reader.ReadInt32(); Player target = GetPlayer(pid); if (target != null) { string path = reader.ReadString(); DataNode node = target.Set(path, reader.ReadObject()); if (onSetPlayerData != null) { onSetPlayerData(target, path, node); } } else { UnityEngine.Debug.LogError("Not found: " + pid); } break; } case Packet.ResponsePing: { int ping = (int)(mMyTime - mPingTime); if (ip != null) { if (onPing != null && ip != null) { onPing(ip, ping); } } else { mCanPing = true; mPing = ping; } break; } case Packet.ResponseSetUDP: { #if !UNITY_WEBPLAYER // The server has a new port for UDP traffic ushort port = reader.ReadUInt16(); if (port != 0 && mTcp.tcpEndPoint != null) { IPAddress ipa = new IPAddress(mTcp.tcpEndPoint.Address.GetAddressBytes()); mServerUdpEndPoint = new IPEndPoint(ipa, port); // Send the first UDP packet to the server if (mUdp.isActive) { mBuffer = Buffer.Create(); mBuffer.BeginPacket(Packet.RequestActivateUDP).Write(playerID); mBuffer.EndPacket(); mUdp.Send(mBuffer, mServerUdpEndPoint); mBuffer.Recycle(); mBuffer = null; } } else { mServerUdpEndPoint = null; } #endif break; } case Packet.ResponseJoiningChannel: { int channelID = reader.ReadInt32(); int count = reader.ReadInt16(); Channel ch = GetChannel(channelID, true); for (int i = 0; i < count; ++i) { int pid = reader.ReadInt32(); Player p = GetPlayer(pid, true); if (reader.ReadBoolean()) { p.name = reader.ReadString(); p.dataNode = reader.ReadDataNode(); } ch.players.Add(p); } break; } case Packet.ResponseLoadLevel: { // Purposely return after loading a level, ensuring that all future callbacks happen after loading int channelID = reader.ReadInt32(); string scene = reader.ReadString(); if (onLoadLevel != null) { onLoadLevel(channelID, scene); } return(false); } case Packet.ResponsePlayerJoined: { int channelID = reader.ReadInt32(); Channel ch = GetChannel(channelID); if (ch != null) { Player p = GetPlayer(reader.ReadInt32(), true); if (reader.ReadBoolean()) { p.name = reader.ReadString(); p.dataNode = reader.ReadDataNode(); } ch.players.Add(p); if (onPlayerJoin != null) { onPlayerJoin(channelID, p); } } break; } case Packet.ResponsePlayerLeft: { int channelID = reader.ReadInt32(); int playerID = reader.ReadInt32(); Channel ch = GetChannel(channelID); if (ch != null) { Player p = ch.GetPlayer(playerID); ch.players.Remove(p); RebuildPlayerDictionary(); if (onPlayerLeave != null) { onPlayerLeave(channelID, p); } } break; } case Packet.ResponseSetHost: { int channelID = reader.ReadInt32(); int hostID = reader.ReadInt32(); for (int i = 0; i < mChannels.size; ++i) { Channel ch = mChannels[i]; if (ch.id == channelID) { ch.host = GetPlayer(hostID); if (onHostChanged != null) { onHostChanged(ch); } break; } } break; } case Packet.ResponseSetChannelData: { int channelID = reader.ReadInt32(); Channel ch = GetChannel(channelID); if (ch != null) { string path = reader.ReadString(); DataNode node = ch.Set(path, reader.ReadObject()); if (onSetChannelData != null) { onSetChannelData(ch, path, node); } } break; } case Packet.ResponseJoinChannel: { int channelID = reader.ReadInt32(); bool success = reader.ReadBoolean(); string msg = success ? null : reader.ReadString(); // mJoining can contain -2 and -1 when joining random channels if (!mJoining.Remove(channelID)) { for (int i = 0; i < mJoining.size; ++i) { int id = mJoining[i]; if (id < 0) { mJoining.RemoveAt(i); break; } } } #if UNITY_EDITOR if (!success) { UnityEngine.Debug.LogError("ResponseJoinChannel: " + success + ", " + msg); } #endif if (onJoinChannel != null) { onJoinChannel(channelID, success, msg); } break; } case Packet.ResponseLeaveChannel: { int channelID = reader.ReadInt32(); for (int i = 0; i < mChannels.size; ++i) { Channel ch = mChannels[i]; if (ch.id == channelID) { mChannels.RemoveAt(i); break; } } RebuildPlayerDictionary(); if (onLeaveChannel != null) { onLeaveChannel(channelID); } // Purposely exit after receiving a "left channel" notification so that other packets get handled in the next frame. return(false); } case Packet.ResponseRenamePlayer: { Player p = GetPlayer(reader.ReadInt32()); string oldName = p.name; if (p != null) { p.name = reader.ReadString(); } if (onRenamePlayer != null) { onRenamePlayer(p, oldName); } break; } case Packet.ResponseCreateObject: { if (onCreate != null) { int playerID = reader.ReadInt32(); int channelID = reader.ReadInt32(); uint objID = reader.ReadUInt32(); onCreate(channelID, playerID, objID, reader); } break; } case Packet.ResponseDestroyObject: { if (onDestroy != null) { int channelID = reader.ReadInt32(); int count = reader.ReadUInt16(); for (int i = 0; i < count; ++i) { uint val = reader.ReadUInt32(); onDestroy(channelID, val); } } break; } case Packet.ResponseTransferObject: { if (onTransfer != null) { int from = reader.ReadInt32(); int to = reader.ReadInt32(); uint id0 = reader.ReadUInt32(); uint id1 = reader.ReadUInt32(); onTransfer(from, to, id0, id1); } break; } case Packet.Error: { string err = reader.ReadString(); if (onError != null) { onError(err); } if (mTcp.stage != TcpProtocol.Stage.Connected && onConnect != null) { onConnect(false, err); } break; } case Packet.Disconnect: { if (onLeaveChannel != null) { while (mChannels.size > 0) { int index = mChannels.size - 1; Channel ch = mChannels[index]; mChannels.RemoveAt(index); onLeaveChannel(ch.id); } } mChannels.Clear(); mGetChannelsCallbacks.Clear(); mDictionary.Clear(); mTcp.Close(false); mLoadFiles.Clear(); mGetFiles.Clear(); mJoining.Clear(); mIsAdmin = false; if (mLocalServer != null) { mLocalServer.localClient = null; mLocalServer = null; } if (onDisconnect != null) { onDisconnect(); } mConfig = new DataNode("Version", Player.version); break; } case Packet.ResponseGetFileList: { string filename = reader.ReadString(); int size = reader.ReadInt32(); string[] files = null; if (size > 0) { files = new string[size]; for (int i = 0; i < size; ++i) { files[i] = reader.ReadString(); } } OnGetFiles cb = null; if (mGetFiles.TryGetValue(filename, out cb)) { mGetFiles.Remove(filename); } if (cb != null) { try { cb(filename, files); } #if UNITY_EDITOR catch (System.Exception ex) { Debug.LogError(ex.Message + ex.StackTrace); } #else catch (System.Exception) {} #endif } break; } case Packet.ResponseLoadFile: { string filename = reader.ReadString(); int size = reader.ReadInt32(); byte[] data = reader.ReadBytes(size); OnLoadFile cb = null; if (mLoadFiles.TryGetValue(filename, out cb)) { mLoadFiles.Remove(filename); } if (cb != null) { try { cb(filename, data); } #if UNITY_EDITOR catch (System.Exception ex) { Debug.LogError(ex.Message + ex.StackTrace); } #else catch (System.Exception) {} #endif } break; } case Packet.ResponseVerifyAdmin: { int pid = reader.ReadInt32(); Player p = GetPlayer(pid); if (p == player) { mIsAdmin = true; } if (onSetAdmin != null) { onSetAdmin(p); } break; } case Packet.ResponseSetServerData: { string path = reader.ReadString(); object obj = reader.ReadObject(); if (obj != null) { DataNode node = mConfig.SetHierarchy(path, obj); if (onSetServerData != null) { onSetServerData(path, node); } } else { DataNode node = mConfig.RemoveHierarchy(path); if (onSetServerData != null) { onSetServerData(path, node); } } break; } case Packet.ResponseChannelList: { if (mGetChannelsCallbacks.Count != 0) { OnGetChannels cb = mGetChannelsCallbacks.Dequeue(); List <Channel.Info> channels = new List <Channel.Info>(); int count = reader.ReadInt32(); for (int i = 0; i < count; ++i) { Channel.Info info = new Channel.Info(); info.id = reader.ReadInt32(); info.players = reader.ReadUInt16(); info.limit = reader.ReadUInt16(); info.hasPassword = reader.ReadBoolean(); info.isPersistent = reader.ReadBoolean(); info.level = reader.ReadString(); info.data = reader.ReadDataNode(); channels.Add(info); } if (cb != null) { cb(channels); } } break; } case Packet.ResponseLockChannel: { int channelID = reader.ReadInt32(); bool isLocked = reader.ReadBoolean(); Channel ch = GetChannel(channelID); if (ch != null) { ch.isLocked = isLocked; } if (onLockChannel != null) { onLockChannel(channelID, isLocked); } break; } } return(true); }
/// <summary> /// Begin sending a new packet to the server. /// </summary> public BinaryWriter BeginSend(byte packetID) { mBuffer = Buffer.Create(false); return(mBuffer.BeginPacket(packetID)); }
/// <summary> /// See if the received packet can be processed and split it up into different ones. /// </summary> bool ProcessBuffer(byte[] bytes, int offset, int byteCount) { if (offset + byteCount > bytes.Length) { LogError("ProcessBuffer(" + bytes.Length + " bytes, offset " + offset + ", count " + byteCount); return(false); } if (mReceiveBuffer == null) { // Create a new packet buffer mReceiveBuffer = Buffer.Create(); mReceiveBuffer.BeginWriting(false).Write(bytes, offset, byteCount); mExpected = 0; mOffset = 0; } else { // Append this data to the end of the last used buffer mReceiveBuffer.BeginWriting(true).Write(bytes, offset, byteCount); } for (mAvailable = mReceiveBuffer.size - mOffset; mAvailable > 4;) { // Figure out the expected size of the packet if (mExpected == 0) { mExpected = mReceiveBuffer.PeekInt(mOffset); // "GET " -- HTTP GET request sent by a web browser if (mExpected == 542393671) { if (httpGetSupport) { if (stage == Stage.Verifying || stage == Stage.WebBrowser) { stage = Stage.WebBrowser; string request = Encoding.ASCII.GetString(mReceiveBuffer.buffer, mOffset, mAvailable); mReceiveBuffer.BeginPacket(Packet.RequestHTTPGet).Write(request); mReceiveBuffer.EndPacket(); mReceiveBuffer.BeginReading(4); lock (mIn) { mIn.Enqueue(mReceiveBuffer); mReceiveBuffer = null; mExpected = 0; mOffset = 0; } } return(true); } mReceiveBuffer.Recycle(); mReceiveBuffer = null; mExpected = 0; mOffset = 0; Disconnect(); return(false); } else if (mExpected < 0 || mExpected > 16777216) { #if UNITY_EDITOR LogError("Malformed data packet: " + mOffset + ", " + mAvailable + " / " + mExpected); var temp = new byte[mReceiveBuffer.size]; for (int i = 0; i < byteCount; ++i) { temp[i] = mReceiveBuffer.buffer[i]; } var fn = "error_" + lastReceivedTime + ".full"; Tools.WriteFile(fn, temp); Debug.Log("Packet saved as " + fn); #else LogError("Malformed data packet: " + mOffset + ", " + mAvailable + " / " + mExpected); #endif mReceiveBuffer.Recycle(); mReceiveBuffer = null; mExpected = 0; mOffset = 0; Disconnect(); return(false); } } // The first 4 bytes of any packet always contain the number of bytes in that packet mAvailable -= 4; // If the entire packet is present if (mAvailable == mExpected) { // Reset the position to the beginning of the packet mReceiveBuffer.BeginReading(mOffset + 4); // This packet is now ready to be processed lock (mIn) { mIn.Enqueue(mReceiveBuffer); mReceiveBuffer = null; mExpected = 0; mOffset = 0; } break; } else if (mAvailable > mExpected) { // There is more than one packet. Extract this packet fully. int realSize = mExpected + 4; var temp = Buffer.Create(); // Extract the packet and move past its size component var bw = temp.BeginWriting(); bw.Write(mReceiveBuffer.buffer, mOffset, realSize); temp.BeginReading(4); // This packet is now ready to be processed lock (mIn) { mIn.Enqueue(temp); // Skip this packet mAvailable -= mExpected; mOffset += realSize; mExpected = 0; } } else { break; } } return(true); }
/// <summary> /// Channel joining process involves multiple steps. It's faster to perform them all at once. /// </summary> public void FinishJoiningChannel() { Buffer buffer = Buffer.Create(); // Step 2: Tell the player who else is in the channel BinaryWriter writer = buffer.BeginPacket(Packet.ResponseJoiningChannel); { writer.Write(channel.id); writer.Write((short)channel.players.size); for (int i = 0; i < channel.players.size; ++i) { TcpPlayer tp = channel.players[i]; writer.Write(tp.id); writer.Write(string.IsNullOrEmpty(tp.name) ? "Guest" : tp.name); #if STANDALONE if (tp.data == null) { writer.Write((byte)0); } else { writer.Write((byte[])tp.data); } #else writer.WriteObject(tp.data); #endif } } // End the first packet, but remember where it ended int offset = buffer.EndPacket(); // Step 3: Inform the player of who is hosting if (channel.host == null) { channel.host = this; } buffer.BeginPacket(Packet.ResponseSetHost, offset); writer.Write(channel.host.id); offset = buffer.EndTcpPacketStartingAt(offset); // Step 4: Send the channel's data if (!string.IsNullOrEmpty(channel.data)) { buffer.BeginPacket(Packet.ResponseSetChannelData, offset); writer.Write(channel.data); offset = buffer.EndTcpPacketStartingAt(offset); } // Step 5: Inform the player of what level we're on buffer.BeginPacket(Packet.ResponseLoadLevel, offset); writer.Write(string.IsNullOrEmpty(channel.level) ? "" : channel.level); offset = buffer.EndTcpPacketStartingAt(offset); // Step 6: Send the list of objects that have been created for (int i = 0; i < channel.created.size; ++i) { Channel.CreatedObject obj = channel.created.buffer[i]; bool isPresent = false; for (int b = 0; b < channel.players.size; ++b) { if (channel.players[b].id == obj.playerID) { isPresent = true; break; } } // If the previous owner is not present, transfer ownership to the host if (!isPresent) { obj.playerID = channel.host.id; } buffer.BeginPacket(Packet.ResponseCreate, offset); writer.Write(obj.playerID); writer.Write(obj.objectIndex); writer.Write(obj.objectID); writer.Write(obj.buffer.buffer, obj.buffer.position, obj.buffer.size); offset = buffer.EndTcpPacketStartingAt(offset); } // Step 7: Send the list of objects that have been destroyed if (channel.destroyed.size != 0) { buffer.BeginPacket(Packet.ResponseDestroy, offset); writer.Write((ushort)channel.destroyed.size); for (int i = 0; i < channel.destroyed.size; ++i) { writer.Write(channel.destroyed.buffer[i]); } offset = buffer.EndTcpPacketStartingAt(offset); } // Step 8: Send all buffered RFCs to the new player for (int i = 0; i < channel.rfcs.size; ++i) { Channel.RFC rfc = channel.rfcs[i]; buffer.BeginWriting(offset); writer.Write(rfc.buffer.buffer, 0, rfc.buffer.size); offset = buffer.EndWriting(); } // Step 9: The join process is now complete buffer.BeginPacket(Packet.ResponseJoinChannel, offset); writer.Write(true); offset = buffer.EndTcpPacketStartingAt(offset); // Send the entire buffer SendTcpPacket(buffer); buffer.Recycle(); }
/// <summary> /// Begin sending a new packet to the server. /// </summary> public BinaryWriter BeginSend(Packet type) { mBuffer = Buffer.Create(false); return mBuffer.BeginPacket(type); }
/// <summary> /// Send periodic updates. /// </summary> void ThreadFunction() { mInternal = new IPEndPoint(Tools.localAddress, mGameServer.tcpPort); mExternal = new IPEndPoint(Tools.externalAddress, mGameServer.tcpPort); for (; ;) { long time = DateTime.UtcNow.Ticks / 10000; if (mShutdown) { mTcp.Disconnect(); mThread = null; #if STANDALONE Tools.Print("TcpLobbyLink shut down"); #endif break; } #if !STANDALONE if (TNManager.isPaused) { Thread.Sleep(500); continue; } #endif Buffer buffer; // Try to establish a connection if (mGameServer != null && !mTcp.isConnected && !mTcp.isTryingToConnect && mNextConnect < time) { #if STANDALONE Tools.Print("TcpLobbyLink is connecting to " + mRemoteAddress + "..."); #endif mUpdateNeeded = true; mNextConnect = time + 15000; mTcp.Connect(mRemoteAddress); } while (mTcp.ReceivePacket(out buffer)) { BinaryReader reader = buffer.BeginReading(); Packet response = (Packet)reader.ReadByte(); if (mTcp.stage == TcpProtocol.Stage.Verifying) { if (mTcp.VerifyResponseID(response, reader)) { mTimeDifference = reader.ReadInt64() - (System.DateTime.UtcNow.Ticks / 10000); mWasConnected = true; #if STANDALONE Tools.Print("TcpLobbyLink connection established"); #endif } else { #if STANDALONE Tools.Print("TcpLobbyLink Error: Protocol version mismatch"); #endif mThread = null; return; } } else if (response == Packet.RequestPing) { } #if STANDALONE else if (response == Packet.Error) { Tools.Print("TcpLobbyLink Error: " + reader.ReadString()); } else if (response == Packet.Disconnect) { Tools.Print("TcpLobbyLink disconnected"); } else { Tools.Print("TcpLobbyLink can't handle this packet: " + response); } #endif buffer.Recycle(); } // Automatically try to re-establish a connection on disconnect if (mWasConnected && !mTcp.isConnected && !mTcp.isTryingToConnect) { mNextConnect = time + 5000; mWasConnected = false; } else if (mGameServer != null && mTcp.isConnected && (mUpdateNeeded || mNextSend < time)) { mUpdateNeeded = false; mNextSend = time + 5000; Buffer buff = Buffer.Create(); BinaryWriter writer = buff.BeginPacket(Packet.RequestAddServer); writer.Write(GameServer.gameID); writer.Write(mGameServer.name); writer.Write((short)mGameServer.playerCount); Tools.Serialize(writer, mInternal); Tools.Serialize(writer, mExternal); buff.EndPacket(); mTcp.SendTcpPacket(buff); buff.Recycle(); } try { Thread.Sleep(10); } catch (System.Threading.ThreadInterruptedException) { return; } } }
/// <summary> /// Begin sending a new packet to the server. /// </summary> public BinaryWriter BeginSend(Packet type) { mBuffer = Buffer.Create(false); return(mBuffer.BeginPacket(type)); }
/// <summary> /// Close the connection. /// </summary> void CloseNotThreadSafe(bool notify) { #if !MODDING #if STANDALONE || UNITY_EDITOR if (id != 0) { Tools.Log(name + " (" + address + "): Disconnected [" + id + "]"); } #endif Buffer.Recycle(mOut); stage = Stage.NotConnected; mSending = false; if (mSocket != null || custom != null) { if (mSocket != null) { try { if (mSocket.Connected) { mSocket.Shutdown(SocketShutdown.Both); } mSocket.Close(); } catch (System.Exception) { } mSocket = null; } if (custom != null) { custom.OnDisconnect(); } if (notify) { var buffer = Buffer.Create(); buffer.BeginPacket(Packet.Disconnect); buffer.EndTcpPacketWithOffset(4); lock (mIn) { Buffer.Recycle(mIn); mIn.Enqueue(buffer); } } else { lock (mIn) Buffer.Recycle(mIn); } } else if (notify && sendQueue != null) { sendQueue = null; Buffer buffer = Buffer.Create(); buffer.BeginPacket(Packet.Disconnect); buffer.EndTcpPacketWithOffset(4); lock (mIn) { Buffer.Recycle(mIn); mIn.Enqueue(buffer); } } if (mReceiveBuffer != null) { mReceiveBuffer.Recycle(); mReceiveBuffer = null; } if (onClose != null) { onClose(this); } id = 0; #endif }
/// <summary> /// See if the received packet can be processed and split it up into different ones. /// </summary> bool ProcessBuffer(int bytes) { if (mReceiveBuffer == null) { // Create a new packet buffer mReceiveBuffer = Buffer.Create(); mReceiveBuffer.BeginWriting(false).Write(mTemp, 0, bytes); mExpected = 0; mOffset = 0; } else { // Append this data to the end of the last used buffer mReceiveBuffer.BeginWriting(true).Write(mTemp, 0, bytes); } for (int available = mReceiveBuffer.size - mOffset; available >= 4;) { // Figure out the expected size of the packet if (mExpected == 0) { mExpected = mReceiveBuffer.PeekInt(mOffset); // "GET " -- HTTP GET request sent by a web browser if (mExpected == 542393671) { if (httpGetSupport) { if (stage == Stage.Verifying || stage == Stage.WebBrowser) { stage = Stage.WebBrowser; string request = Encoding.ASCII.GetString(mReceiveBuffer.buffer, mOffset, available); mReceiveBuffer.BeginPacket(Packet.RequestHTTPGet).Write(request); mReceiveBuffer.EndPacket(); mReceiveBuffer.BeginReading(4); lock (mIn) mIn.Enqueue(mReceiveBuffer); mReceiveBuffer = null; mExpected = 0; mOffset = 0; } return(true); } mReceiveBuffer.Recycle(); mReceiveBuffer = null; mExpected = 0; mOffset = 0; Disconnect(); return(false); } else if (mExpected < 0 || mExpected > 16777216) { #if UNITY_EDITOR UnityEngine.Debug.LogError("Malformed data packet: " + mOffset + ", " + available + " / " + mExpected); #else Tools.LogError("Malformed data packet: " + mOffset + ", " + available + " / " + mExpected); #endif mReceiveBuffer.Recycle(); mReceiveBuffer = null; mExpected = 0; mOffset = 0; Disconnect(); return(false); } } // The first 4 bytes of any packet always contain the number of bytes in that packet available -= 4; // If the entire packet is present if (available == mExpected) { // Reset the position to the beginning of the packet mReceiveBuffer.BeginReading(mOffset + 4); // This packet is now ready to be processed lock (mIn) mIn.Enqueue(mReceiveBuffer); mReceiveBuffer = null; mExpected = 0; mOffset = 0; break; } else if (available > mExpected) { // There is more than one packet. Extract this packet fully. int realSize = mExpected + 4; Buffer temp = Buffer.Create(); // Extract the packet and move past its size component BinaryWriter bw = temp.BeginWriting(false); bw.Write(mReceiveBuffer.buffer, mOffset, realSize); temp.BeginReading(4); // This packet is now ready to be processed lock (mIn) mIn.Enqueue(temp); // Skip this packet available -= mExpected; mOffset += realSize; mExpected = 0; } else { break; } } return(true); }