nettobac  0.0.0
Network features for FreeBASIC code
nettobac.bas
Go to the documentation of this file.
1 /'* \file nettobac.bas
2 
3 This file contains the function bodies of the classes, designed to
4 handle network client and server connections.
5 
6 Copyright (C) LGPLv2.1, see ReadMe.md for details.
7 
8 \since 0.0.0
9 '/
10 
11 
12 #INCLUDE ONCE "nettobac.bi"
13 
14 
15 /'* \brief Create a connection
16 \param Socket the socket to use
17 \param Ep the pointer for error messages
18 
19 This constructor checks the socket and sets some options for fast
20 access.
21 
22 \since 0.0.0
23 '/
24 CONSTRUCTOR n2bConnection(BYVAL Socket AS LONG, BYVAL Ep AS ZSTRING PTR PTR)
25  Sock = Socket
26  Errr = Ep
27  DIM AS LONG tmp = 1
28  IF setsockopt(Sock, IPPROTO_TCP, TCP_NODELAY, CAST(ANY PTR, @tmp), SIZEOF(tmp)) = SOCKET_ERROR _
29  THEN *Errr = @"setsockopt"
30 END CONSTRUCTOR
31 
32 
33 /'* \brief Finish a connection
34 
35 This destructor closes the open socket, if any.
36 
37 \since 0.0.0
38 '/
39 DESTRUCTOR n2bConnection()
40  IF Sock = SOCKET_ERROR THEN EXIT DESTRUCTOR
41  closesocket(Sock)
42 END DESTRUCTOR
43 
44 
45 /'* \brief Send a `STRING` over socket
46 \param Dat the data to send
47 \param ReTry the number of re-tries when socket isn't ready
48 \returns the number of bytes sent (-1 in case of error)
49 
50 This function sends a `STRING` variable to the peer, sending all bytes
51 from parameter `Dat`.
52 
53 The function waits until the socket is ready to send. The maximum
54 waiting time can get specified by parameter `ReTry` in steps of 1 / 50
55 seconds.
56 
57 \note `ReTry = 0` specifies a single shot.
58 
59 \since 0.0.0
60 '/
61 FUNCTION n2bConnection.nPut(BYVAL Dat AS STRING, BYVAL ReTry AS USHORT = 100) AS INTEGER
62  RETURN nPut(SADD(Dat), LEN(Dat), ReTry)
63 END FUNCTION
64 
65 
66 /'* \brief Send any data over socket
67 \param Dat a pointer to the data in memory
68 \param Az the number of bytes to send
69 \param ReTry the number of re-tries when socket isn't ready
70 \returns the number of bytes sent (-1 in case of error)
71 
72 This function sends data to the peer, reading `Az` number of bytes from
73 the buffer `Dat`.
74 
75 The function waits until the socket is ready to send. The maximum
76 waiting time can get specified by parameter `ReTry` in steps of 1 / 50
77 seconds.
78 
79 \note `ReTry = 0` specifies a single shot.
80 
81 \since 0.0.0
82 '/
83 FUNCTION n2bConnection.nPut(BYVAL Dat AS ANY PTR, BYVAL Az AS INTEGER, BYVAL ReTry AS USHORT = 100) AS INTEGER
84  IF Sock = SOCKET_ERROR THEN *Errr = @"put socket check" : RETURN -1
85  IF Dat = 0 ORELSE Az < 1 THEN *Errr = @"put data check" : RETURN -1
86  FD_ZERO(@FdsW)
87  DIM AS INTEGER try = ReTry
88  DO
89  FD_SET_(Sock, @FdsW)
90  IF select_(Sock + 1, 0, @FdsW, 0, @Timeout) = SOCKET_ERROR _
91  THEN *Errr = @"select" : RETURN -1
92  try -= 1 : IF try < -1 THEN *Errr = @"retry" : RETURN -1
93  SLEEP 20
94  LOOP UNTIL FD_ISSET(Sock, @FdsW)
95  VAR x = Az
96  DO
97  VAR n = send(Sock, Dat, x, 0)
98  IF n = SOCKET_ERROR THEN *Errr = @"send data" : RETURN -1
99  Dat += n
100  x -= n
101  LOOP UNTIL x <= 0 : RETURN Az - x
102 END FUNCTION
103 
104 
105 /'* \brief Receive data form socket.
106 \param Res The `STRING` variable to append the downloaded bytes
107 \param ReTry A counter to limit the number of re-tries
108 \returns 0 (zero) on success, an error message otherwise
109 
110 This function receives data from the peer. The incomming bytes get
111 appended to the result variable `Res`.
112 
113 The function waits until the socket is ready to send before each chunk
114 of data (1024 bytes). The waiting time can get limited by parameter
115 `ReTry` in steps of 1 / 50 seconds.
116 
117 \note The maximum waiting time depend on the value of `ReTry` and the
118  number of chunks to send.
119 
120 \note `ReTry = 0` specifies a single shot.
121 
122 \since 0.0.0
123 '/
124 FUNCTION n2bConnection.nGet(BYREF Res AS STRING, BYVAL ReTry AS USHORT = 100) AS CONST ZSTRING CONST PTR
125  IF Sock = SOCKET_ERROR THEN *Errr = @"get socket check" : RETURN *Errr
126  CONST size = &h400
127  DIM AS STRING*size buf
128  FD_ZERO(@FdsR)
129  DO
130  DIM AS INTEGER try = ReTry
131  DO
132  FD_SET_(Sock, @FdsR)
133  IF select_(Sock + 1, @FdsR, 0, 0, @Timeout) = SOCKET_ERROR _
134  THEN *Errr = @"select" : RETURN *Errr
135  try -= 1 : IF try < -1 THEN *Errr = @"retry" : RETURN *Errr
136  SLEEP 20
137  LOOP UNTIL FD_ISSET(Sock, @FdsR)
138  VAR n = recv(Sock, CAST(UBYTE PTR, @buf), size, 0)
139  SELECT CASE n
140  CASE SOCKET_ERROR : *Errr = @"receive data" : RETURN *Errr
141  CASE 0 : IF 0 = LEN(Res) THEN *Errr = @"disconnected" : RETURN *Errr
142  EXIT DO
143  CASE ELSE : Res &= LEFT(buf, n)
144  END SELECT
145  LOOP : RETURN 0
146 END FUNCTION
147 
148 
149 /'* \brief Constructor to open the socket
150 
151 The constructor opens a socket for the new instance (client or server)
152 and checks the result.
153 
154 \since 0.0.0
155 '/
156 CONSTRUCTOR n2bFactory()
157  Sock = opensocket(AF_INET, SOCK_STREAM, 0)
158  IF Sock = SOCKET_ERROR THEN Errr = @"opensocket"
159 END CONSTRUCTOR
160 
161 
162 /'* \brief Destructor to close all opened connections and the socket.
163 
164 The destructor `DELETE`s all open connections and closes the socket
165 opened in the constructor.
166 
167 \since 0.0.0
168 '/
169 DESTRUCTOR n2bFactory()
170  FOR i AS INTEGER = 0 TO UBOUND(Slots)
171  DELETE Slots(i)
172  NEXT
173  IF Sock <> SOCKET_ERROR THEN closesocket(Sock)
174 END DESTRUCTOR
175 
176 
177 /'* \brief Generate a new connection and add the instance to array Slots
178 \param Socket The socket number for that new connection
179 \returns a pointer to the new connection
180 
181 This function collects pointers to newly created connections in the
182 array Slots, in order to auto `DELETE` the instances in the destructor.
183 If you want to get rid of a connection before the destructor gets
184 called, use method nClose().
185 
186 \since 0.0.0
187 '/
188 FUNCTION n2bFactory.slot(BYVAL Socket AS LONG) AS n2bConnection PTR
189  'IF Socket = SOCKET_ERROR THEN Errr = @"slot socket check" : RETURN 0
190  VAR r = NEW n2bConnection(Socket, @Errr) _
191  , u = UBOUND(Slots) + 1
192  IF r THEN REDIM PRESERVE Slots(u) : Slots(u) = r
193  RETURN r
194 END FUNCTION
195 
196 
197 /'* \brief Close a connection
198 \param Con The pointer to the connection instance
199 \returns 0 (zero) on success, an error message otherwise
200 
201 Call this function in order to close a connection.
202 
203 \note The destructor n2bFactory::~n2bFactory() will close all remaining
204  connections, so it's optional to call this function.
205 
206 \since 0.0.0
207 '/
208 FUNCTION n2bFactory.nClose(BYVAL Con AS n2bConnection PTR) AS CONST ZSTRING CONST PTR
209  VAR u = UBOUND(Slots)
210  FOR i AS INTEGER = 0 TO u
211  IF Slots(i) <> Con THEN CONTINUE FOR
212  DELETE Con
213  Slots(i) = Slots(u)
214  IF u > 0 THEN REDIM PRESERVE Slots(u - 1) : RETURN 0
215  REDIM PRESERVE Slots(-1 TO -1) : RETURN 0
216  NEXT : Errr = @"find connection" : RETURN Errr
217 END FUNCTION
218 
219 
220 /'* \brief Generate a client instance
221 \param Uri the URI adress to connect to (ie. `"www.freebasic.net"`)
222 \param Port the port number to use (defaults to 80)
223 
224 Create a client instance to the specified server adress, resolve its IP
225 and connect to the destination port.
226 
227 \since 0.0.0
228 '/
229 CONSTRUCTOR nettobacClient(BYREF Uri AS STRING, BYVAL Port AS USHORT = 80)
230  BASE()
231  IF Sock = SOCKET_ERROR THEN EXIT CONSTRUCTOR
232  VAR he = gethostbyname(SADD(Uri))
233  IF he = 0 THEN Errr = @"client resolve IP" : EXIT CONSTRUCTOR
234 
235  DIM AS sockaddr_in sadr
236  sadr.sin_family = AF_INET
237  sadr.sin_port = htons(Port)
238  sadr.sin_addr = *CPTR(in_addr PTR, he->h_addr_list[0])
239 
240  IF connect(Sock, CPTR(sockaddr PTR, @sadr), SIZEOF(sockaddr)) < 0 _
241  THEN Errr = @"client connect"
242 END CONSTRUCTOR
243 
244 
245 /'* \brief Open a client connection to a server
246 \returns a newly created connection (or zero on failure)
247 
248 This function opens a client connection to the server, which was
249 specified in the privious constructor call. The connection is ready to
250 send or receive data.
251 
252 Use function n2bFactory::nClose() to close the connection.
253 
254 \since 0.0.0
255 '/
256 VIRTUAL FUNCTION nettobacClient.nOpen() AS n2bConnection PTR
257  IF Sock = SOCKET_ERROR THEN Errr = @"client socket check" : RETURN 0
258  RETURN slot(Sock)
259 END FUNCTION
260 
261 
262 /'* \brief Generate a server instance
263 \param Port the port number to use (defaults to 80)
264 \param Max The maximum number of client connections
265 
266 Create a server instance for a limited number of clients and start
267 listening on the specified port.
268 
269 \since 0.0.0
270 '/
271 CONSTRUCTOR nettobacServer(BYVAL Port AS USHORT = 80, BYVAL Max AS INTEGER = 64)
272  BASE() : IF Sock = SOCKET_ERROR THEN EXIT CONSTRUCTOR
273 
274  DIM AS LONG yes = 1
275  IF SOCKET_ERROR = setsockopt(Sock, SOL_SOCKET, SO_REUSEADDR, @yes, SIZEOF(LONG)) _
276  THEN Errr = @"setsockopt"
277 
278  DIM AS sockaddr_in sadr
279  sadr.sin_family = AF_INET
280  sadr.sin_port = htons(port)
281  sadr.sin_addr.s_addr = INADDR_ANY
282  IF bind(Sock, CPTR(sockaddr PTR, @sadr), SIZEOF(sockaddr)) = SOCKET_ERROR THEN
283  Errr = @"server bind"
284  ELSEIF listen(Sock, Max) = SOCKET_ERROR THEN
285  Errr = @"server listen"
286  END IF
287 END CONSTRUCTOR
288 
289 
290 /'* \brief Open a server connection to a client
291 \returns A pointer to a new n2bConnection instance (or zero on failure)
292 
293 This function opens a connection to a client, if a request is pending.
294 It opens a new #n2bConnection to the client and returns its pointer.
295 Otherwise it returns 0 (zero), meaning there is no client connection
296 request pending.
297 
298 Use function n2bFactory::nClose() to close the connection.
299 
300 \note The `BASE` class n2bFactory will close all remaining connections
301  in its destructor, so it's optional to call function
302  n2bFactory::nClose().
303 
304 \since 0.0.0
305 '/
306 VIRTUAL FUNCTION nettobacServer.nOpen() AS n2bConnection PTR
307  DIM AS fd_set readfd
308  FD_ZERO(@readfd)
309  FD_SET_(Sock, @readfd)
310  IF select_(Sock + 1, @readfd, 0, 0, @Timeout) = SOCKET_ERROR _
311  THEN Errr = @"server select" : RETURN 0
312  IF 0 = FD_ISSET(Sock, @readfd) THEN Errr = @"server isset" : RETURN 0
313  VAR clientsock = accept(Sock, 0, 0)
314  IF clientsock = SOCKET_ERROR THEN Errr = @"server accept" : RETURN 0
315  RETURN slot(clientsock)
316 END FUNCTION
317