1 module dast.async.tcpclient; 2 3 import core.sync, 4 dast.async.core, 5 dast.async.selector, 6 tame.buffer, 7 tame.meta; 8 9 version (Windows) import dast.async.iocp; 10 11 version (Posix) import core.stdc.errno; 12 13 @safe: 14 15 alias ConnectionHandler = void delegate(in Address addr); 16 17 /// TCP Client 18 class TCPClient : SocketChannel { 19 mixin Forward!"_socket"; 20 21 ConnectionHandler onConnected; 22 SimpleHandler onClosed; 23 /++ 24 Warning: The received data is stored a inner buffer. 25 you would make a copy of it. 26 +/ 27 RecvHandler onReceived; 28 SimpleHandler onDisconnected; 29 30 @property final bufferSize() const => _rBuf.length; 31 32 // client side 33 this(EventLoop loop, AddrFamily family = AddrFamily.IPv4, uint bufferSize = 4 * 1024) { 34 this(loop, tcpSocket(family), bufferSize); 35 } 36 37 // server side 38 this(EventLoop loop, Socket socket, uint bufferSize = 4 * 1024) nothrow @trusted { 39 super(loop, WT.TCP); 40 flags |= WF.ReadWrite | WF.ETMode; 41 debug (Log) 42 trace("Buffer size for read: ", bufferSize); 43 _rBuf = BUF(bufferSize); 44 this.socket = socket; 45 _connected = socket.isAlive; 46 } 47 48 void connect(in Address addr) @trusted { 49 if (_connected) 50 return; 51 52 try { 53 const a = _socket.addressFamily == AddrFamily.IPv6 ? 54 IPv6Addr(0) : IPv4Addr(0); 55 _socket.bind(a); 56 doConnect(addr); 57 start(); 58 _connected = true; 59 if (onConnected) 60 onConnected(addr); 61 } catch (Exception e) 62 debug (Log) { 63 error(e); 64 } 65 } 66 67 void reconnect(in Address addr) { 68 if (_connected) 69 close(); 70 _connected = false; 71 socket = tcpSocket(_socket.addressFamily ? _socket.addressFamily : AddrFamily.IPv4); 72 connect(addr); 73 } 74 75 @property isConnected() const => _connected; 76 77 void start() nothrow { 78 if (_isReg) 79 return; 80 _loop.register(this); 81 _isReg = true; 82 version (Windows) 83 recv(); 84 } 85 86 /// safe for big data sending 87 void write(const void[] data) nothrow @trusted { 88 if (!_connected) 89 return onError("The connection has been closed"); 90 if (data.length) { 91 //auto buf = new WSABUF; 92 //*cast(const(void)[]*)buf = data; 93 _iocpWrite.operation = IocpOperation.write; 94 if (checkErr(WSASend(handle, cast(WSABUF*)&data, 1, null, 0, 95 &_iocpWrite.overlapped, null), "write")) { 96 close(); 97 } 98 } 99 } 100 101 override void close() { 102 debug (Log) { 103 if (!_writeQueue.empty) 104 warning("Some data has not been sent yet"); 105 } 106 107 super.close(); 108 _connected = false; 109 _socket.shutdown(SocketShutdown.both); 110 _socket.close(); 111 112 if (onClosed) 113 onClosed(); 114 } 115 116 version (Posix) override void onRead() { 117 debug (Log) 118 trace("start reading"); 119 while (_isReg) { 120 const len = _socket.receive(_rBuf); 121 debug (Log) 122 trace("read nbytes", len); 123 124 if (len > 0) { 125 if (onReceived) 126 onReceived(_rBuf[0 .. len]); 127 128 // It's possible that more data are waiting for read in inner buffer 129 if (len == _rBuf.length) 130 continue; 131 } else if (len < 0) { 132 // FIXME: Needing refactor or cleanup 133 // check more error status 134 if (errno != EINTR && errno != EAGAIN) 135 onError(text("Socket error on write: fd=", handle, ", message=", lastSocketError())); 136 } else { 137 debug (Log) 138 warning("connection broken: ", _socket.remoteAddress); 139 disconnected(); 140 _socket.close(); // release the resource 141 } 142 break; 143 } 144 } 145 146 version (Windows) { 147 /// Called by selector after data sent 148 final onWrite(uint len) @trusted { 149 } 150 151 void onRead(uint len) { 152 debug (Log) 153 trace("data reading ", len, " bytes"); 154 155 if (len) { 156 if (onReceived) 157 onReceived(_rBuf[0 .. len]); 158 debug (Log) 159 trace("read ", len, " bytes"); 160 161 return recv(); // continue reading 162 } 163 debug (Log) 164 warning("connection broken: ", _socket.remoteAddress); 165 disconnected(); 166 } 167 168 final flush() @trusted { 169 } 170 } 171 172 private void disconnected() { 173 _isReg = false; 174 if (onDisconnected) 175 onDisconnected(); 176 } 177 178 protected: 179 final clearQueue() { 180 //mutex.lock_nothrow(); 181 //scope (exit) 182 //mutex.unlock_nothrow(); 183 //_writeQueue.clear(); 184 } 185 186 version (Posix) { 187 bool cancelWriting; 188 189 public final flush() nothrow @trusted { 190 size_t len; 191 while (_isReg && !cancelWriting && !_writeQueue.empty) { 192 const data = _writeQueue.front; 193 const n = tryWrite(data); 194 if (!n) // error 195 break; 196 if (data.length == n) { 197 debug (Log) 198 trace("written ", n, " bytes"); 199 _writeQueue.pop(); 200 if (onSent) 201 onSent(data); 202 len += n; 203 /+ 204 mutex.lock_nothrow(); 205 scope (exit) 206 mutex.unlock_nothrow(); 207 try 208 cond.notify(); 209 catch (Exception) { 210 assert(0); 211 }+/ 212 } 213 } 214 return len; 215 } 216 217 /// Try to write a block of data. 218 final size_t tryWrite(in void[] data) nothrow 219 in (data.length) { 220 const len = _socket.send(data); 221 debug (Log) 222 trace("actually sent bytes: ", len, " / ", data.length); 223 224 if (len > 0) 225 return len; 226 227 // FIXME: check more error status 228 const err = errno; 229 if (err != EINTR && err != EAGAIN && err != EWOULDBLOCK) { 230 onError(text("Socket error on write: fd=", handle, 231 ", errno=", err, ", message: ", lastSocketError())); 232 } 233 return 0; 234 } 235 236 private void doConnect(in Address addr) { 237 _socket.connect(addr); 238 } 239 240 ubyte[] _rBuf; 241 } 242 243 bool _connected; 244 245 //WriteQueue _writeQueue; 246 version (Windows) : 247 nothrow: 248 const(ubyte)[] _rBuf; 249 private: 250 void recv() @trusted { 251 uint dwReceived = void, dwFlags; 252 253 debug (Log) 254 trace("start receiving handle=", handle); 255 256 checkErr(WSARecv(handle, cast(WSABUF*)&_rBuf, 1, &dwReceived, &dwFlags, 257 &_iocpRead.overlapped, null), "recv"); 258 } 259 260 void doConnect(in Address addr) @trusted { 261 _iocpWrite.operation = IocpOperation.connect; 262 checkErr(ConnectEx(handle, addr.name, addr.nameLen, null, 0, null, 263 &_iocpWrite.overlapped), "connect"); 264 } 265 266 mixin checkErr; 267 IocpContext _iocpRead = {operation: IocpOperation.read}, _iocpWrite; 268 }