RaspberrPi project source code
guowenxue
2024-04-11 69b42a43ca4b2d93be203c34f6b45f5de1e32a15
commit | author | age
29b331 1 /********************************************************************************
GW 2  *      Copyright:  (C) 2022 LingYun IoT System Studio
3  *                  All rights reserved.
4  *
5  *       Filename:  socket.c
6  *    Description:  This file is for socket API functions
7  *
8  *        Version:  1.0.0(18/04/22)
9  *         Author:  Guo Wenxue <guowenxue@gmail.com>
10  *      ChangeLog:  1, Release initial version on "18/04/22 17:09:59"
11  *
12  ********************************************************************************/
13 #include <stdio.h>
14 #include <unistd.h>
15 #include <string.h>
16 #include <netdb.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #include <sys/un.h>
20 #include <poll.h>
21 #include <errno.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <linux/sockios.h>
25 #include <sys/ioctl.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <netinet/tcp.h>
29 #include <arpa/inet.h>
30 #include <sys/resource.h>
31
32 #include "socket.h"
33 #include "logger.h"
34
35 /*  description: initial socket context
36  *   input args:
37  *               $sock:  socket context pointer
38  *               $host:  connect server hostname for client mode, unused for server mode
39  *               $port:  connect server port for client mode or listen port for server mode
40  * return value: <0: failure   0:ok
41  */
42 int socket_init(socket_ctx_t *sock, char *host, int port)
43 {
44     if( !sock || port<=0 )
45         return -1;
46
47     memset( sock, 0, sizeof(*sock) );
48     sock->fd = -1;
49     sock->port = port;
50     if( host ) /* server no need it */
51     {
52         strncpy(sock->host, host, HOSTNAME_LEN);
53     }
54
55     return 0;
56 }
57
58 /*  description: close socket
59  *   input args:
60  *               $sock:  socket context pointer
61  * return value: <0: failure   0:ok
62  */
63 int socket_term(socket_ctx_t *sock)
64 {
65     if( !sock )
66         return -1;
67
68     if( sock->fd > 0)
69     {
70         close(sock->fd);
71         sock->fd = -1;
b74ad5 72         sock->connected = 0;
29b331 73     }
GW 74
75     return 0;
76 }
77
78 /*  description: socket server start listen
79  *   input args:
80  *               $sock:  socket context pointer
81  * return value: <0: failure   0:ok
82  */
83 #if 0 /* --TBD-- */
84 int socket_listen(socket_ctx_t *sock)
85 {
86     int                 rv = 0;
87     struct sockaddr_in  addr;
88     int                 backlog = 13;
89
90     if( !sock )
91         return -1;
92
93     set_socket_rlimit(); /* set max open socket count */
94 }
95 #endif
b74ad5 96
GW 97 /*  description: check socket connect status
98  *   input args:
99  *               $sock:  socket context pointer
100  * return value: 1: connected   0:disconnected
101  */
102 int socket_connected(socket_ctx_t *sock)
103 {
104     struct tcp_info   info;
105     int               len=sizeof(info);
106     int               changed = 0;
107
108     if( !sock )
109     {
110         return 0;
111     }
112
113     if( sock->fd < 0 )
114     {
115         /* socket is connected before but got disconnected now */
116         changed = sock->connected ? 1 : 0;
117         sock->connected = 0;
118         goto out;
119     }
120
121     getsockopt(sock->fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
122     if( TCP_ESTABLISHED==info.tcpi_state )
123     {
124         /* socket is disconnected before but got connected now */
125         changed = !sock->connected ? 1 : 0;
126         sock->connected = 1;
127     }
128     else
129     {
130         /* socket is connected before but got disconnected now */
131         changed = sock->connected ? 1 : 0;
132         sock->connected = 0;
133     }
134
135 out:
136     if( changed )
137     {
138         log_info("socket status got %s\n", sock->connected?"connected":"disconnected");
139     }
140     return sock->connected;
141 }
29b331 142
GW 143 /*  description: socket connect to server in block mode
144  *   input args:
145  *               $sock:  socket context pointer
146  * return value: <0: failure   0:ok
147  */
148 int socket_connect(socket_ctx_t *sock)
149 {
150     int                 rv = 0;
151     int                 sockfd = 0;
152     char                service[20];
153     struct addrinfo     hints, *rp;
154     struct addrinfo    *res = NULL;
155     struct in_addr      inaddr;
156     struct sockaddr_in  addr;
157     int                 len = sizeof(addr);
158
159     if( !sock )
160         return -1;
161
162     socket_term(sock);
163
164     /*+--------------------------------------------------+
165      *| use getaddrinfo() to do domain name translation  |
166      *+--------------------------------------------------+*/
167
168     memset(&hints, 0, sizeof(struct addrinfo));
169     hints.ai_family = AF_INET; /* Only support IPv4 */
170     hints.ai_socktype = SOCK_STREAM;
171     hints.ai_protocol = IPPROTO_TCP; /* TCP protocol */
172
173     /* If $host is a valid IP address, then don't use name resolution */
174     if( inet_aton(sock->host, &inaddr) )
175     {
176         //log_info("%s is a valid IP address, don't use domain name resolution.\n", sock->host);
177         hints.ai_flags |= AI_NUMERICHOST;
178     }
179
180     /* Obtain address(es) matching host/port */
181     snprintf(service, sizeof(service), "%d", sock->port);
182     if( (rv=getaddrinfo(sock->host, service, &hints, &res)) )
183     {
184         log_error("getaddrinfo() parser [%s:%s] failed: %s\n", sock->host, service, gai_strerror(rv));
185         return -3;
186     }
187
188     /* getaddrinfo() returns a list of address structures. Try each
189        address until we successfully connect or bind */
190     for (rp=res; rp!=NULL; rp=rp->ai_next)
191     {
192 #if 0
193         char                  ipaddr[INET_ADDRSTRLEN];
194         struct sockaddr_in   *sp = (struct sockaddr_in *) rp->ai_addr;
195
196         /* print domain name translation result */
197         memset( ipaddr, 0, sizeof(ipaddr) );
198         if( inet_ntop(AF_INET, &sp->sin_addr, ipaddr, sizeof(ipaddr)) )
199         {
200             log_info("domain name resolution [%s->%s]\n", sock->host, ipaddr);
201         }
202 #endif
203
204         /*  Create the socket */
205         sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
206         if( sockfd < 0)
207         {
208             log_error("socket() create failed: %s\n", strerror(errno));
209             rv = -3;
210             continue;
211         }
212
213         /* connect to server */
214         rv = connect(sockfd, rp->ai_addr, len);
215         if( 0 == rv )
216         {
217             sock->fd = sockfd;
218             log_info("Connect to server[%s:%d] on fd[%d] successfully!\n", sock->host, sock->port, sockfd);
219             break;
220         }
221         else
222         {
223             /* socket connect get error, try another IP address */
224             close(sockfd);
225             continue;
226         }
227     }
228
229     freeaddrinfo(res);
230     return rv;
231 }
232
233 /*  description: send data from the socket
234  *   input args:
235  *               $sock :  socket context pointer
236  *               $data :  socket send data
237  *               $bytes:  socket send data bytes
238  * return value: <0: failure   0:ok
239  */
240 int socket_send(socket_ctx_t *sock, char *data, int bytes)
241 {
242     int            rv = 0;
243     int            i = 0;
244     int            left_bytes = bytes;
245
246     if( !sock || !data || bytes<= 0 )
247         return -1;
248
249     while( left_bytes > 0 )
250     {
251         rv=write(sock->fd, &data[i], left_bytes);
252         if( rv < 0 )
253         {
254             log_info("socket[%d] write() failure: %s, close socket now\n", sock->fd, strerror(errno));
255             socket_term(sock);
256             return -2;
257         }
258         else if( rv == left_bytes )
259         {
260             log_info("socket send %d bytes data over\n", bytes);
261             return 0;
262         }
263         else
264         {
265             /* not send over this time, continue to send left data  */
266             i += rv;
267             left_bytes -= rv;
268             continue;
269         }
270     }
271
272     return 0;
273 }
274
275 /*  description: receive data from the socket
276  *   input args:
277  *               $sock :  socket context pointer
278  *               $buf  :  socket receive data buffer
279  *               $size :  socket receive data buffer size
280  *               $timeout: receive data time, <=0 will don't timeout
281  * return value: <0: failure   0:ok
282  */
283 int socket_recv(socket_ctx_t *sock, char *buf, int size, int timeout)
284 {
285     int               rv = 0;
286     fd_set            rdset;
287     int               maxfd;
288
289     if( !sock || !buf || size<= 0 )
290         return -1;
291
292     memset(buf, 0, size);
293
294     maxfd = sock->fd;
295     FD_ZERO(&rdset);
296     FD_SET(sock->fd, &rdset);
297
298     if( timeout <= 0 ) /* no timeout  */
299     {
300         rv=select(maxfd+1, &rdset, NULL, NULL, NULL);
301     }
302     else
303     {
304         struct timeval    tv;
305         tv.tv_sec = timeout;
306         tv.tv_usec = 0;
307         rv=select(maxfd+1, &rdset, NULL, NULL, &tv);
308     }
309
310     if( rv < 0 )
311     {
312         log_error("select() on socket[%d] got error: %s\n", sock->fd, strerror(errno));
313         return -2;
314     }
315     else if( rv == 0 )
316     {
317         log_error("select() on socket[%d] get timeout\n", sock->fd);
318         return 0;
319     }
320     else
321     {
322         rv = read(sock->fd, buf, size);
323         if( rv <= 0 )
324         {
325             log_error("socket[%d] read() failure or got disconnected: %s, close socket now\n", sock->fd, strerror(errno));
326             socket_term(sock);
327             return -2;
328         }
329         else
330         {
331             log_debug("socket[%d] receive %d bytes data\n", sock->fd, rv);
332             return rv;
333         }
334     }
335 }
336
337
338 /*+-------------------------------------------------------------------+
339  *|                socket utils function                              |
340  *+-------------------------------------------------------------------+*/
341
342 /* description: set socket listen port as reusable, fix port already used bug  */
343 int socket_set_reuseaddr(int sockfd)
344 {
345     int opt = 1;
346     int len = sizeof (int);
347
348     if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &opt, len))
349     {
350         log_error("Set socket[%d] option SO_REUSEADDR failed:%s\n", sockfd, strerror(errno));
351         return -1;
352     }
353     log_debug("Set socket[%d] option SO_REUSEADDR ok\n", sockfd);
354
355     return 0;
356 }
357
358 /* set socket as non-block mode, common socket default work as block mode */
359 int socket_set_nonblock(int sockfd)
360 {
361     int opts;
362     /*
363      * fcntl may set:
364      *
365      * EACCES, EAGAIN: Operation is prohibited by locks held by other
366      *          processes. Or, operation is prohibited because the file has
367      *          been memory-mapped by another process.
368      * EBADF:   fd is not an open file descriptor, or the command was F_SETLK
369      *          or F_SETLKW and the file descriptor open mode doesn't match
370      *          with the type of lock requested.
371      * EDEADLK: It was detected that the specified F_SETLKW command would
372      *          cause a deadlock.
373      * EFAULT:  lock is outside your accessible address space.
374      * EINTR:   For F_SETLKW, the command was interrupted by a signal. For
375      *          F_GETLK and F_SETLK, the command was interrupted by a signal
376      *          before the lock was checked or acquired. Most likely when
377      *          locking a remote file (e.g. locking over NFS), but can
378      *          sometimes happen locally.
379      * EINVAL:  For F_DUPFD, arg is negative or is greater than the maximum
380      *          allowable value. For F_SETSIG, arg is not an allowable signal
381      *          number.
382      * EMFILE:  For F_DUPFD, the process already has the maximum number of
383      *          file descriptors open.
384      * ENOLCK:  Too many segment locks open, lock table is full, or a remote
385      *          locking protocol failed (e.g. locking over NFS).
386      * EPERM:   Attempted to clear the O_APPEND flag on a file that has the
387      *          append-only attribute set.
388      */
389     opts = fcntl(sockfd, F_GETFL);
390     if (opts < 0)
391     {
392         log_warn("fcntl() get socket options failure: %s\n", strerror(errno));
393         return -1;
394     }
395
396     opts |= O_NONBLOCK;
397
398     if (fcntl(sockfd, F_SETFL, opts) < 0)
399     {
400         log_warn("fcntl() set socket options failure: %s\n", strerror(errno));
401         return -1;
402     }
403
404     log_debug("Set socket[%d] none blocking\n", sockfd);
405     return opts;
406 }
407
408
409 /* set socket receive and send buffer size in linux kernel space */
410 int socket_set_buffer(int sockfd, int rsize, int ssize)
411 {
412     int        opt;
413     socklen_t  optlen = sizeof(opt);
414
415     if(sockfd < 0)
416         return -1;
417
418     /* Get system default receive buffer size, Linux X86: 85K */
419     if (getsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, (char *) &opt, &optlen))
420     {
421         log_warn("getsockopt() get receive buffer failure: %s\n", strerror(errno));
422         return -2;
423     }
424
425     /* Only when current receive buffer size larger than the default one will change it  */
426     if(rsize > opt)
427     {
428         opt = (int) rsize;
429         if (setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, (char *) &opt, optlen))
430         {
431             log_warn("setsockopt() set receive buffer to %d failure: %s\n", opt, strerror(errno));
432             return -2;
433         }
434     }
435
436     /* Get system default send buffer size, Linux X86: 16K */
437     if (getsockopt (sockfd, SOL_SOCKET, SO_SNDBUF, (char *) &opt, &optlen))
438     {
439         log_warn("getsockopt() get send buffer failure: %s\n", strerror(errno));
440         return -3;
441     }
442
443     /* Only when current receive buffer size larger than the default one will change it  */
444     if(ssize > opt)
445     {
446         opt = (int) ssize;
447         if (setsockopt (sockfd, SOL_SOCKET, SO_SNDBUF, (char *) &opt, optlen))
448         {
449             log_warn("setsockopt() set send buffer to %d failure: %s\n", opt, strerror(errno));
450             return -3;
451         }
452     }
453
454     log_info("Set socket[%d] RCVBUF size:%d  SNDBUF size:%d\n", sockfd, rsize, ssize);
455     return 0;
456 }
457
458 /*
459  * Enable socket SO_KEEPALIVE, if the connection disconnected, any system call on socket
460  * will return immediately and errno will be set to "WSAENOTCONN"
461  *
462  * keepalive is not program related, but socket related, * so if you have multiple sockets,
463  * you can handle keepalive for each of them separately.
464  *
465  * Reference: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
466  */
467 int socket_set_keepalive(int sockfd, int keepintvl, int keepcnt)
468 {
469     int  opt;
470
471     if(sockfd < 0)
472         return -1;
473
474     /* Enable the KEEPALIVE flag */
475     opt = 1;
476     if (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof (opt)))
477     {
478         log_warn("setsockopt() enable SO_KEEPALIVE failure: %s\n", strerror(errno));
479         return -2;
480     }
481
482     if(keepintvl || keepcnt)
483     {
484         /*
485          *  The tcp_keepidle parameter specifies the interval between the last data packet sent
486          *  (simple ACKs are not considered data) and the first keepalive probe; after the
487          *  connection is marked to need keepalive, this counter is not used any further.
488          *  ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_time
489          *  7200
490          */
491         opt = 3; /* 3 seconds  */
492         if (setsockopt (sockfd, SOL_TCP, TCP_KEEPIDLE, (char *) &opt, sizeof (opt)))
493         {
494             log_error("setsockopt() set TCP_KEEPIDLE to %d seconds failure: %s\n", opt, strerror(errno));
495             return -3;
496         }
497
498         if((opt=keepintvl) > 0)
499         {
500             /*
501              * The tcp_keepintvl parameter specifies the interval between subsequential keepalive
502              * probes, regardless of what the connection has exchanged in the meantime.
503              * ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_intvl
504              * 75
505              */
506             if (setsockopt (sockfd, SOL_TCP, TCP_KEEPINTVL, (char *) &opt, sizeof (opt)))
507             {
508                 log_error("setsockopt() set TCP_KEEPINTVL to %d failure: %s\n", opt, strerror(errno));
509                 return -4;
510             }
511         }
512
513         if((opt=keepcnt) > 0)
514         {
515             /*
516              * The TCP_KEEPCNT option specifies the maximum number of unacknowledged probes to
517              * send before considering the connection dead and notifying the application layer
518              * probes to be sent. The value of TCP_KEEPCNT is an integer value between 1 and n,
519              * where n is the value of the systemwide tcp_keepcnt parameter.
520              * ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_probes
521              * 9
522              */
523             if (setsockopt (sockfd, SOL_TCP, TCP_KEEPCNT, (char *) &opt, sizeof (opt)))
524             {
525                 log_error("setsockopt() set TCP_KEEPCNT to %d failure: %s\n", opt, strerror(errno));
526                 return -5;
527             }
528         }
529     }
530
531     log_debug("Set socket[%d] KEEPINTVL:%d  KEEPCNT:%d\n", sockfd, keepintvl, keepcnt);
532     return 0;
533 }
534
535
536 /* Set open file description count to max */
537 void set_socket_rlimit(void)
538 {
539     struct rlimit limit = {0};
540
541     getrlimit(RLIMIT_NOFILE, &limit );
542     limit.rlim_cur  = limit.rlim_max;
543     setrlimit(RLIMIT_NOFILE, &limit );
544
545     log_info("set socket open fd max count to %d\n", limit.rlim_max);
546 }
547