/src/Mooege/Net/Server.cs
C# | 314 lines | 218 code | 62 blank | 34 comment | 39 complexity | 76e273412ee9c1c4686128ce6f08af67 MD5 | raw file
Possible License(s): BSD-3-Clause, CC-BY-SA-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-2.0
- /*
- * Copyright (C) 2011 mooege project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using Mooege.Common.Extensions;
- using Mooege.Common.Logging;
- namespace Mooege.Net
- {
- public class Server : IDisposable
- {
- public bool IsListening { get; private set; }
- public int Port { get; private set; }
- protected Socket Listener;
- protected Dictionary<Socket, IConnection> Connections = new Dictionary<Socket, IConnection>();
- protected object ConnectionLock = new object();
- public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);
- public delegate void ConnectionDataEventHandler(object sender, ConnectionDataEventArgs e);
- public event ConnectionEventHandler OnConnect;
- public event ConnectionEventHandler OnDisconnect;
- public event ConnectionDataEventHandler DataReceived;
- public event ConnectionDataEventHandler DataSent;
- protected static readonly Logger Logger = LogManager.CreateLogger();
- private bool _disposed;
- public virtual void Run() { }
- #region listener
- public virtual bool Listen(string bindIP, int port)
- {
- // Check if the server has been disposed.
- if (_disposed) throw new ObjectDisposedException(this.GetType().Name, "Server has been disposed.");
- // Check if the server is already listening.
- if (IsListening) throw new InvalidOperationException("Server is already listening.");
- // Create new TCP socket and set socket options.
- Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- // Setup our options:
- // * NoDelay - don't use packet coalescing
- // * DontLinger - don't keep sockets around once they've been disconnected
- Listener.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
- Listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
- try
- {
- // Bind.
- Listener.Bind(new IPEndPoint(IPAddress.Parse(bindIP), port));
- this.Port = port;
- }
- catch (SocketException)
- {
- Logger.Fatal(string.Format("{0} can't bind on {1}, server shutting down..", this.GetType().Name, bindIP));
- this.Shutdown();
- return false;
- }
- // Start listening for incoming connections.
- Listener.Listen(10);
- IsListening = true;
- // Begin accepting any incoming connections asynchronously.
- Listener.BeginAccept(AcceptCallback, null);
- return true;
- }
- private void AcceptCallback(IAsyncResult result)
- {
- if (Listener == null) return;
- try
- {
- var socket = Listener.EndAccept(result); // Finish accepting the incoming connection.
- var connection = new Connection(this, socket); // Add the new connection to the dictionary.
- lock (ConnectionLock) Connections[socket] = connection; // add the connection to list.
- OnClientConnection(new ConnectionEventArgs(connection)); // Raise the OnConnect event.
- connection.BeginReceive(ReceiveCallback, connection); // Begin receiving on the new connection connection.
- Listener.BeginAccept(AcceptCallback, null); // Continue receiving other incoming connection asynchronously.
- }
- catch (NullReferenceException) { } // we recive this after issuing server-shutdown, just ignore it.
- catch (Exception e)
- {
- Logger.DebugException(e, "AcceptCallback");
- }
- }
- private void ReceiveCallback(IAsyncResult result)
- {
- var connection = result.AsyncState as Connection; // Get the connection connection passed to the callback.
- if (connection == null) return;
- try
- {
- var bytesRecv = connection.EndReceive(result); // Finish receiving data from the socket.
- if (bytesRecv > 0)
- {
- OnDataReceived(new ConnectionDataEventArgs(connection, connection.RecvBuffer.Enumerate(0, bytesRecv))); // Raise the DataReceived event.
- if (connection.IsConnected) connection.BeginReceive(ReceiveCallback, connection); // Begin receiving again on the socket, if it is connected.
- else RemoveConnection(connection, true); // else remove it from connection list.
- }
- else RemoveConnection(connection, true); // Connection was lost.
- }
- catch (SocketException)
- {
- RemoveConnection(connection, true); // An error occured while receiving, connection has disconnected.
- }
- catch (Exception e)
- {
- Logger.DebugException(e, "ReceiveCallback");
- }
- }
- public virtual int Send(Connection connection, IEnumerable<byte> data, SocketFlags flags)
- {
- if (connection == null) throw new ArgumentNullException("connection");
- if (data == null) throw new ArgumentNullException("data");
- var buffer = data.ToArray();
- return Send(connection, buffer, 0, buffer.Length, SocketFlags.None);
- }
- public virtual int Send(Connection connection, byte[] buffer, int start, int count, SocketFlags flags)
- {
- if (connection == null) throw new ArgumentNullException("connection");
- if (buffer == null) throw new ArgumentNullException("buffer");
- var totalBytesSent = 0;
- var bytesRemaining = buffer.Length;
- try
- {
- while (bytesRemaining > 0) // Ensure we send every byte.
- {
- var bytesSent = connection.Socket.Send(buffer, totalBytesSent, bytesRemaining, flags); // Send the remaining data.
- if (bytesSent > 0)
- OnDataSent(new ConnectionDataEventArgs(connection, buffer.Enumerate(totalBytesSent, bytesSent))); // Raise the Data Sent event.
- // Decrement bytes remaining and increment bytes sent.
- bytesRemaining -= bytesSent;
- totalBytesSent += bytesSent;
- }
- }
- catch (SocketException)
- {
- RemoveConnection(connection, true); // An error occured while sending, connection has disconnected.
- }
- catch (Exception e)
- {
- Logger.DebugException(e, "Send");
- }
- return totalBytesSent;
- }
- #endregion
- #region service methods
- public IEnumerable<IConnection> GetConnections()
- {
- lock (ConnectionLock)
- foreach (IConnection connection in Connections.Values)
- yield return connection;
- }
- #endregion
- #region events
- protected virtual void OnClientConnection(ConnectionEventArgs e)
- {
- var handler = OnConnect;
- if (handler != null) handler(this, e);
- }
- protected virtual void OnClientDisconnect(ConnectionEventArgs e)
- {
- var handler = OnDisconnect;
- if (handler != null) handler(this, e);
- }
- protected virtual void OnDataReceived(ConnectionDataEventArgs e)
- {
- var handler = DataReceived;
- if (handler != null) handler(this, e);
- }
- protected virtual void OnDataSent(ConnectionDataEventArgs e)
- {
- var handler = DataSent;
- if (handler != null) handler(this, e);
- }
- #endregion
- #region disconnect & shutdown handlers
- public virtual void DisconnectAll()
- {
- lock (ConnectionLock)
- {
- foreach (var connection in Connections.Values.Cast<Connection>().Where(conn => conn.IsConnected)) // Check if the connection is connected.
- {
- // Disconnect and raise the OnDisconnect event.
- connection.Socket.Disconnect(false);
- OnClientDisconnect(new ConnectionEventArgs(connection));
- }
- Connections.Clear();
- }
- }
- public virtual void Disconnect(Connection connection)
- {
- if (connection == null) throw new ArgumentNullException("connection");
- if (!connection.IsConnected) return;
- connection.Socket.Disconnect(false);
- RemoveConnection(connection, true);
- }
- private void RemoveConnection(Connection connection, bool raiseEvent)
- {
- // Remove the connection from the dictionary and raise the OnDisconnection event.
- lock (ConnectionLock)
- if (Connections.Remove(connection.Socket) && raiseEvent)
- OnClientDisconnect(new ConnectionEventArgs(connection));
- }
- public virtual void Shutdown()
- {
- // Check if the server has been disposed.
- if (_disposed) throw new ObjectDisposedException(this.GetType().Name, "Server has been disposed.");
- // Check if the server is actually listening.
- if (!IsListening) return;
- // Close the listener socket.
- if (Listener != null)
- {
- Listener.Close();
- Listener = null;
- }
- // Disconnect the clients.
- foreach(var connection in this.Connections.ToList()) // use ToList() so we don't get collection modified exception there
- {
- connection.Value.Disconnect();
- }
- Listener = null;
- IsListening = false;
- }
- #endregion
- #region de-ctor
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed) return;
- if (disposing)
- {
- Shutdown(); // Close the listener socket.
- DisconnectAll(); // Disconnect all users.
- }
- // Dispose of unmanaged resources here.
- _disposed = true;
- }
- #endregion
- }
- }