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