using System; using System.Threading; using System.Collections.Generic; using System.Net; namespace Lidgren.Network { /// /// Represents a local peer capable of holding zero, one or more connections to remote peers /// public partial class NetPeer { private static int s_initializedPeersCount; private int m_listenPort; private object m_tag; private object m_messageReceivedEventCreationLock = new object(); internal readonly List m_connections; private readonly Dictionary m_connectionLookup; private string m_shutdownReason; /// /// Gets the NetPeerStatus of the NetPeer /// public NetPeerStatus Status { get { return m_status; } } /// /// Signalling event which can be waited on to determine when a message is queued for reading. /// Note that there is no guarantee that after the event is signaled the blocked thread will /// find the message in the queue. Other user created threads could be preempted and dequeue /// the message before the waiting thread wakes up. /// public AutoResetEvent MessageReceivedEvent { get { if (m_messageReceivedEvent == null) { lock (m_messageReceivedEventCreationLock) // make sure we don't create more than one event object { if (m_messageReceivedEvent == null) m_messageReceivedEvent = new AutoResetEvent(false); } } return m_messageReceivedEvent; } } /// /// Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start() has been called! /// public long UniqueIdentifier { get { return m_uniqueIdentifier; } } /// /// Gets the port number this NetPeer is listening and sending on, if Start() has been called /// public int Port { get { return m_listenPort; } } /// /// Returns an UPnP object if enabled in the NetPeerConfiguration /// public NetUPnP UPnP { get { return m_upnp; } } /// /// Gets or sets the application defined object containing data about the peer /// public object Tag { get { return m_tag; } set { m_tag = value; } } /// /// Gets a copy of the list of connections /// public List Connections { get { lock (m_connections) return new List(m_connections); } } /// /// Gets the number of active connections /// public int ConnectionsCount { get { return m_connections.Count; } } /// /// Statistics on this NetPeer since it was initialized /// public NetPeerStatistics Statistics { get { return m_statistics; } } /// /// Gets the configuration used to instanciate this NetPeer /// public NetPeerConfiguration Configuration { get { return m_configuration; } } /// /// NetPeer constructor /// public NetPeer(NetPeerConfiguration config) { m_configuration = config; m_statistics = new NetPeerStatistics(this); m_releasedIncomingMessages = new NetQueue(4); m_unsentUnconnectedMessages = new NetQueue>(2); m_connections = new List(); m_connectionLookup = new Dictionary(); m_handshakes = new Dictionary(); m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0); m_status = NetPeerStatus.NotRunning; m_receivedFragmentGroups = new Dictionary>(); } /// /// Binds to socket and spawns the networking thread /// public void Start() { if (m_status != NetPeerStatus.NotRunning) { // already running! Just ignore... LogWarning("Start() called on already running NetPeer - ignoring."); return; } m_status = NetPeerStatus.Starting; // fix network thread name if (m_configuration.NetworkThreadName == "Lidgren network thread") { int pc = Interlocked.Increment(ref s_initializedPeersCount); m_configuration.NetworkThreadName = "Lidgren network thread " + pc.ToString(); } InitializeNetwork(); // start network thread m_networkThread = new Thread(new ThreadStart(NetworkLoop)); m_networkThread.Name = m_configuration.NetworkThreadName; m_networkThread.IsBackground = true; m_networkThread.Start(); // send upnp discovery if (m_upnp != null) m_upnp.Discover(this); // allow some time for network thread to start up in case they call Connect() or UPnP calls immediately Thread.Sleep(50); } /// /// Get the connection, if any, for a certain remote endpoint /// public NetConnection GetConnection(IPEndPoint ep) { NetConnection retval; // this should not pose a threading problem, m_connectionLookup is never added to concurrently // and TryGetValue will not throw an exception on fail, only yield null, which is acceptable m_connectionLookup.TryGetValue(ep, out retval); return retval; } /// /// Read a pending message from any connection, blocking up to maxMillis if needed /// public NetIncomingMessage WaitMessage(int maxMillis) { var msg = ReadMessage(); if (msg != null) return msg; // no need to wait; we already have a message to deliver var msgEvt = MessageReceivedEvent; msgEvt.WaitOne(maxMillis); return ReadMessage(); } /// /// Read a pending message from any connection, if any /// public NetIncomingMessage ReadMessage() { NetIncomingMessage retval; if (m_releasedIncomingMessages.TryDequeue(out retval)) { if (retval.MessageType == NetIncomingMessageType.StatusChanged) { NetConnectionStatus status = (NetConnectionStatus)retval.PeekByte(); retval.SenderConnection.m_visibleStatus = status; } } return retval; } /// /// Read a pending message from any connection, if any /// public int ReadMessages(IList addTo) { int added = m_releasedIncomingMessages.TryDrain(addTo); if (added > 0) { for (int i = 0; i < added; i++) { var index = addTo.Count - added + i; var nim = addTo[index]; if (nim.MessageType == NetIncomingMessageType.StatusChanged) { NetConnectionStatus status = (NetConnectionStatus)nim.PeekByte(); nim.SenderConnection.m_visibleStatus = status; } } } return added; } // send message immediately internal void SendLibrary(NetOutgoingMessage msg, IPEndPoint recipient) { VerifyNetworkThread(); NetException.Assert(msg.m_isSent == false); bool connReset; int len = msg.Encode(m_sendBuffer, 0, 0); SendPacket(len, recipient, 1, out connReset); } /// /// Create a connection to a remote endpoint /// public NetConnection Connect(string host, int port) { return Connect(new IPEndPoint(NetUtility.Resolve(host), port), null); } /// /// Create a connection to a remote endpoint /// public NetConnection Connect(string host, int port, NetOutgoingMessage hailMessage) { return Connect(new IPEndPoint(NetUtility.Resolve(host), port), hailMessage); } /// /// Create a connection to a remote endpoint /// public NetConnection Connect(IPEndPoint remoteEndPoint) { return Connect(remoteEndPoint, null); } /// /// Create a connection to a remote endpoint /// public virtual NetConnection Connect(IPEndPoint remoteEndPoint, NetOutgoingMessage hailMessage) { if (remoteEndPoint == null) throw new ArgumentNullException("remoteEndPoint"); lock (m_connections) { if (m_status == NetPeerStatus.NotRunning) throw new NetException("Must call Start() first"); if (m_connectionLookup.ContainsKey(remoteEndPoint)) throw new NetException("Already connected to that endpoint!"); NetConnection hs; if (m_handshakes.TryGetValue(remoteEndPoint, out hs)) { // already trying to connect to that endpoint; make another try switch (hs.m_status) { case NetConnectionStatus.InitiatedConnect: // send another connect hs.m_connectRequested = true; break; case NetConnectionStatus.RespondedConnect: // send another response hs.SendConnectResponse((float)NetTime.Now, false); break; default: // weird LogWarning("Weird situation; Connect() already in progress to remote endpoint; but hs status is " + hs.m_status); break; } return hs; } NetConnection conn = new NetConnection(this, remoteEndPoint); conn.m_status = NetConnectionStatus.InitiatedConnect; conn.m_localHailMessage = hailMessage; // handle on network thread conn.m_connectRequested = true; conn.m_connectionInitiator = true; m_handshakes.Add(remoteEndPoint, conn); return conn; } } /// /// Send raw bytes; only used for debugging /// #if DEBUG public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination) #else public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination) #endif { // wrong thread - this miiiight crash with network thread... but what's a boy to do. Array.Copy(arr, offset, m_sendBuffer, 0, length); bool unused; SendPacket(length, destination, 1, out unused); } /// /// In DEBUG, throws an exception, in RELEASE logs an error message /// /// internal void ThrowOrLog(string message) { #if DEBUG throw new NetException(message); #else LogError(message); #endif } /// /// Disconnects all active connections and closes the socket /// public void Shutdown(string bye) { // called on user thread if (m_socket == null) return; // already shut down LogDebug("Shutdown requested"); m_shutdownReason = bye; m_status = NetPeerStatus.ShutdownRequested; } } }