1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.io.TcpStream;
13 
14 import hunt.io.channel.Common;
15 import hunt.io.TcpStreamOptions;
16 import hunt.io.IoError;
17 
18 import hunt.collection.ByteBuffer;
19 import hunt.collection.BufferUtils;
20 import hunt.event.selector.Selector;
21 import hunt.concurrency.SimpleQueue;
22 import hunt.event;
23 import hunt.logging.ConsoleLogger;
24 import hunt.Functions;
25 
26 import std.exception;
27 import std.format;
28 import std.socket;
29 import std.string;
30 
31 import core.atomic;
32 import core.stdc.errno;
33 import core.thread;
34 import core.time;
35 
36 version (HAVE_EPOLL) {
37     import core.sys.linux.netinet.tcp : TCP_KEEPCNT;
38 }
39 
40 
41 
42 /**
43  *
44  */
45 class TcpStream : AbstractStream {
46     SimpleEventHandler closeHandler;
47     protected shared bool _isConnected; // It's always true for server.
48 
49     private TcpStreamOptions _tcpOption;
50     private int retryCount = 0;
51     private Selector _loop;
52 
53     // for client
54     this(Selector loop, TcpStreamOptions option = null, AddressFamily family = AddressFamily.INET) {
55         if (option is null)
56             _tcpOption = TcpStreamOptions.create();
57         else
58             _tcpOption = option;
59         super(loop, family, _tcpOption.bufferSize);
60         version(HUNT_IO_DEBUG) tracef("buffer size: %d bytes", _tcpOption.bufferSize);
61         this.socket = new Socket(family, SocketType.STREAM, ProtocolType.TCP);
62         _isClient = true;
63         _isConnected = false;
64         _loop = loop;
65     }
66 
67     // for server
68     this(Selector loop, Socket socket, TcpStreamOptions option = null) {
69         if (option is null)
70             _tcpOption = TcpStreamOptions.create();
71         else
72             _tcpOption = option;
73         super(loop, socket.addressFamily, _tcpOption.bufferSize);
74         this.socket = socket;
75         _remoteAddress = socket.remoteAddress();
76         _localAddress = socket.localAddress();
77 
78         _isClient = false;
79         _isConnected = true;
80         _loop = loop;
81         setKeepalive();
82     }
83 
84     void options(TcpStreamOptions option) @property {
85         assert(option !is null);
86         this._tcpOption = option;
87     }
88 
89     TcpStreamOptions options() @property {
90         return this._tcpOption;
91     }
92 
93     override bool isBusy() {
94         return _isWritting;
95     }
96 
97     void connect(string hostname, ushort port) {
98         Address[] addresses = getAddress(hostname, port);
99         if(addresses is null) {
100             throw new SocketException("Can't resolve hostname: " ~ hostname);
101         }
102         Address selectedAddress;
103         foreach(Address addr; addresses) {
104             string ip = addr.toAddrString();
105             if(ip.startsWith("::")) // skip IPV6
106                 continue;
107             if(ip.length <= 16) {
108                 selectedAddress = addr;
109                 break;
110             }
111         }
112 
113         if(selectedAddress is null) {
114             warning("No IPV4 avaliable");
115             selectedAddress = addresses[0];
116         }
117         version(HUNT_IO_DEBUG) {
118             infof("connecting with: hostname=%s, ip=%s, port=%d ", hostname, selectedAddress.toAddrString(), port);
119         }
120         connect(selectedAddress); // always select the first one.
121     }
122 
123     void connect(Address addr) {
124         if (_isConnected)
125             return;
126 
127         _remoteAddress = addr;
128         import std.parallelism;
129 
130         auto connectionTask = task(&doConnect, addr);
131         taskPool.put(connectionTask);
132         // doConnect(addr);
133     }
134 
135     void reconnect() {
136         if (!_isClient) {
137             throw new Exception("Only client can call this method.");
138         }
139 
140         if (_isConnected || retryCount >= _tcpOption.retryTimes)
141             return;
142 
143         retryCount++;
144         _isConnected = false;
145         this.socket = new Socket(this._family, SocketType.STREAM, ProtocolType.TCP);
146 
147         version (HUNT_DEBUG)
148             tracef("reconnecting %d...", retryCount);
149         connect(_remoteAddress);
150     }
151 
152     protected override bool doConnect(Address addr)  {
153         try {
154             version (HUNT_DEBUG)
155                 tracef("connecting to %s...", addr);
156             // Address binded = createAddress(this.socket.addressFamily);
157             // this.socket.bind(binded);
158             this.socket.blocking = true;
159             if(super.doConnect(addr))
160             {
161               this.socket.blocking = false;
162               setKeepalive();
163               _localAddress = this.socket.localAddress();
164               start();
165               _isConnected = true;
166             } else
167             {
168                 errorOccurred(ErrorCode.CONNECTIONEFUSED,"Connection refused");
169                 _isConnected = false;
170             }
171 
172         } catch (SocketOSException ex) {
173             // Must try the best to catch all the exceptions. It's because it will executed in another thread.
174             debug warning(ex.msg);
175             version(HUNT_IO_DEBUG) warning(ex);
176             _isConnected = false;
177         }
178 
179         if (_connectionHandler !is null) {
180             try {
181                 _connectionHandler(_isConnected);
182             } catch(Exception ex) {
183                 debug warning(ex.msg);
184                 version(HUNT_IO_DEBUG) warning(ex);
185             }
186         }
187         return true;
188     }
189 
190     // www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
191     // http://www.importnew.com/27624.html
192     private void setKeepalive() {
193         version (HAVE_EPOLL) {
194             if (_tcpOption.isKeepalive) {
195                 this.socket.setKeepAlive(_tcpOption.keepaliveTime, _tcpOption.keepaliveInterval);
196                 this.setOption(SocketOptionLevel.TCP,
197                         cast(SocketOption) TCP_KEEPCNT, _tcpOption.keepaliveProbes);
198                 // version (HUNT_DEBUG) checkKeepAlive();
199             }
200         } else version (HAVE_IOCP) {
201             if (_tcpOption.isKeepalive) {
202                 this.socket.setKeepAlive(_tcpOption.keepaliveTime, _tcpOption.keepaliveInterval);
203                 // this.setOption(SocketOptionLevel.TCP, cast(SocketOption) TCP_KEEPCNT,
204                 //     _tcpOption.keepaliveProbes);
205                 // version (HUNT_DEBUG) checkKeepAlive();
206             }
207         }
208     }
209 
210     version (HUNT_DEBUG) private void checkKeepAlive() {
211         version (HAVE_EPOLL) {
212             int time;
213             int ret1 = getOption(SocketOptionLevel.TCP, cast(SocketOption) TCP_KEEPIDLE, time);
214             tracef("ret=%d, time=%d", ret1, time);
215 
216             int interval;
217             int ret2 = getOption(SocketOptionLevel.TCP, cast(SocketOption) TCP_KEEPINTVL, interval);
218             tracef("ret=%d, interval=%d", ret2, interval);
219 
220             int isKeep;
221             int ret3 = getOption(SocketOptionLevel.SOCKET, SocketOption.KEEPALIVE, isKeep);
222             tracef("ret=%d, keepalive=%s", ret3, isKeep == 1);
223 
224             int probe;
225             int ret4 = getOption(SocketOptionLevel.TCP, cast(SocketOption) TCP_KEEPCNT, probe);
226             tracef("ret=%d, interval=%d", ret4, probe);
227         }
228     }
229 
230     TcpStream connected(ConnectionHandler handler) {
231         _connectionHandler = handler;
232         return this;
233     }
234 
235     TcpStream received(DataReceivedHandler handler) {
236         dataReceivedHandler = handler;
237         return this;
238     }
239 
240     TcpStream writed(SimpleActionHandler handler) {
241         dataWriteDoneHandler = handler;
242         return this;
243     }
244     alias onWritten = writed;
245 
246     TcpStream closed(SimpleEventHandler handler) {
247         closeHandler = handler;
248         return this;
249     }
250 
251     TcpStream disconnected(SimpleEventHandler handler) {
252         disconnectionHandler = handler;
253         return this;
254     }
255 
256     TcpStream error(ErrorEventHandler handler) {
257         errorHandler = handler;
258         return this;
259     }
260 
261     override bool isConnected() nothrow {
262         return _isConnected;
263     }
264 
265     override void start() {
266         if (_isRegistered)
267             return;
268         _inLoop.register(this);
269         _isRegistered = true;
270         version (HAVE_IOCP)
271         {
272             //auto iopc = cast(AbstractSelector)_loop;
273             //iopc.putTast(this);
274            // this.beginRead();
275         }
276     }
277 
278     void write(ByteBuffer buffer) {
279         assert(buffer !is null);
280 
281         if (!_isConnected) {
282             throw new Exception(format("The connection is down! remote: %s",
283                 this.remoteAddress.toString()));
284         }
285 
286         version (HUNT_IO_DEBUG)
287             infof("data buffered (%d bytes): fd=%d", buffer.limit(), this.handle);
288         _isWritting = true;
289         // initializeWriteQueue();
290         _writeQueue.enqueue(buffer);
291         onWrite();
292     }
293 
294     /**
295     */
296     void write(const(ubyte)[] data) {
297         if (data.length == 0 || !_isConnected)
298             return;
299 
300         if (!_isConnected) {
301             throw new Exception("The connection is down!");
302         }
303         version (HAVE_IOCP)
304         {
305             return write(BufferUtils.toBuffer(cast(byte[])data));
306         } else
307         {
308             version (HUNT_IO_DEBUG_MORE) {
309                 infof("%d bytes(fd=%d): %(%02X %)", data.length, this.handle, data[0 .. $]);
310             } else  version (HUNT_IO_DEBUG) {
311                 if (data.length <= 32)
312                     infof("%d bytes(fd=%d): %(%02X %)", data.length, this.handle, data[0 .. $]);
313                 else
314                     infof("%d bytes(fd=%d): %(%02X %)", data.length, this.handle, data[0 .. 32]);
315             }
316 
317             if ((_writeQueue.isEmpty()) && !_isWritting) {
318                 _isWritting = true;
319                 const(ubyte)[] d = data;
320 
321                 while (!isClosing() && !isWriteCancelling && d.length > 0) {
322                     version (HUNT_IO_DEBUG)
323                         infof("to write directly %d bytes, fd=%d", d.length, this.handle);
324                     size_t nBytes = tryWrite(d);
325 
326                     if (nBytes == d.length) {
327                         version (HUNT_IO_DEBUG)
328                             tracef("write all out at once: %d / %d bytes, fd=%d", nBytes, d.length, this.handle);
329                         checkAllWriteDone();
330                         break;
331                     } else if (nBytes > 0) {
332                         version (HUNT_IO_DEBUG)
333                             tracef("write out partly: %d / %d bytes, fd=%d", nBytes, d.length, this.handle);
334                         d = d[nBytes .. $];
335                     } else {
336                         version (HUNT_IO_DEBUG)
337                             warningf("buffering data: %d bytes, fd=%d", d.length, this.handle);
338                         _writeQueue.enqueue(BufferUtils.toBuffer(cast(byte[]) d));
339                         break;
340                     }
341                 }
342             } else {
343                 write(BufferUtils.toBuffer(cast(byte[]) data));
344             }
345         }
346     }
347 
348     void shutdownInput() {
349         this.socket.shutdown(SocketShutdown.RECEIVE);
350     }
351 
352     void shutdownOutput() {
353         this.socket.shutdown(SocketShutdown.SEND);
354     }
355 
356     override protected void onDisconnected() {
357         version(HUNT_DEBUG) {
358             infof("peer disconnected: fd=%d", this.handle);
359         }
360         if (disconnectionHandler !is null)
361             disconnectionHandler();
362 
363         this.close();
364     }
365 
366 protected:
367     bool _isClient;
368     ConnectionHandler _connectionHandler;
369 
370     override void onRead() {
371         version (HUNT_IO_DEBUG)
372             trace("start to read");
373 
374         version (Posix) {
375             while (!_isClosed && !tryRead()) {
376                 version (HUNT_IO_DEBUG)
377                     trace("continue reading...");
378             }
379         } else {
380             if (!_isClosed)
381             {
382                 doRead();
383             }
384 
385         }
386 
387         //if (this.isError) {
388         //    string msg = format("Socket error on read: fd=%d, code=%d, message: %s",
389         //            this.handle, errno, this.errorMessage);
390         //    debug errorf(msg);
391         //    if (!isClosed())
392         //        errorOccurred(msg);
393         //}
394     }
395 
396     override void onClose() {
397         bool lastConnectStatus = _isConnected;
398         super.onClose();
399         if(lastConnectStatus) {
400             version (HUNT_IO_DEBUG) {
401                 if (!_writeQueue.isEmpty) {
402                     warningf("Some data has not been sent yet: fd=%d", this.handle);
403                 }
404                 infof("Closing a connection with: %s, fd=%d", this.remoteAddress, this.handle);
405             }
406 
407             resetWriteStatus();
408             _isConnected = false;
409             version (HUNT_IO_DEBUG)
410                 infof("notifying TCP stream down: fd=%d", this.handle);
411             if (closeHandler !is null)
412                 closeHandler();
413         }
414 
415     }
416 
417 }