1 module dfins.channel; 2 3 import std.experimental.logger; 4 import core.time : dur; 5 import std.socket; 6 7 /** 8 * Timeout exception 9 */ 10 class ChannelTimeoutException : Exception { 11 this(string msg, string ip, const(ubyte[]) messageSent, string file = null, size_t line = 0) @trusted { 12 _ip = ip; 13 _messageSent = messageSent; 14 15 import std..string : format; 16 17 super("%s -- PLC ip %s".format(msg, ip)); 18 } 19 20 private string _ip; 21 string ip() { 22 return _ip; 23 } 24 25 private const(ubyte[]) _messageSent; 26 const(ubyte[]) messageSent() { 27 return _messageSent; 28 } 29 } 30 31 unittest { 32 ChannelTimeoutException ex = new ChannelTimeoutException("message", "192.168.221.1", [10, 11]); 33 assert(ex.ip == "192.168.221.1"); 34 assert(ex.messageSent == [10, 11]); 35 assert(ex.msg == "message -- PLC ip 192.168.221.1"); 36 } 37 38 39 interface IChannel { 40 ubyte[] send(const(ubyte[]) msg); 41 } 42 43 /** 44 * Implementazione di $(LINK IChannel) con protocollo ethernet (UDP) 45 */ 46 class UdpChannel : IChannel { 47 private Socket socket; 48 private Address address; 49 this(Socket socket, Address address) { 50 assert(socket !is null); 51 this.socket = socket; 52 53 assert(address !is null); 54 this.address = address; 55 } 56 57 /** 58 * Invia il messaggio e ritorna una risposta 59 */ 60 ubyte[] send(const(ubyte[]) msg) { 61 int attempt; 62 while (true) { 63 try { 64 return sendSingle(msg); 65 } catch (Exception e) { 66 ++attempt; 67 tracef("attempt %d failed", attempt); 68 if (attempt > 2) { 69 // tre tentativi 70 throw e; 71 } 72 } 73 } 74 } 75 76 private ubyte[] sendSingle(const(ubyte[]) msg) { 77 socket.sendTo(msg, address); 78 79 ubyte[1024] reply; 80 version (Win32) { 81 uint len = socket.receiveFrom(reply, address); 82 } else { 83 long len = socket.receiveFrom(reply, address); 84 } 85 86 if (len > 0) { 87 return reply[0 .. len].dup; 88 } else { 89 throw new ChannelTimeoutException("Channel send error", address.toAddrString, msg); 90 } 91 } 92 } 93 94 /** 95 /* 96 * Convenience functions that create an `IChannel` object 97 * 98 * Params: 99 * ip = IP address 100 * timeout = Send and recieve timeout in ms 101 * port = Port number (defaul 9600) 102 */ 103 IChannel createUdpChannel(string ip, long timeout, ushort port = 9600) 104 in { 105 assert(ip.length); 106 assert(timeout >= 0); 107 } 108 do { 109 auto sock = new UdpSocket(); 110 sock.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"msecs"(timeout)); 111 112 Address addr = parseAddress(ip, port); 113 return new UdpChannel(sock, addr); 114 }