1 module cheetah.socketclient; 2 3 import vibe.core.net : TCPConnection; 4 import vibe.core.core : runTask; 5 import vibe.core.task : Task; 6 7 import cheetah.socketserver; 8 import cheetah.socketeventargs; 9 import cheetah.socketeventtype; 10 import cheetah.socketeventchain; 11 12 /// Wrapper for a tcp socket client. 13 class SocketClient(T) { 14 private: 15 /// The server the client is connected to. 16 SocketServer!T _server; 17 18 /// The low-level tcp connection. 19 TCPConnection _connection; 20 21 /// The reading task. 22 Task _readTask; 23 24 /// The event args for the client. 25 SocketEventArgs!T _eventArgs; 26 27 /// The connect events. 28 SocketEventChain!T _connectEvents; 29 30 /// The disconnect events. 31 SocketEventChain!T _disconnectEvents; 32 33 /// The receive events. 34 SocketEventChain!T _receiveEvents; 35 36 /// The error events. 37 SocketEventChain!T _errorEvents; 38 39 /// The buffer. 40 ubyte[] _buffer; 41 42 /// The current received amount of bytes. 43 size_t _currentReceiveAmount; 44 45 /// Boolean determining whether the client has been disconnected or not. 46 bool _disconnected; 47 48 /// Generic data that can be associated with the client. 49 T _data; 50 51 /// The ip address. 52 string _ipAddress; 53 54 /// The port. 55 ushort _port; 56 57 /// The address string. 58 string _addressString; 59 60 /// The client id. 61 size_t _clientId; 62 63 public: 64 /** 65 * Creates a new tcp client. 66 * Params: 67 * server = The server. 68 * connection = The low-level tcp connection. 69 */ 70 this(SocketServer!T server, TCPConnection connection) { 71 _server = server; 72 _connection = connection; 73 _eventArgs = new SocketEventArgs!T(_server, this); 74 75 _ipAddress = _connection.remoteAddress.toAddressString(); 76 _port = _connection.remoteAddress.port; 77 _addressString = _connection.remoteAddress.toString(); 78 } 79 80 @property { 81 /// Gets the amount of available bytes for receive. 82 size_t availableReceiveAmount() @trusted { return cast(size_t)_connection.leastSize; } 83 84 /// Gets the current received amount of bytes. 85 size_t currentReceiveAmount() pure const @safe { return _currentReceiveAmount; } 86 87 /// Gets the current buffer. 88 auto buffer() { return _buffer; } 89 90 /// Gets a boolean determining whether the client has been disconnected or not. 91 bool disconnected() pure const @safe { return _disconnected; } 92 93 /// Gets a boolean determining whether the client is connected or not. 94 bool connected() @trusted { return !_disconnected && _connection.connected; } 95 96 /// Gets the generic data associated with the client. 97 T data() { return _data; } 98 99 /** 100 * Sets the generic data associated with the client. 101 * Params: 102 * newData = The generic data to associated with the client. 103 */ 104 void data(T newData) { 105 _data = newData; 106 } 107 108 /// Gets the remote address. 109 auto remoteAddress() { return _connection.remoteAddress; } 110 111 /// Gets the remote ip address. 112 auto ipAddress() pure @safe { return _ipAddress; } 113 114 /// Gets the remote port. 115 auto port() pure @safe { return _port; } 116 117 /// Gets the address string. 118 auto address() pure @safe { return _addressString; } 119 120 package(cheetah) { 121 /// Sets the client id. 122 void clientId(size_t newClientId) { 123 _clientId = newClientId; 124 } 125 } 126 127 /// Gets the client id. 128 size_t clientId() { return _clientId; } 129 } 130 131 /** 132 * Resets the current receive state. 133 * Params: 134 * cachedAmount = (optional) An amount of cached bytes. 135 */ 136 void resetReceive(size_t cachedAmount = 0) { 137 _currentReceiveAmount = cachedAmount; 138 139 _buffer = _currentReceiveAmount ? _buffer[$-_currentReceiveAmount .. $] : []; 140 } 141 142 /// Reads the current available bytes. 143 void read() { 144 read(availableReceiveAmount); 145 } 146 147 /** 148 * Reads a specific amount of bytes. 149 * Paramms: 150 * amount = The amount of bytes to read. 151 */ 152 void read(size_t amount) { 153 if (!amount) { 154 return; 155 } 156 157 auto available = availableReceiveAmount; 158 159 if (amount > available) { 160 amount = available; 161 } 162 163 try { 164 auto temp = new ubyte[amount]; 165 166 _connection.read(temp); 167 168 _buffer ~= temp; 169 170 _currentReceiveAmount += amount; 171 } 172 catch (Exception e) { 173 report(e); 174 } 175 catch (Throwable e) { 176 report(new Exception(e.toString())); 177 178 throw e; 179 } 180 } 181 182 /** 183 * Writes a buffer to the socket. 184 * Params: 185 * buffer = The buffer to write. 186 */ 187 void write(ubyte[] buffer) { 188 try { 189 _connection.write(buffer); 190 } 191 catch (Exception e) { 192 report(e); 193 } 194 catch (Throwable e) { 195 report(new Exception(e.toString())); 196 197 throw e; 198 } 199 } 200 201 /// Closes the socket. 202 void close() { 203 synchronized { 204 if (_disconnected) { 205 return; 206 } 207 208 _disconnected = true; 209 210 try { 211 _server.removeClient(_clientId); 212 213 if (_connection.connected) { 214 _connection.close(); 215 } 216 217 fireEvent(SocketEventType.disconnect, _eventArgs); 218 } 219 catch (Exception e) { 220 report(e); 221 } 222 catch (Throwable e) { 223 report(new Exception(e.toString())); 224 225 throw e; 226 } 227 } 228 } 229 230 /** 231 * Moves onto the next event handler in a chain. 232 * Params: 233 * eventType = The event type to move onto next event handler. 234 */ 235 void moveNext(SocketEventType eventType) { 236 switch (eventType) { 237 case SocketEventType.connect: { 238 _connectEvents.moveNext(); 239 break; 240 } 241 case SocketEventType.disconnect: { 242 _disconnectEvents.moveNext(); 243 break; 244 } 245 case SocketEventType.receive: { 246 _receiveEvents.moveNext(); 247 break; 248 } 249 case SocketEventType.error: { 250 _errorEvents.moveNext(); 251 break; 252 } 253 254 default: { 255 _server.moveNext(eventType); 256 break; 257 } 258 } 259 } 260 261 262 package(cheetah): 263 @property { 264 /// Gets the event args of the client. 265 auto eventArgs() { return _eventArgs; } 266 } 267 268 /** 269 * Sets up events for the client. 270 * Params: 271 * connectEvents = The connect events to setup. 272 * disconnectEvents = The disconnect events to setup. 273 * receiveEvents = The receive events to setup. 274 * errorEvents = The error events to setup. 275 */ 276 void setupEvents(SocketEventChain!T connectEvents, SocketEventChain!T disconnectEvents, SocketEventChain!T receiveEvents, SocketEventChain!T errorEvents) { 277 if (connectEvents) _connectEvents = new SocketEventChain!T(connectEvents._events); 278 if (disconnectEvents) _disconnectEvents = new SocketEventChain!T(disconnectEvents._events); 279 if (receiveEvents) _receiveEvents = new SocketEventChain!T(receiveEvents._events); 280 if (errorEvents) _errorEvents = new SocketEventChain!T(errorEvents._events); 281 } 282 283 /** 284 * Fires a socket event. 285 * Params: 286 * eventType = The event type to fire. 287 * e = The event args to pass to the handler. 288 */ 289 void fireEvent(SocketEventType eventType, SocketEventArgs!T e) { 290 switch (eventType) { 291 case SocketEventType.connect: { 292 if (_connectEvents) { 293 _connectEvents(e); 294 return; 295 } 296 break; 297 } 298 case SocketEventType.disconnect: { 299 if (_disconnectEvents) { 300 _disconnectEvents(e); 301 return; 302 } 303 break; 304 } 305 case SocketEventType.receive: { 306 if (_receiveEvents) { 307 _receiveEvents(e); 308 return; 309 } 310 break; 311 } 312 case SocketEventType.error: { 313 if (_errorEvents) { 314 _errorEvents(e); 315 return; 316 } 317 break; 318 } 319 320 default: break; 321 } 322 323 _server.fireEvent(eventType, e); 324 } 325 326 /** 327 * Reports an error to the error handler. 328 * Params: 329 * error = The error to handle. 330 * server = The server tied to the error. 331 * client = The client tied to the error. 332 */ 333 void report(Exception error) { 334 fireEvent(SocketEventType.error, new SocketEventArgs!T(_server, this, error)); 335 } 336 337 /// Processes the client. 338 void process() { 339 _readTask = runTask({ 340 try { 341 while (connected) { 342 if (_connection.leastSize) { 343 fireEvent(SocketEventType.receive, _eventArgs); 344 } 345 } 346 } 347 catch (Exception e) { 348 report(e); 349 } 350 catch (Throwable e) { 351 report(new Exception(e.toString())); 352 353 throw e; 354 } 355 }); 356 357 _readTask.join(); 358 359 close(); 360 } 361 }