/// <summary> /// Retrieve a player by their UDP end point. /// </summary> TcpPlayer GetPlayer(IPEndPoint ip) { TcpPlayer p = null; mDictionaryEP.TryGetValue(ip, out p); return(p); }
/// <summary> /// Retrieve a player by their ID. /// </summary> TcpPlayer GetPlayer(int id) { TcpPlayer p = null; mDictionaryID.TryGetValue(id, out p); return(p); }
/// <summary> /// Send the outgoing buffer to all connected players. /// </summary> void EndSend(bool reliable) { mBuffer.EndPacket(); if (mBuffer.size > 1024) { reliable = true; } for (int i = 0; i < mChannels.size; ++i) { Channel channel = mChannels[i]; for (int b = 0; b < channel.players.size; ++b) { TcpPlayer player = channel.players[b]; if (player.stage == TcpProtocol.Stage.Connected) { if (reliable || player.udpEndPoint == null || !mAllowUdp) { player.SendTcpPacket(mBuffer); } else { mUdp.Send(mBuffer, player.udpEndPoint); } } } } mBuffer.Recycle(); mBuffer = null; }
/// <summary> /// Send the outgoing buffer to all players in the specified channel. /// </summary> void SendToChannel(bool reliable, Channel channel, Buffer buffer) { mBuffer.MarkAsUsed(); if (mBuffer.size > 1024) { reliable = true; } for (int i = 0; i < channel.players.size; ++i) { TcpPlayer player = channel.players[i]; if (player.stage == TcpProtocol.Stage.Connected) { if (reliable || player.udpEndPoint == null || !mAllowUdp) { player.SendTcpPacket(mBuffer); } else { mUdp.Send(mBuffer, player.udpEndPoint); } } } mBuffer.Recycle(); }
internal NomadLivePlayer(TcpPlayer player) { this.player = player; Id = player.id.ToString(); Character = this; Object = null; // TODO }
/// <summary> /// Remove the specified player. /// </summary> void RemovePlayer(TcpPlayer p) { if (p != null) { SendLeaveChannel(p, false); #if STANDALONE Console.WriteLine(p.address + " has disconnected"); #endif p.Release(); mPlayers.Remove(p); if (p.udpEndPoint != null) { mDictionaryEP.Remove(p.udpEndPoint); p.udpEndPoint = null; } if (p.id != 0) { if (mDictionaryID.Remove(p.id)) { if (lobbyLink != null) { lobbyLink.SendUpdate(this); } if (onPlayerDisconnect != null) { onPlayerDisconnect(p); } } p.id = 0; } } }
/// <summary> /// Remove the specified player from the channel. /// </summary> public void RemovePlayer(TcpPlayer p, List <uint> destroyedObjects) { destroyedObjects.Clear(); if (players.Remove(p)) { // When the host leaves, clear the host (it gets changed in SendLeaveChannel) if (p == host) { host = null; } // Remove all of the non-persistent objects that were created by this player for (int i = created.size; i > 0;) { Channel.CreatedObject obj = created[--i]; if (obj.playerID == p.id) { if (obj.type == 2) { if (obj.buffer != null) { obj.buffer.Recycle(); } uint objID = obj.objectID; created.RemoveAt(i); destroyedObjects.Add(objID); if (objID >= 32768) { mCreatedObjectDictionary.Remove(objID); } DestroyObjectRFCs(objID); } else if (players.size != 0) { // The same operation happens on the client as well obj.playerID = players[0].id; } } } // Close the channel if it wasn't persistent if ((!persistent || playerLimit < 1) && players.size == 0) { closed = true; for (int i = 0; i < rfcs.size; ++i) { RFC r = rfcs[i]; if (r.data != null) { r.data.Recycle(); } } rfcs.Clear(); } } }
/// <summary> /// Add a new player entry. /// </summary> TcpPlayer AddPlayer(Socket socket) { TcpPlayer player = new TcpPlayer(); player.StartReceiving(socket); mPlayers.Add(player); return(player); }
/// <summary> /// Have the specified player assume control of the channel. /// </summary> void SendSetHost(TcpPlayer player) { if (player.channel != null && player.channel.host != player) { player.channel.host = player; BinaryWriter writer = BeginSend(Packet.ResponseSetHost); writer.Write(player.id); EndSend(true, player.channel, null); } }
/// <summary> /// Change the player's UDP end point and update the local dictionary. /// </summary> void SetPlayerUdpEndPoint(TcpPlayer player, IPEndPoint udp) { if (player.udpEndPoint != null) { mDictionaryEP.Remove(player.udpEndPoint); } player.udpEndPoint = udp; if (udp != null) { mDictionaryEP[udp] = player; } }
/// <summary> /// Leave the channel the player is in. /// </summary> void SendLeaveChannel(TcpPlayer player, bool notify) { Channel ch = player.channel; if (ch != null) { // Remove this player from the channel ch.RemovePlayer(player, mTemp); player.channel = null; // Are there other players left? if (ch.players.size > 0) { BinaryWriter writer; // Inform the other players that the player's objects should be destroyed if (mTemp.size > 0) { writer = BeginSend(Packet.ResponseDestroy); writer.Write((ushort)mTemp.size); for (int i = 0; i < mTemp.size; ++i) { writer.Write(mTemp[i]); } EndSend(true, ch, null); } // If this player was the host, choose a new host if (ch.host == null) { SendSetHost(ch.players[0]); } // Inform everyone of this player leaving the channel writer = BeginSend(Packet.ResponsePlayerLeft); writer.Write(player.id); EndSend(true, ch, null); } else if (!ch.persistent) { // No other players left -- delete this channel mChannels.Remove(ch); } // Notify the player that they have left the channel if (notify && player.isConnected) { BeginSend(Packet.ResponseLeaveChannel); EndSend(true, player); } } }
/// <summary> /// Remove the specified player from the channel. /// </summary> public void RemovePlayer(TcpPlayer p, List <uint> destroyedObjects) { destroyedObjects.Clear(); if (p == host) { host = null; } if (players.Remove(p)) { // Remove all of the non-persistent objects that were created by this player for (int i = created.size; i > 0;) { Channel.CreatedObject obj = created[--i]; if (obj.type == 2 && obj.playerID == p.id) { if (obj.buffer != null) { obj.buffer.Recycle(); } created.RemoveAt(i); destroyedObjects.Add(obj.uniqueID); DestroyObjectRFCs(obj.uniqueID); } } // Close the channel if it wasn't persistent if ((!persistent || playerLimit < 1) && players.size == 0) { closed = true; for (int i = 0; i < rfcs.size; ++i) { RFC r = rfcs[i]; if (r.buffer != null) { r.buffer.Recycle(); } } rfcs.Clear(); } } }
/// <summary> /// Send the outgoing buffer to the specified player. /// </summary> void EndSend(bool reliable, TcpPlayer player) { mBuffer.EndPacket(); if (mBuffer.size > 1024) { reliable = true; } if (reliable || player.udpEndPoint == null || !mAllowUdp) { player.SendTcpPacket(mBuffer); } else { mUdp.Send(mBuffer, player.udpEndPoint); } mBuffer.Recycle(); mBuffer = null; }
/// <summary> /// Log an error message. /// </summary> void Error(TcpPlayer p, string error) { #if UNITY_EDITOR if (p != null) { UnityEngine.Debug.LogError(error + " (" + p.address + ")"); } else { UnityEngine.Debug.LogError(error); } #elif STANDALONE if (p != null) { Console.WriteLine(p.address + " ERROR: " + error); } else { Console.WriteLine("ERROR: " + error); } #endif }
/// <summary> /// Join the specified channel. /// </summary> void SendJoinChannel(TcpPlayer player, Channel channel) { if (player.channel == null || player.channel != channel) { // Set the player's channel player.channel = channel; // Everything else gets sent to the player, so it's faster to do it all at once player.FinishJoiningChannel(); // Inform the channel that a new player is joining BinaryWriter writer = BeginSend(Packet.ResponsePlayerJoined); { writer.Write(player.id); writer.Write(string.IsNullOrEmpty(player.name) ? "Guest" : player.name); } EndSend(true, channel, null); // Add this player to the channel now that the joining process is complete channel.players.Add(player); } }
/// <summary> /// Log an error message. /// </summary> void Error(TcpPlayer p, string error) { #if UNITY_EDITOR if (p != null) UnityEngine.Debug.LogError(error + " (" + p.address + ")"); else UnityEngine.Debug.LogError(error); #elif STANDALONE if (p != null) Console.WriteLine(p.address + " ERROR: " + error); else Console.WriteLine("ERROR: " + error); #endif }
/// <summary> /// Process a packet from the player. /// </summary> void ProcessChannelPacket(TcpPlayer player, Buffer buffer, BinaryReader reader, Packet request) { switch (request) { case Packet.RequestCreate: { // Create a new object ushort objectIndex = reader.ReadUInt16(); byte type = reader.ReadByte(); uint uniqueID = 0; if (type != 0) { uniqueID = --player.channel.objectCounter; // 1-32767 is reserved for existing scene objects. // 32768 - 16777215 is for dynamically created objects. if (uniqueID < 32768) { player.channel.objectCounter = 0xFFFFFF; uniqueID = 0xFFFFFF; } Channel.CreatedObject obj = new Channel.CreatedObject(); obj.playerID = player.id; obj.objectID = objectIndex; obj.uniqueID = uniqueID; obj.type = type; if (buffer.size > 0) { obj.buffer = buffer; buffer.MarkAsUsed(); } player.channel.created.Add(obj); } // Inform the channel BinaryWriter writer = BeginSend(Packet.ResponseCreate); writer.Write(player.id); writer.Write(objectIndex); writer.Write(uniqueID); if (buffer.size > 0) writer.Write(buffer.buffer, buffer.position, buffer.size); EndSend(true, player.channel, null); break; } case Packet.RequestDestroy: { // Destroy the specified network object uint uniqueID = reader.ReadUInt32(); // Remove this object if (player.channel.DestroyObject(uniqueID)) { // Inform all players in the channel that the object should be destroyed BinaryWriter writer = BeginSend(Packet.ResponseDestroy); writer.Write((ushort)1); writer.Write(uniqueID); EndSend(true, player.channel, null); } break; } case Packet.RequestLoadLevel: { // Change the currently loaded level if (player.channel.host == player) { player.channel.Reset(); player.channel.level = reader.ReadString(); BinaryWriter writer = BeginSend(Packet.ResponseLoadLevel); writer.Write(string.IsNullOrEmpty(player.channel.level) ? "" : player.channel.level); EndSend(true, player.channel, null); } break; } case Packet.RequestSetHost: { // Transfer the host state from one player to another if (player.channel.host == player) { TcpPlayer newHost = GetPlayer(reader.ReadInt32()); if (newHost != null && newHost.channel == player.channel) SendSetHost(newHost); } break; } case Packet.RequestLeaveChannel: { SendLeaveChannel(player, true); break; } case Packet.RequestCloseChannel: { player.channel.persistent = false; player.channel.closed = true; break; } case Packet.RequestSetPlayerLimit: { player.channel.playerLimit = reader.ReadUInt16(); break; } case Packet.RequestRemoveRFC: { uint id = reader.ReadUInt32(); string funcName = ((id & 0xFF) == 0) ? reader.ReadString() : null; player.channel.DeleteRFC(id, funcName); break; } case Packet.RequestSetChannelData: { player.channel.data = reader.ReadString(); BinaryWriter writer = BeginSend(Packet.ResponseSetChannelData); writer.Write(player.channel.data); EndSend(true, player.channel, null); break; } } }
/// <summary> /// Leave the channel the player is in. /// </summary> void SendLeaveChannel(TcpPlayer player, bool notify) { Channel ch = player.channel; if (ch != null) { // Remove this player from the channel ch.RemovePlayer(player, mTemp); player.channel = null; // Are there other players left? if (ch.players.size > 0) { BinaryWriter writer; // Inform the other players that the player's objects should be destroyed if (mTemp.size > 0) { writer = BeginSend(Packet.ResponseDestroy); writer.Write((ushort)mTemp.size); for (int i = 0; i < mTemp.size; ++i) writer.Write(mTemp[i]); EndSend(true, ch, null); } // If this player was the host, choose a new host if (ch.host == null) SendSetHost(ch.players[0]); // Inform everyone of this player leaving the channel writer = BeginSend(Packet.ResponsePlayerLeft); writer.Write(player.id); EndSend(true, ch, null); } else if (!ch.persistent) { // No other players left -- delete this channel mChannels.Remove(ch); } // Notify the player that they have left the channel if (notify && player.isConnected) { BeginSend(Packet.ResponseLeaveChannel); EndSend(true, player); } } }
/// <summary> /// Receive and process a single incoming packet. /// Returns 'true' if a packet was received, 'false' otherwise. /// </summary> bool ProcessPlayerPacket(Buffer buffer, TcpPlayer player, bool reliable) { BinaryReader reader = buffer.BeginReading(); Packet request = (Packet)reader.ReadByte(); //#if UNITY_EDITOR // DEBUG // if (request != Packet.RequestPing) UnityEngine.Debug.Log("Server: " + request + " " + buffer.position + " " + buffer.size); //#else // if (request != Packet.RequestPing) Console.WriteLine("Server: " + request + " " + buffer.position + " " + buffer.size); //#endif // If the player has not yet been verified, the first packet must be an ID request if (player.stage == TcpProtocol.Stage.Verifying) { if (player.VerifyRequestID(request, reader, true)) { mDictionaryID.Add(player.id, player); if (lobbyLink != null) lobbyLink.SendUpdate(this); if (onPlayerConnect != null) onPlayerConnect(player); return true; } #if STANDALONE Console.WriteLine(player.address + " has failed the verification step"); #endif RemovePlayer(player); return false; } switch (request) { case Packet.Empty: { break; } case Packet.Error: { Error(player, reader.ReadString()); break; } case Packet.Disconnect: { RemovePlayer(player); break; } case Packet.RequestPing: { // Respond with a ping back BeginSend(Packet.ResponsePing); EndSend(true, player); break; } case Packet.RequestSetUDP: { int port = reader.ReadUInt16(); if (port != 0) { IPAddress ip = new IPAddress(player.tcpEndPoint.Address.GetAddressBytes()); SetPlayerUdpEndPoint(player, new IPEndPoint(ip, port)); } else SetPlayerUdpEndPoint(player, null); // Let the player know if we are hosting an active UDP connection ushort udp = mUdp.isActive ? (ushort)mUdp.listeningPort : (ushort)0; BeginSend(Packet.ResponseSetUDP).Write(udp); EndSend(true, player); // Send an empty packet to the target player to open up UDP for communication if (player.udpEndPoint != null) mUdp.SendEmptyPacket(player.udpEndPoint); break; } case Packet.RequestJoinChannel: { // Join the specified channel int channelID = reader.ReadInt32(); string pass = reader.ReadString(); string levelName = reader.ReadString(); bool persist = reader.ReadBoolean(); ushort playerLimit = reader.ReadUInt16(); // Join a random existing channel or create a new one if (channelID == -2) { bool randomLevel = string.IsNullOrEmpty(levelName); channelID = -1; for (int i = 0; i < mChannels.size; ++i) { Channel ch = mChannels[i]; if (ch.isOpen && (randomLevel || levelName.Equals(ch.level)) && (string.IsNullOrEmpty(ch.password) || (ch.password == pass))) { channelID = ch.id; break; } } // If no level name has been specified and no channels were found, we're done if (randomLevel && channelID == -1) { BinaryWriter writer = BeginSend(Packet.ResponseJoinChannel); writer.Write(false); writer.Write("No suitable channels found"); EndSend(true, player); break; } } // Join a random new channel if (channelID == -1) { channelID = mRandom.Next(100000000); for (int i = 0; i < 1000; ++i) { if (!ChannelExists(channelID)) break; channelID = mRandom.Next(100000000); } } if (player.channel == null || player.channel.id != channelID) { bool isNew; Channel channel = CreateChannel(channelID, out isNew); if (channel == null || !channel.isOpen) { BinaryWriter writer = BeginSend(Packet.ResponseJoinChannel); writer.Write(false); writer.Write("The requested channel is closed"); EndSend(true, player); } else if (isNew) { channel.password = pass; channel.persistent = persist; channel.level = levelName; channel.playerLimit = playerLimit; SendLeaveChannel(player, false); SendJoinChannel(player, channel); } else if (string.IsNullOrEmpty(channel.password) || (channel.password == pass)) { SendLeaveChannel(player, false); SendJoinChannel(player, channel); } else { BinaryWriter writer = BeginSend(Packet.ResponseJoinChannel); writer.Write(false); writer.Write("Wrong password"); EndSend(true, player); } } break; } case Packet.RequestSetName: { // Change the player's name player.name = reader.ReadString(); BinaryWriter writer = BeginSend(Packet.ResponseRenamePlayer); writer.Write(player.id); writer.Write(player.name); if (player.channel != null) { EndSend(true, player.channel, null); } else { EndSend(true, player); } break; } case Packet.RequestSaveFile: { string fileName = reader.ReadString(); byte[] data = reader.ReadBytes(reader.ReadInt32()); SaveFile(fileName, data); break; } case Packet.RequestLoadFile: { string fn = reader.ReadString(); byte[] data = LoadFile(fn); BinaryWriter writer = BeginSend(Packet.ResponseLoadFile); writer.Write(fn); if (data != null) { writer.Write(data.Length); writer.Write(data); } else { writer.Write(0); } EndSend(true, player); break; } case Packet.RequestDeleteFile: { DeleteFile(reader.ReadString()); break; } case Packet.RequestNoDelay: { player.noDelay = reader.ReadBoolean(); break; } case Packet.RequestChannelList: { BinaryWriter writer = BeginSend(Packet.ResponseChannelList); int count = 0; for (int i = 0; i < mChannels.size; ++i) if (!mChannels[i].closed) ++count; writer.Write(count); for (int i = 0; i < mChannels.size; ++i) { Channel ch = mChannels[i]; if (!ch.closed) { writer.Write(ch.id); writer.Write((ushort)ch.players.size); writer.Write(ch.playerLimit); writer.Write(!string.IsNullOrEmpty(ch.password)); writer.Write(ch.persistent); writer.Write(ch.level); writer.Write(ch.data); } } EndSend(true, player); break; } case Packet.RequestSetTimeout: { // The passed value is in seconds, but the stored value is in milliseconds (to avoid a math operation) player.timeoutTime = reader.ReadInt32() * 1000; break; } case Packet.ForwardToPlayer: { // Forward this packet to the specified player TcpPlayer target = GetPlayer(reader.ReadInt32()); if (target != null && target.isConnected) { // Reset the position back to the beginning (4 bytes for size, 1 byte for ID, 4 bytes for player) buffer.position = buffer.position - 9; target.SendTcpPacket(buffer); } break; } case Packet.Broadcast: { // 4 bytes for size, 1 byte for ID buffer.position = buffer.position - 5; // Forward the packet to everyone connected to the server for (int i = 0; i < mPlayers.size; ++i) { TcpPlayer tp = mPlayers[i]; if (reliable || tp.udpEndPoint == null || !mAllowUdp) { tp.SendTcpPacket(buffer); } else mUdp.Send(buffer, tp.udpEndPoint); } break; } default: { if (player.channel != null && (int)request <= (int)Packet.ForwardToPlayerBuffered) { // Other packets can only be processed while in a channel if ((int)request >= (int)Packet.ForwardToAll) { ProcessForwardPacket(player, buffer, reader, request, reliable); } else { ProcessChannelPacket(player, buffer, reader, request); } } else if (onCustomPacket != null) { onCustomPacket(player, buffer, reader, request, reliable); } 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> /// Remove the specified player from the channel. /// </summary> public void RemovePlayer(TcpPlayer p, List<uint> destroyedObjects) { destroyedObjects.Clear(); if (p == host) host = null; if (players.Remove(p)) { // Remove all of the non-persistent objects that were created by this player for (int i = created.size; i > 0; ) { Channel.CreatedObject obj = created[--i]; if (obj.type == 2 && obj.playerID == p.id) { if (obj.buffer != null) obj.buffer.Recycle(); created.RemoveAt(i); destroyedObjects.Add(obj.uniqueID); DestroyObjectRFCs(obj.uniqueID); } } // Close the channel if it wasn't persistent if ((!persistent || playerLimit < 1) && players.size == 0) { closed = true; for (int i = 0; i < rfcs.size; ++i) { RFC r = rfcs[i]; if (r.buffer != null) r.buffer.Recycle(); } rfcs.Clear(); } } }
/// <summary> /// Change the player's UDP end point and update the local dictionary. /// </summary> void SetPlayerUdpEndPoint(TcpPlayer player, IPEndPoint udp) { if (player.udpEndPoint != null) mDictionaryEP.Remove(player.udpEndPoint); player.udpEndPoint = udp; if (udp != null) mDictionaryEP[udp] = player; }
/// <summary> /// Add a new player entry. /// </summary> TcpPlayer AddPlayer(Socket socket) { TcpPlayer player = new TcpPlayer(); player.StartReceiving(socket); mPlayers.Add(player); return player; }
internal void NotifyPlayerDisconnect(TcpPlayer player) => livePlayers.Remove(player.id.ToString());
/// <summary> /// Thread that will be processing incoming data. /// </summary> void ThreadFunction() { for (; ;) { Buffer buffer; bool received = false; mTime = DateTime.Now.Ticks / 10000; IPEndPoint ip; // Stop the listener if the port is 0 (MakePrivate() was called) if (mListenerPort == 0) { if (mListener != null) { mListener.Stop(); mListener = null; if (lobbyLink != null) { lobbyLink.Stop(); } if (onShutdown != null) { onShutdown(); } } } else { // Add all pending connections while (mListener != null && mListener.Pending()) { #if STANDALONE TcpPlayer p = AddPlayer(mListener.AcceptSocket()); Console.WriteLine(p.address + " has connected"); #else AddPlayer(mListener.AcceptSocket()); #endif } } // Process datagrams first while (mUdp.listeningPort != 0 && mUdp.ReceivePacket(out buffer, out ip)) { if (buffer.size > 0) { TcpPlayer player = GetPlayer(ip); if (player != null) { try { if (ProcessPlayerPacket(buffer, player, false)) { received = true; } } catch (System.Exception) { RemovePlayer(player); } } } buffer.Recycle(); } // Process player connections next for (int i = 0; i < mPlayers.size;) { TcpPlayer player = mPlayers[i]; // Process up to 100 packets at a time for (int b = 0; b < 100 && player.ReceivePacket(out buffer); ++b) { if (buffer.size > 0) { try { if (ProcessPlayerPacket(buffer, player, true)) { received = true; } } #if STANDALONE catch (System.Exception ex) { Console.WriteLine("ERROR (ProcessPlayerPacket): " + ex.Message); RemovePlayer(player); } #else catch (System.Exception) { RemovePlayer(player); } #endif } buffer.Recycle(); } // Time out -- disconnect this player if (player.stage == TcpProtocol.Stage.Connected) { // Up to 10 seconds can go without a single packet before the player is removed if (player.lastReceivedTime + 10000 < mTime) { #if STANDALONE Console.WriteLine(player.address + " has timed out"); #endif RemovePlayer(player); continue; } } else if (player.lastReceivedTime + 2000 < mTime) { #if STANDALONE Console.WriteLine(player.address + " has timed out"); #endif RemovePlayer(player); continue; } ++i; } if (!received) { Thread.Sleep(1); } } }
/// <summary> /// Process a packet from the player. /// </summary> void ProcessChannelPacket(TcpPlayer player, Buffer buffer, BinaryReader reader, Packet request) { switch (request) { case Packet.RequestCreate: { // Create a new object ushort objectIndex = reader.ReadUInt16(); byte type = reader.ReadByte(); uint uniqueID = 0; if (type != 0) { uniqueID = --player.channel.objectCounter; // 1-32767 is reserved for existing scene objects. // 32768 - 16777215 is for dynamically created objects. if (uniqueID < 32768) { player.channel.objectCounter = 0xFFFFFF; uniqueID = 0xFFFFFF; } Channel.CreatedObject obj = new Channel.CreatedObject(); obj.playerID = player.id; obj.objectID = objectIndex; obj.uniqueID = uniqueID; obj.type = type; if (buffer.size > 0) { obj.buffer = buffer; buffer.MarkAsUsed(); } player.channel.created.Add(obj); } // Inform the channel BinaryWriter writer = BeginSend(Packet.ResponseCreate); writer.Write(player.id); writer.Write(objectIndex); writer.Write(uniqueID); if (buffer.size > 0) { writer.Write(buffer.buffer, buffer.position, buffer.size); } EndSend(true, player.channel, null); break; } case Packet.RequestDestroy: { // Destroy the specified network object uint uniqueID = reader.ReadUInt32(); // Remove this object if (player.channel.DestroyObject(uniqueID)) { // Inform all players in the channel that the object should be destroyed BinaryWriter writer = BeginSend(Packet.ResponseDestroy); writer.Write((ushort)1); writer.Write(uniqueID); EndSend(true, player.channel, null); } break; } case Packet.RequestLoadLevel: { // Change the currently loaded level if (player.channel.host == player) { player.channel.Reset(); player.channel.level = reader.ReadString(); BinaryWriter writer = BeginSend(Packet.ResponseLoadLevel); writer.Write(string.IsNullOrEmpty(player.channel.level) ? "" : player.channel.level); EndSend(true, player.channel, null); } break; } case Packet.RequestSetHost: { // Transfer the host state from one player to another if (player.channel.host == player) { TcpPlayer newHost = GetPlayer(reader.ReadInt32()); if (newHost != null && newHost.channel == player.channel) { SendSetHost(newHost); } } break; } case Packet.RequestLeaveChannel: { SendLeaveChannel(player, true); break; } case Packet.RequestCloseChannel: { player.channel.persistent = false; player.channel.closed = true; break; } case Packet.RequestSetPlayerLimit: { player.channel.playerLimit = reader.ReadUInt16(); break; } case Packet.RequestRemoveRFC: { uint id = reader.ReadUInt32(); string funcName = ((id & 0xFF) == 0) ? reader.ReadString() : null; player.channel.DeleteRFC(id, funcName); break; } case Packet.RequestSetChannelData: { player.channel.data = reader.ReadString(); BinaryWriter writer = BeginSend(Packet.ResponseSetChannelData); writer.Write(player.channel.data); EndSend(true, player.channel, null); break; } } }
/// <summary> /// Process a packet that's meant to be forwarded. /// </summary> void ProcessForwardPacket(TcpPlayer player, Buffer buffer, BinaryReader reader, Packet request, bool reliable) { // We can't send unreliable packets if UDP is not active if (!mUdp.isActive || buffer.size > 1024) { reliable = true; } switch (request) { case Packet.ForwardToHost: { // Reset the position back to the beginning (4 bytes for size, 1 byte for ID) buffer.position = buffer.position - 5; // Forward the packet to the channel's host if (reliable || player.channel.host.udpEndPoint == null || !mAllowUdp) { player.channel.host.SendTcpPacket(buffer); } else { mUdp.Send(buffer, player.channel.host.udpEndPoint); } break; } case Packet.ForwardToPlayerBuffered: { // 4 bytes for size, 1 byte for ID int origin = buffer.position - 5; // Figure out who the intended recipient is TcpPlayer targetPlayer = GetPlayer(reader.ReadInt32()); // Save this function call uint target = reader.ReadUInt32(); string funcName = ((target & 0xFF) == 0) ? reader.ReadString() : null; buffer.position = origin; player.channel.CreateRFC(target, funcName, buffer); // Forward the packet to the target player if (targetPlayer != null && targetPlayer.isConnected) { if (reliable || targetPlayer.udpEndPoint == null || !mAllowUdp) { targetPlayer.SendTcpPacket(buffer); } else { mUdp.Send(buffer, targetPlayer.udpEndPoint); } } break; } default: { // We want to exclude the player if the request was to forward to others TcpPlayer exclude = ( request == Packet.ForwardToOthers || request == Packet.ForwardToOthersSaved) ? player : null; // 4 bytes for size, 1 byte for ID int origin = buffer.position - 5; // If the request should be saved, let's do so if (request == Packet.ForwardToAllSaved || request == Packet.ForwardToOthersSaved) { uint target = reader.ReadUInt32(); string funcName = ((target & 0xFF) == 0) ? reader.ReadString() : null; buffer.position = origin; player.channel.CreateRFC(target, funcName, buffer); } else { buffer.position = origin; } // Forward the packet to everyone except the sender for (int i = 0; i < player.channel.players.size; ++i) { TcpPlayer tp = player.channel.players[i]; if (tp != exclude) { if (reliable || tp.udpEndPoint == null || !mAllowUdp) { tp.SendTcpPacket(buffer); } else { mUdp.Send(buffer, tp.udpEndPoint); } } } break; } } }
/// <summary> /// Receive and process a single incoming packet. /// Returns 'true' if a packet was received, 'false' otherwise. /// </summary> bool ProcessPlayerPacket(Buffer buffer, TcpPlayer player, bool reliable) { BinaryReader reader = buffer.BeginReading(); Packet request = (Packet)reader.ReadByte(); //#if UNITY_EDITOR // DEBUG // if (request != Packet.RequestPing) UnityEngine.Debug.Log("Server: " + request + " " + buffer.position + " " + buffer.size); //#else // if (request != Packet.RequestPing) Console.WriteLine("Server: " + request + " " + buffer.position + " " + buffer.size); //#endif // If the player has not yet been verified, the first packet must be an ID request if (player.stage == TcpProtocol.Stage.Verifying) { if (player.VerifyRequestID(request, reader, true)) { mDictionaryID.Add(player.id, player); if (lobbyLink != null) { lobbyLink.SendUpdate(this); } if (onPlayerConnect != null) { onPlayerConnect(player); } return(true); } #if STANDALONE Console.WriteLine(player.address + " has failed the verification step"); #endif RemovePlayer(player); return(false); } switch (request) { case Packet.Empty: { break; } case Packet.Error: { Error(player, reader.ReadString()); break; } case Packet.Disconnect: { RemovePlayer(player); break; } case Packet.RequestPing: { // Respond with a ping back BeginSend(Packet.ResponsePing); EndSend(true, player); break; } case Packet.RequestSetUDP: { int port = reader.ReadUInt16(); if (port != 0) { IPAddress ip = new IPAddress(player.tcpEndPoint.Address.GetAddressBytes()); SetPlayerUdpEndPoint(player, new IPEndPoint(ip, port)); } else { SetPlayerUdpEndPoint(player, null); } // Let the player know if we are hosting an active UDP connection ushort udp = mUdp.isActive ? (ushort)mUdp.listeningPort : (ushort)0; BeginSend(Packet.ResponseSetUDP).Write(udp); EndSend(true, player); // Send an empty packet to the target player to open up UDP for communication if (player.udpEndPoint != null) { mUdp.SendEmptyPacket(player.udpEndPoint); } break; } case Packet.RequestJoinChannel: { // Join the specified channel int channelID = reader.ReadInt32(); string pass = reader.ReadString(); string levelName = reader.ReadString(); bool persist = reader.ReadBoolean(); ushort playerLimit = reader.ReadUInt16(); // Join a random existing channel or create a new one if (channelID == -2) { bool randomLevel = string.IsNullOrEmpty(levelName); channelID = -1; for (int i = 0; i < mChannels.size; ++i) { Channel ch = mChannels[i]; if (ch.isOpen && (randomLevel || levelName.Equals(ch.level)) && (string.IsNullOrEmpty(ch.password) || (ch.password == pass))) { channelID = ch.id; break; } } // If no level name has been specified and no channels were found, we're done if (randomLevel && channelID == -1) { BinaryWriter writer = BeginSend(Packet.ResponseJoinChannel); writer.Write(false); writer.Write("No suitable channels found"); EndSend(true, player); break; } } // Join a random new channel if (channelID == -1) { channelID = mRandom.Next(100000000); for (int i = 0; i < 1000; ++i) { if (!ChannelExists(channelID)) { break; } channelID = mRandom.Next(100000000); } } if (player.channel == null || player.channel.id != channelID) { bool isNew; Channel channel = CreateChannel(channelID, out isNew); if (channel == null || !channel.isOpen) { BinaryWriter writer = BeginSend(Packet.ResponseJoinChannel); writer.Write(false); writer.Write("The requested channel is closed"); EndSend(true, player); } else if (isNew) { channel.password = pass; channel.persistent = persist; channel.level = levelName; channel.playerLimit = playerLimit; SendLeaveChannel(player, false); SendJoinChannel(player, channel); } else if (string.IsNullOrEmpty(channel.password) || (channel.password == pass)) { SendLeaveChannel(player, false); SendJoinChannel(player, channel); } else { BinaryWriter writer = BeginSend(Packet.ResponseJoinChannel); writer.Write(false); writer.Write("Wrong password"); EndSend(true, player); } } break; } case Packet.RequestSetName: { // Change the player's name player.name = reader.ReadString(); BinaryWriter writer = BeginSend(Packet.ResponseRenamePlayer); writer.Write(player.id); writer.Write(player.name); if (player.channel != null) { EndSend(true, player.channel, null); } else { EndSend(true, player); } break; } case Packet.RequestSaveFile: { string fileName = reader.ReadString(); byte[] data = reader.ReadBytes(reader.ReadInt32()); SaveFile(fileName, data); break; } case Packet.RequestLoadFile: { string fn = reader.ReadString(); byte[] data = LoadFile(fn); BinaryWriter writer = BeginSend(Packet.ResponseLoadFile); writer.Write(fn); if (data != null) { writer.Write(data.Length); writer.Write(data); } else { writer.Write(0); } EndSend(true, player); break; } case Packet.RequestDeleteFile: { DeleteFile(reader.ReadString()); break; } case Packet.RequestNoDelay: { player.noDelay = reader.ReadBoolean(); break; } case Packet.RequestChannelList: { BinaryWriter writer = BeginSend(Packet.ResponseChannelList); int count = 0; for (int i = 0; i < mChannels.size; ++i) { if (!mChannels[i].closed) { ++count; } } writer.Write(count); for (int i = 0; i < mChannels.size; ++i) { Channel ch = mChannels[i]; if (!ch.closed) { writer.Write(ch.id); writer.Write((ushort)ch.players.size); writer.Write(ch.playerLimit); writer.Write(!string.IsNullOrEmpty(ch.password)); writer.Write(ch.persistent); writer.Write(ch.level); writer.Write(ch.data); } } EndSend(true, player); break; } case Packet.ForwardToPlayer: { // Forward this packet to the specified player TcpPlayer target = GetPlayer(reader.ReadInt32()); if (target != null && target.isConnected) { // Reset the position back to the beginning (4 bytes for size, 1 byte for ID, 4 bytes for player) buffer.position = buffer.position - 9; target.SendTcpPacket(buffer); } break; } default: { if (player.channel != null && (int)request <= (int)Packet.ForwardToPlayerBuffered) { // Other packets can only be processed while in a channel if ((int)request >= (int)Packet.ForwardToAll) { ProcessForwardPacket(player, buffer, reader, request, reliable); } else { ProcessChannelPacket(player, buffer, reader, request); } } else if (onCustomPacket != null) { onCustomPacket(player, buffer, reader, request, reliable); } 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); } } // 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> /// Remove the specified player. /// </summary> void RemovePlayer(TcpPlayer p) { if (p != null) { SendLeaveChannel(p, false); #if STANDALONE Console.WriteLine(p.address + " has disconnected"); #endif p.Release(); mPlayers.Remove(p); if (p.udpEndPoint != null) { mDictionaryEP.Remove(p.udpEndPoint); p.udpEndPoint = null; } if (p.id != 0) { if (mDictionaryID.Remove(p.id)) { if (lobbyLink != null) lobbyLink.SendUpdate(this); if (onPlayerDisconnect != null) onPlayerDisconnect(p); } p.id = 0; } } }
/// <summary> /// Send the outgoing buffer to all players in the specified channel. /// </summary> void EndSend(bool reliable, Channel channel, TcpPlayer exclude) { mBuffer.EndPacket(); if (mBuffer.size > 1024) reliable = true; for (int i = 0; i < channel.players.size; ++i) { TcpPlayer player = channel.players[i]; if (player.stage == TcpProtocol.Stage.Connected && player != exclude) { if (reliable || player.udpEndPoint == null || !mAllowUdp) { player.SendTcpPacket(mBuffer); } else mUdp.Send(mBuffer, player.udpEndPoint); } } mBuffer.Recycle(); mBuffer = null; }
/// <summary> /// Process a packet that's meant to be forwarded. /// </summary> void ProcessForwardPacket(TcpPlayer player, Buffer buffer, BinaryReader reader, Packet request, bool reliable) { // We can't send unreliable packets if UDP is not active if (!mUdp.isActive || buffer.size > 1024) reliable = true; switch (request) { case Packet.ForwardToHost: { // Reset the position back to the beginning (4 bytes for size, 1 byte for ID) buffer.position = buffer.position - 5; // Forward the packet to the channel's host if (reliable || player.channel.host.udpEndPoint == null || !mAllowUdp) { player.channel.host.SendTcpPacket(buffer); } else mUdp.Send(buffer, player.channel.host.udpEndPoint); break; } case Packet.ForwardToPlayerBuffered: { // 4 bytes for size, 1 byte for ID int origin = buffer.position - 5; // Figure out who the intended recipient is TcpPlayer targetPlayer = GetPlayer(reader.ReadInt32()); // Save this function call uint target = reader.ReadUInt32(); string funcName = ((target & 0xFF) == 0) ? reader.ReadString() : null; buffer.position = origin; player.channel.CreateRFC(target, funcName, buffer); // Forward the packet to the target player if (targetPlayer != null && targetPlayer.isConnected) { if (reliable || targetPlayer.udpEndPoint == null || !mAllowUdp) { targetPlayer.SendTcpPacket(buffer); } else mUdp.Send(buffer, targetPlayer.udpEndPoint); } break; } default: { // We want to exclude the player if the request was to forward to others TcpPlayer exclude = ( request == Packet.ForwardToOthers || request == Packet.ForwardToOthersSaved) ? player : null; // 4 bytes for size, 1 byte for ID int origin = buffer.position - 5; // If the request should be saved, let's do so if (request == Packet.ForwardToAllSaved || request == Packet.ForwardToOthersSaved) { uint target = reader.ReadUInt32(); string funcName = ((target & 0xFF) == 0) ? reader.ReadString() : null; buffer.position = origin; player.channel.CreateRFC(target, funcName, buffer); } else buffer.position = origin; // Forward the packet to everyone except the sender for (int i = 0; i < player.channel.players.size; ++i) { TcpPlayer tp = player.channel.players[i]; if (tp != exclude) { if (reliable || tp.udpEndPoint == null || !mAllowUdp) { tp.SendTcpPacket(buffer); } else mUdp.Send(buffer, tp.udpEndPoint); } } break; } } }
internal void NotifyPlayerConnect(TcpPlayer player) { var id = player.id.ToString(); NotifyPlayerJoin(id, player.name); livePlayers[id] = new NomadLivePlayer(player); }
/// <summary> /// Send the outgoing buffer to the specified player. /// </summary> void EndSend(bool reliable, TcpPlayer player) { mBuffer.EndPacket(); if (mBuffer.size > 1024) reliable = true; if (reliable || player.udpEndPoint == null || !mAllowUdp) { player.SendTcpPacket(mBuffer); } else mUdp.Send(mBuffer, player.udpEndPoint); mBuffer.Recycle(); mBuffer = null; }