1 /* 2 * DSFML - The Simple and Fast Multimedia Library for D 3 * 4 * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com) 5 * 6 * This software is provided 'as-is', without any express or implied warranty. 7 * In no event will the authors be held liable for any damages arising from the 8 * use of this software. 9 * 10 * Permission is granted to anyone to use this software for any purpose, 11 * including commercial applications, and to alter it and redistribute it 12 * freely, subject to the following restrictions: 13 * 14 * 1. The origin of this software must not be misrepresented; you must not claim 15 * that you wrote the original software. If you use this software in a product, 16 * an acknowledgment in the product documentation would be appreciated but is 17 * not required. 18 * 19 * 2. Altered source versions must be plainly marked as such, and must not be 20 * misrepresented as being the original software. 21 * 22 * 3. This notice may not be removed or altered from any source distribution 23 * 24 * 25 * DSFML is based on SFML (Copyright Laurent Gomila) 26 */ 27 28 /** 29 * TCP is a connected protocol, which means that a TCP socket can only 30 * communicate with the host it is connected to. 31 * 32 * It can't send or receive anything if it is not connected. 33 * 34 * The TCP protocol is reliable but adds a slight overhead. It ensures that your 35 * data will always be received in order and without errors (no data corrupted, 36 * lost or duplicated). 37 * 38 * When a socket is connected to a remote host, you can retrieve informations 39 * about this host with the `getRemoteAddress` and `getRemotePort` functions. 40 * 41 * You can also get the local port to which the socket is bound (which is 42 * automatically chosen when the socket is connected), with the `getLocalPort` 43 * function. 44 * 45 * Sending and receiving data can use either the low-level or the high-level 46 * functions. The low-level functions process a raw sequence of bytes, and 47 * cannot ensure that one call to Send will exactly match one call to Receive at 48 * the other end of the socket. 49 * 50 * The high-level interface uses packets (see $(PACKET_LINK)), which are easier 51 * to use and provide more safety regarding the data that is exchanged. You can 52 * look at the $(PACKET_LINK) class to get more details about how they work. 53 * 54 * The socket is automatically disconnected when it is destroyed, but if you 55 * want to explicitely close the connection while the socket instance is still 56 * alive, you can call disconnect. 57 * 58 * Example: 59 * --- 60 * // ----- The client ----- 61 * 62 * // Create a socket and connect it to 192.168.1.50 on port 55001 63 * auto socket = new TcpSocket(); 64 * socket.connect("192.168.1.50", 55001); 65 * 66 * // Send a message to the connected host 67 * string message = "Hi, I am a client"; 68 * socket.send(message); 69 * 70 * // Receive an answer from the server 71 * char[1024] buffer; 72 * size_t received = 0; 73 * socket.receive(buffer, received); 74 * writeln("The server said: ", buffer[0 .. received]); 75 * 76 * // ----- The server ----- 77 * 78 * // Create a listener to wait for incoming connections on port 55001 79 * auto listener = TcpListener(); 80 * listener.listen(55001); 81 * 82 * // Wait for a connection 83 * auto socket = new TcpSocket(); 84 * listener.accept(socket); 85 * writeln("New client connected: ", socket.getRemoteAddress()); 86 * 87 * // Receive a message from the client 88 * char[1024] buffer; 89 * size_t received = 0; 90 * socket.receive(buffer, received); 91 * writeln("The client said: ", buffer[0 .. received]); 92 * 93 * // Send an answer 94 * string message = "Welcome, client"; 95 * socket.send(message); 96 * --- 97 * 98 * See_Also: 99 * $(SOCKET_LINK), $(UDPSOCKET_LINK), $(PACKET_LINK) 100 */ 101 module nudsfml.network.tcpsocket; 102 103 public import nudsfml.system.time; 104 105 import nudsfml.network.ipaddress; 106 import nudsfml.network.packet; 107 import nudsfml.network.socket; 108 109 import nudsfml.system.err; 110 111 /** 112 * Specialized socket using the TCP protocol. 113 */ 114 class TcpSocket:Socket 115 { 116 package sfTcpSocket* sfPtr; 117 118 /// Default constructor. 119 this() 120 { 121 sfPtr = sfTcpSocket_create(); 122 } 123 124 /// Destructor. 125 ~this() 126 { 127 import nudsfml.system.config; 128 mixin(destructorOutput); 129 sfTcpSocket_destroy(sfPtr); 130 } 131 132 /** 133 * Get the port to which the socket is bound locally. 134 * 135 * If the socket is not connected, this function returns 0. 136 * 137 * Returns: Port to which the socket is bound. 138 */ 139 ushort getLocalPort() const 140 { 141 return sfTcpSocket_getLocalPort(sfPtr); 142 } 143 144 /** 145 * Get the address of the connected peer. 146 * 147 * It the socket is not connected, this function returns `IpAddress.None`. 148 * 149 * Returns: Address of the remote peer. 150 */ 151 IpAddress getRemoteAddress() const 152 { 153 IpAddress temp; 154 155 sfTcpSocket_getRemoteAddress(sfPtr,&temp); 156 157 return temp; 158 } 159 160 /** 161 * Get the port of the connected peer to which the socket is connected. 162 * 163 * If the socket is not connected, this function returns 0. 164 * 165 * Returns: Remote port to which the socket is connected. 166 */ 167 ushort getRemotePort() const 168 { 169 return sfTcpSocket_getRemotePort(sfPtr); 170 } 171 172 /** 173 * Set the blocking state of the socket. 174 * 175 * In blocking mode, calls will not return until they have completed their 176 * task. For example, a call to `receive` in blocking mode won't return 177 * until some data was actually received. 178 * 179 * In non-blocking mode, calls will always return immediately, using the 180 * return code to signal whether there was data available or not. By 181 * default, all sockets are blocking. 182 * 183 * Params: 184 * blocking = true to set the socket as blocking, false for non-blocking 185 */ 186 void setBlocking(bool blocking) 187 { 188 sfTcpSocket_setBlocking(sfPtr, blocking); 189 } 190 191 /** 192 * Connect the socket to a remote peer. 193 * 194 * In blocking mode, this function may take a while, especially if the 195 * remote peer is not reachable. The last parameter allows you to stop 196 * trying to connect after a given timeout. 197 * 198 * If the socket was previously connected, it is first disconnected. 199 * 200 * Params: 201 * host = Address of the remote peer 202 * port = Port of the remote peer 203 * timeout = Optional maximum time to wait 204 * 205 * Returns: Status code. 206 */ 207 Status connect(IpAddress host, ushort port, Time timeout = Time.Zero) 208 { 209 return sfTcpSocket_connect(sfPtr, &host, port, timeout.asMicroseconds()); 210 } 211 212 /** 213 * Disconnect the socket from its remote peer. 214 * 215 * This function gracefully closes the connection. If the socket is not 216 * connected, this function has no effect. 217 */ 218 void disconnect() 219 { 220 sfTcpSocket_disconnect(sfPtr); 221 } 222 223 /** 224 * Tell whether the socket is in blocking or non-blocking mode. 225 * 226 * Returns: true if the socket is blocking, false otherwise. 227 */ 228 bool isBlocking() const 229 { 230 return (sfTcpSocket_isBlocking(sfPtr)); 231 } 232 233 /** 234 * Send raw data to the remote peer. 235 * 236 * This function will fail if the socket is not connected. 237 * 238 * Params: 239 * data = Sequence of bytes to send 240 * 241 * Returns: Status code. 242 */ 243 Status send(const(void)[] data) 244 { 245 return sfTcpSocket_send(sfPtr, data.ptr, data.length); 246 } 247 248 /** 249 * Send a formatted packet of data to the remote peer. 250 * 251 * This function will fail if the socket is not connected. 252 * 253 * Params: 254 * packet = Packet to send 255 * 256 * Returns: Status code. 257 */ 258 Status send(Packet packet) 259 { 260 import std.stdio; 261 //temporary packet to be removed on function exit 262 scope SfPacket temp = new SfPacket(); 263 264 //getting packet's "to send" data 265 temp.append(packet.onSend()); 266 267 //send the data 268 return sfTcpSocket_sendPacket(sfPtr, temp.sfPtr); 269 } 270 271 /** 272 * Receive raw data from the remote peer. 273 * 274 * In blocking mode, this function will wait until some bytes are actually 275 * received. This function will fail if the socket is not connected. 276 * 277 * Params: 278 * data = Array to fill with the received bytes 279 * sizeReceived = This variable is filled with the actual number of bytes 280 received 281 * 282 * Returns: Status code. 283 */ 284 Status receive(void[] data , out size_t sizeReceived) 285 { 286 return sfTcpSocket_receive(sfPtr, data.ptr, data.length, &sizeReceived); 287 } 288 289 /** 290 * Receive a formatted packet of data from the remote peer. 291 * 292 * In blocking mode, this function will wait until the whole packet has been 293 * received. This function will fail if the socket is not connected. 294 * 295 * Params: 296 * packet = Packet to fill with the received data 297 * 298 * Returns: Status code. 299 */ 300 Status receive(Packet packet) 301 { 302 //temporary packet to be removed on function exit 303 scope SfPacket temp = new SfPacket(); 304 305 //get the sent data 306 Status status = sfTcpSocket_receivePacket(sfPtr, temp.sfPtr); 307 308 //put data into the packet so that it can process it first if it wants. 309 packet.onRecieve(temp.getData()); 310 311 return status; 312 } 313 } 314 315 unittest 316 { 317 //TODO: Expand to use more methods in TcpSocket 318 version(DSFML_Unittest_Network) 319 { 320 import std.stdio; 321 import nudsfml.network.tcplistener; 322 323 writeln("Unittest for Tcp Socket"); 324 325 //socket connecting to server 326 auto clientSocket = new TcpSocket(); 327 328 //listener looking for new sockets 329 auto listener = new TcpListener(); 330 listener.listen(55003); 331 332 //get our client socket to connect to the server 333 clientSocket.connect(IpAddress.LocalHost, 55003); 334 335 //packet to send data 336 auto sendPacket = new Packet(); 337 338 //Packet to receive data 339 auto receivePacket = new Packet(); 340 341 //socket on the server side connected to the client's socket 342 auto serverSocket = new TcpSocket(); 343 344 //accepts a new connection and binds it to the socket in the parameter 345 listener.accept(serverSocket); 346 347 string temp = "I'm sending you stuff!"; 348 349 //Let's greet the server! 350 //sendPacket.writeString("Hello, I'm a client!"); 351 //clientSocket.send(sendPacket); 352 353 clientSocket.send(temp); 354 355 //And get the data on the server side 356 //serverSocket.receive(receivePacket); 357 358 char[1024] temp2; 359 size_t received; 360 361 serverSocket.receive(temp2, received); 362 363 //What did we get from the client? 364 writeln("Gotten from client: ", cast(string)temp2[0..received]); 365 366 //clear the packets to send/get new information 367 sendPacket.clear(); 368 receivePacket.clear(); 369 370 //Respond back to the client 371 sendPacket.write("Hello, I'm your server."); 372 373 serverSocket.send(sendPacket); 374 375 clientSocket.receive(receivePacket); 376 377 string message; 378 receivePacket.read!string(message); 379 writeln("Gotten from server: ", message); 380 381 clientSocket.disconnect(); 382 writeln(); 383 } 384 } 385 386 package extern(C): 387 388 struct sfTcpSocket; 389 390 //Create a new TCP socket 391 sfTcpSocket* sfTcpSocket_create(); 392 393 //Destroy a TCP socket 394 void sfTcpSocket_destroy(sfTcpSocket* socket); 395 396 //Set the blocking state of a TCP listener 397 void sfTcpSocket_setBlocking(sfTcpSocket* socket, bool blocking); 398 399 //Tell whether a TCP socket is in blocking or non-blocking mode 400 bool sfTcpSocket_isBlocking(const(sfTcpSocket)* socket); 401 402 //Get the port to which a TCP socket is bound locally 403 ushort sfTcpSocket_getLocalPort(const(sfTcpSocket)* socket); 404 405 //Get the address of the connected peer of a TCP socket 406 void sfTcpSocket_getRemoteAddress(const(sfTcpSocket)* socket, IpAddress* ipAddress); 407 408 //Get the port of the connected peer to which a TCP socket is connected 409 ushort sfTcpSocket_getRemotePort(const(sfTcpSocket)* socket); 410 411 //Connect a TCP socket to a remote peer 412 Socket.Status sfTcpSocket_connect(sfTcpSocket* socket, IpAddress* host, ushort port, long timeout); 413 414 //Disconnect a TCP socket from its remote peer 415 void sfTcpSocket_disconnect(sfTcpSocket* socket); 416 417 //Send raw data to the remote peer of a TCP socket 418 Socket.Status sfTcpSocket_send(sfTcpSocket* socket, const void* data, size_t size); 419 420 //Receive raw data from the remote peer of a TCP socket 421 Socket.Status sfTcpSocket_receive(sfTcpSocket* socket, void* data, size_t maxSize, size_t* sizeReceived); 422 423 //Send a formatted packet of data to the remote peer of a TCP socket 424 Socket.Status sfTcpSocket_sendPacket(sfTcpSocket* socket, sfPacket* packet); 425 426 //Receive a formatted packet of data from the remote peer 427 Socket.Status sfTcpSocket_receivePacket(sfTcpSocket* socket, sfPacket* packet);