PageRenderTime 1219ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Mooege/Net/Server.cs

http://github.com/mooege/mooege
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
  1. /*
  2. * Copyright (C) 2011 mooege project
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. */
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Linq;
  21. using System.Net;
  22. using System.Net.Sockets;
  23. using Mooege.Common.Extensions;
  24. using Mooege.Common.Logging;
  25. namespace Mooege.Net
  26. {
  27. public class Server : IDisposable
  28. {
  29. public bool IsListening { get; private set; }
  30. public int Port { get; private set; }
  31. protected Socket Listener;
  32. protected Dictionary<Socket, IConnection> Connections = new Dictionary<Socket, IConnection>();
  33. protected object ConnectionLock = new object();
  34. public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);
  35. public delegate void ConnectionDataEventHandler(object sender, ConnectionDataEventArgs e);
  36. public event ConnectionEventHandler OnConnect;
  37. public event ConnectionEventHandler OnDisconnect;
  38. public event ConnectionDataEventHandler DataReceived;
  39. public event ConnectionDataEventHandler DataSent;
  40. protected static readonly Logger Logger = LogManager.CreateLogger();
  41. private bool _disposed;
  42. public virtual void Run() { }
  43. #region listener
  44. public virtual bool Listen(string bindIP, int port)
  45. {
  46. // Check if the server has been disposed.
  47. if (_disposed) throw new ObjectDisposedException(this.GetType().Name, "Server has been disposed.");
  48. // Check if the server is already listening.
  49. if (IsListening) throw new InvalidOperationException("Server is already listening.");
  50. // Create new TCP socket and set socket options.
  51. Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  52. // Setup our options:
  53. // * NoDelay - don't use packet coalescing
  54. // * DontLinger - don't keep sockets around once they've been disconnected
  55. Listener.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
  56. Listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
  57. try
  58. {
  59. // Bind.
  60. Listener.Bind(new IPEndPoint(IPAddress.Parse(bindIP), port));
  61. this.Port = port;
  62. }
  63. catch (SocketException)
  64. {
  65. Logger.Fatal(string.Format("{0} can't bind on {1}, server shutting down..", this.GetType().Name, bindIP));
  66. this.Shutdown();
  67. return false;
  68. }
  69. // Start listening for incoming connections.
  70. Listener.Listen(10);
  71. IsListening = true;
  72. // Begin accepting any incoming connections asynchronously.
  73. Listener.BeginAccept(AcceptCallback, null);
  74. return true;
  75. }
  76. private void AcceptCallback(IAsyncResult result)
  77. {
  78. if (Listener == null) return;
  79. try
  80. {
  81. var socket = Listener.EndAccept(result); // Finish accepting the incoming connection.
  82. var connection = new Connection(this, socket); // Add the new connection to the dictionary.
  83. lock (ConnectionLock) Connections[socket] = connection; // add the connection to list.
  84. OnClientConnection(new ConnectionEventArgs(connection)); // Raise the OnConnect event.
  85. connection.BeginReceive(ReceiveCallback, connection); // Begin receiving on the new connection connection.
  86. Listener.BeginAccept(AcceptCallback, null); // Continue receiving other incoming connection asynchronously.
  87. }
  88. catch (NullReferenceException) { } // we recive this after issuing server-shutdown, just ignore it.
  89. catch (Exception e)
  90. {
  91. Logger.DebugException(e, "AcceptCallback");
  92. }
  93. }
  94. private void ReceiveCallback(IAsyncResult result)
  95. {
  96. var connection = result.AsyncState as Connection; // Get the connection connection passed to the callback.
  97. if (connection == null) return;
  98. try
  99. {
  100. var bytesRecv = connection.EndReceive(result); // Finish receiving data from the socket.
  101. if (bytesRecv > 0)
  102. {
  103. OnDataReceived(new ConnectionDataEventArgs(connection, connection.RecvBuffer.Enumerate(0, bytesRecv))); // Raise the DataReceived event.
  104. if (connection.IsConnected) connection.BeginReceive(ReceiveCallback, connection); // Begin receiving again on the socket, if it is connected.
  105. else RemoveConnection(connection, true); // else remove it from connection list.
  106. }
  107. else RemoveConnection(connection, true); // Connection was lost.
  108. }
  109. catch (SocketException)
  110. {
  111. RemoveConnection(connection, true); // An error occured while receiving, connection has disconnected.
  112. }
  113. catch (Exception e)
  114. {
  115. Logger.DebugException(e, "ReceiveCallback");
  116. }
  117. }
  118. public virtual int Send(Connection connection, IEnumerable<byte> data, SocketFlags flags)
  119. {
  120. if (connection == null) throw new ArgumentNullException("connection");
  121. if (data == null) throw new ArgumentNullException("data");
  122. var buffer = data.ToArray();
  123. return Send(connection, buffer, 0, buffer.Length, SocketFlags.None);
  124. }
  125. public virtual int Send(Connection connection, byte[] buffer, int start, int count, SocketFlags flags)
  126. {
  127. if (connection == null) throw new ArgumentNullException("connection");
  128. if (buffer == null) throw new ArgumentNullException("buffer");
  129. var totalBytesSent = 0;
  130. var bytesRemaining = buffer.Length;
  131. try
  132. {
  133. while (bytesRemaining > 0) // Ensure we send every byte.
  134. {
  135. var bytesSent = connection.Socket.Send(buffer, totalBytesSent, bytesRemaining, flags); // Send the remaining data.
  136. if (bytesSent > 0)
  137. OnDataSent(new ConnectionDataEventArgs(connection, buffer.Enumerate(totalBytesSent, bytesSent))); // Raise the Data Sent event.
  138. // Decrement bytes remaining and increment bytes sent.
  139. bytesRemaining -= bytesSent;
  140. totalBytesSent += bytesSent;
  141. }
  142. }
  143. catch (SocketException)
  144. {
  145. RemoveConnection(connection, true); // An error occured while sending, connection has disconnected.
  146. }
  147. catch (Exception e)
  148. {
  149. Logger.DebugException(e, "Send");
  150. }
  151. return totalBytesSent;
  152. }
  153. #endregion
  154. #region service methods
  155. public IEnumerable<IConnection> GetConnections()
  156. {
  157. lock (ConnectionLock)
  158. foreach (IConnection connection in Connections.Values)
  159. yield return connection;
  160. }
  161. #endregion
  162. #region events
  163. protected virtual void OnClientConnection(ConnectionEventArgs e)
  164. {
  165. var handler = OnConnect;
  166. if (handler != null) handler(this, e);
  167. }
  168. protected virtual void OnClientDisconnect(ConnectionEventArgs e)
  169. {
  170. var handler = OnDisconnect;
  171. if (handler != null) handler(this, e);
  172. }
  173. protected virtual void OnDataReceived(ConnectionDataEventArgs e)
  174. {
  175. var handler = DataReceived;
  176. if (handler != null) handler(this, e);
  177. }
  178. protected virtual void OnDataSent(ConnectionDataEventArgs e)
  179. {
  180. var handler = DataSent;
  181. if (handler != null) handler(this, e);
  182. }
  183. #endregion
  184. #region disconnect & shutdown handlers
  185. public virtual void DisconnectAll()
  186. {
  187. lock (ConnectionLock)
  188. {
  189. foreach (var connection in Connections.Values.Cast<Connection>().Where(conn => conn.IsConnected)) // Check if the connection is connected.
  190. {
  191. // Disconnect and raise the OnDisconnect event.
  192. connection.Socket.Disconnect(false);
  193. OnClientDisconnect(new ConnectionEventArgs(connection));
  194. }
  195. Connections.Clear();
  196. }
  197. }
  198. public virtual void Disconnect(Connection connection)
  199. {
  200. if (connection == null) throw new ArgumentNullException("connection");
  201. if (!connection.IsConnected) return;
  202. connection.Socket.Disconnect(false);
  203. RemoveConnection(connection, true);
  204. }
  205. private void RemoveConnection(Connection connection, bool raiseEvent)
  206. {
  207. // Remove the connection from the dictionary and raise the OnDisconnection event.
  208. lock (ConnectionLock)
  209. if (Connections.Remove(connection.Socket) && raiseEvent)
  210. OnClientDisconnect(new ConnectionEventArgs(connection));
  211. }
  212. public virtual void Shutdown()
  213. {
  214. // Check if the server has been disposed.
  215. if (_disposed) throw new ObjectDisposedException(this.GetType().Name, "Server has been disposed.");
  216. // Check if the server is actually listening.
  217. if (!IsListening) return;
  218. // Close the listener socket.
  219. if (Listener != null)
  220. {
  221. Listener.Close();
  222. Listener = null;
  223. }
  224. // Disconnect the clients.
  225. foreach(var connection in this.Connections.ToList()) // use ToList() so we don't get collection modified exception there
  226. {
  227. connection.Value.Disconnect();
  228. }
  229. Listener = null;
  230. IsListening = false;
  231. }
  232. #endregion
  233. #region de-ctor
  234. public void Dispose()
  235. {
  236. Dispose(true);
  237. GC.SuppressFinalize(this);
  238. }
  239. protected virtual void Dispose(bool disposing)
  240. {
  241. if (_disposed) return;
  242. if (disposing)
  243. {
  244. Shutdown(); // Close the listener socket.
  245. DisconnectAll(); // Disconnect all users.
  246. }
  247. // Dispose of unmanaged resources here.
  248. _disposed = true;
  249. }
  250. #endregion
  251. }
  252. }