1 // Written in the D programming language. 2 3 /** 4 This module contains the base class of the server that can be used to create various kinds of servers. 5 6 Copyright: LightHouse Software, 2023 7 License: $(HTTP https://github.com/aquaratixc/ESL-License, Experimental Software License 1.0). 8 Authors: Oleg Bakharev, 9 Ilya Pertsev 10 */ 11 module styx2000.protosrv.baseserver; 12 13 private { 14 import std.algorithm : remove; 15 import std.socket; 16 } 17 18 class BaseServer(uint BUFFER_SIZE, uint MAXIMAL_NUMBER_OF_CONNECTIONS) 19 { 20 protected 21 { 22 ubyte[BUFFER_SIZE] _buffer; 23 24 string _address; 25 ushort _port; 26 bool _immediate; 27 Socket _listener; 28 Socket[] _readable; 29 SocketSet _sockets; 30 } 31 32 33 34 /** 35 An abstract method that must be implemented in a derived class and that is necessary for the server to process incoming data. 36 Params: 37 request = Array of unsigned bytes that represents message. 38 */ 39 abstract ubyte[] handle(ubyte[] request); 40 41 /** 42 Runs a configured server. 43 */ 44 final void run() 45 { 46 while (true) 47 { 48 serve; 49 50 scope (failure) 51 { 52 _sockets = null; 53 _listener.close; 54 } 55 } 56 } 57 58 59 /** 60 An abstract method that must be implemented in a derived class and that is necessary for the server to process incoming data. 61 Params: 62 address = IPv4 address as string. 63 port = Unsigned port number. 64 backlog = An integer that means the number of established connections that can be processed at any time. 65 immediate = The logical value showing whether the server will close the connection after processing the request from the client. 66 */ 67 final void setup4(string address, ushort port, int backlog = 10, bool immediate = false) 68 { 69 _address = address; 70 _port = port; 71 _listener = new Socket(AddressFamily.INET, SocketType.STREAM); 72 _immediate = immediate; 73 74 with (_listener) 75 { 76 bind(new InternetAddress(_address, _port)); 77 listen(backlog); 78 } 79 80 _sockets = new SocketSet(MAXIMAL_NUMBER_OF_CONNECTIONS + 1); 81 } 82 83 84 /** 85 An abstract method that must be implemented in a derived class and that is necessary for the server to process incoming data. 86 Params: 87 address = IPv4 address as string. 88 port = Unsigned port number. 89 backlog = An integer that means the number of established connections that can be processed at any time. 90 immediate = The logical value showing whether the server will close the connection after processing the request from the client. 91 */ 92 final void setup6(string address, ushort port, int backlog = 10, bool immediate = false) 93 { 94 _address = address; 95 _port = port; 96 _listener = new Socket(AddressFamily.INET6, SocketType.STREAM); 97 _immediate = immediate; 98 99 with (_listener) 100 { 101 bind(new Internet6Address(_address, _port)); 102 listen(backlog); 103 } 104 105 _sockets = new SocketSet(MAXIMAL_NUMBER_OF_CONNECTIONS + 1); 106 } 107 108 private 109 { 110 /// Serve a connection. For internal use. 111 final void serve() 112 { 113 _sockets.add(_listener); 114 115 foreach (socket; _readable) 116 { 117 _sockets.add(socket); 118 } 119 120 Socket.select(_sockets, null, null); 121 122 for (uint i = 0; i < _readable.length; i++) 123 { 124 if (_sockets.isSet(_readable[i])) 125 { 126 auto realBufferSize = _readable[i].receive(_buffer); 127 128 if (realBufferSize != 0) 129 { 130 auto data = _buffer[0 .. realBufferSize]; 131 132 _readable[i].send(handle(data)); 133 } 134 135 if (_immediate) 136 { 137 _readable[i].close; 138 _readable = _readable.remove(i); 139 i--; 140 } 141 } 142 } 143 144 if (_sockets.isSet(_listener)) 145 { 146 Socket currentSocket = null; 147 148 scope (failure) 149 { 150 if (currentSocket) 151 { 152 currentSocket.close; 153 } 154 } 155 156 currentSocket = _listener.accept; 157 158 if (_readable.length < MAXIMAL_NUMBER_OF_CONNECTIONS) 159 { 160 _readable ~= currentSocket; 161 } 162 else 163 { 164 currentSocket.close; 165 } 166 } 167 _sockets.reset; 168 } 169 } 170 }