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 }