1 module evael.network.ClientSocket; 2 3 import std.socket; 4 import std.string : format; 5 import std.array : insertInPlace; 6 import std.exception : enforce; 7 8 import core.thread; 9 10 public import evael.network.Packet; 11 12 import msgpack; 13 14 class ClientSocket 15 { 16 enum PACKET_SIZE = 4096; 17 18 private alias void delegate(ref Packet) OnDataEvent; 19 private alias void delegate() OnDisconnectedEvent; 20 21 private OnDataEvent m_onDataEvent; 22 private OnDisconnectedEvent m_onDisconnectedEvent; 23 24 /// Socket 25 private Socket m_socket; 26 27 /// Main thread 28 private Thread m_thread; 29 30 private bool m_connected; 31 32 @nogc @safe 33 public this() pure nothrow 34 { 35 this.m_connected = false; 36 } 37 38 @nogc @safe 39 public this(Socket socket) pure nothrow 40 { 41 this.m_socket = socket; 42 this(); 43 } 44 45 /** 46 * Connects to a server. 47 * Params: 48 * ip : server ip address 49 * port : server port 50 */ 51 public void connect(in string ip, in ushort port) 52 { 53 assert(!this.m_connected, "Client is already connected to a server."); 54 55 this.m_socket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP); 56 this.m_socket.blocking(true); 57 this.m_socket.connect(new InternetAddress(ip, port)); 58 59 enforce(this.m_socket.isAlive(), format("Unable to connect to server %s:%d.", ip, port)); 60 61 this.m_connected = true; 62 63 this.m_thread = new Thread(&this.waitForData); 64 this.m_thread.start(); 65 } 66 67 /** 68 * Main thread. 69 */ 70 private void waitForData() 71 { 72 while(this.m_connected) 73 { 74 auto packet = Packet(PACKET_SIZE); 75 76 immutable length = this.m_socket.receive(packet.data[]); 77 78 if (length > 0) 79 { 80 packet.data.length = length; 81 this.m_onDataEvent(packet); 82 } 83 else 84 { 85 this.m_connected = false; 86 87 if (this.m_onDisconnectedEvent !is null) 88 { 89 this.m_onDisconnectedEvent(); 90 } 91 } 92 } 93 } 94 95 /** 96 * Sends data as struct messages. 97 * Params: 98 * message : message to send 99 */ 100 public void send(T)(in auto ref T message) 101 { 102 ubyte[] data = message.pack(); 103 104 data.insertInPlace(0, GetAttribute!(ubyte, "OpCode", T)); 105 106 this.m_socket.send(data); 107 } 108 109 /** 110 * Receives data. 111 */ 112 public int receive(ubyte[] data) 113 { 114 return this.m_socket.receive(data); 115 } 116 117 /** 118 * Closes connection. 119 */ 120 public void close() 121 { 122 this.m_connected = false; 123 124 this.m_socket.shutdown(SocketShutdown.BOTH); 125 this.m_socket.close(); 126 } 127 128 @nogc @safe 129 @property pure nothrow 130 { 131 public bool connected() const 132 { 133 return this.m_connected; 134 } 135 136 public Socket socket() 137 { 138 return this.m_socket; 139 } 140 141 public void onDataEvent(OnDataEvent value) 142 { 143 this.m_onDataEvent = value; 144 } 145 146 public void onDisconnectedEvent(OnDisconnectedEvent value) 147 { 148 this.m_onDisconnectedEvent = value; 149 } 150 } 151 } 152 153 154 T getStruct(T)(ubyte[] data) 155 { 156 return data.unpack!T(); 157 } 158 159 template GetAttribute(T, string name, alias symbol) 160 { 161 import std.string; 162 163 T GetAttribute() 164 { 165 enum UDAs = __traits(getAttributes, symbol); 166 167 static if (UDAs.length) 168 { 169 foreach (i, UDA; UDAs) 170 { 171 if (UDA.stringof.startsWith(name)) 172 return UDA.value; 173 } 174 } 175 else static assert(false, "No OpCode attribute for message " ~ symbol.stringof); 176 177 return 0; 178 } 179 }