/*********************************************************************************
|
* Copyright: (C) 2020 LingYun IoT System Studio
|
* All rights reserved.
|
*
|
* Filename: socket.c
|
* Description: This file is for socket API
|
*
|
* Version: 1.0.0(2020年04月16日)
|
* Author: Guo Wenxue <guowenxue@gmail.com>
|
* ChangeLog: 1, Release initial version on "2020年04月16日 13时43分28秒"
|
*
|
********************************************************************************/
|
|
#include "socket.h"
|
#include "logger.h"
|
|
static int proc_sock_connect(socket_t *sock);
|
|
/* description: initialise socket context and create socket fd
|
* input args: $sock: socket context
|
* $type: SOCK_TYPE_LISTEN, SOCK_TYPE_ACCEPT or SOCK_TYPE_CONNEC
|
* $create: call socket() create or not
|
* return value: <0: failure 0: successfully
|
*/
|
int socket_ctx_init(socket_t *sock, uint8_t type, int create)
|
{
|
int fd = -1;
|
|
if( !sock )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
memset(sock, 0, sizeof(*sock));
|
sock->type = type;
|
|
sock->keepintvl=600;
|
sock->keepcnt = 3;
|
|
if( create )
|
{
|
fd = socket(AF_INET, SOCK_STREAM, 0);
|
if( fd < 0 )
|
{
|
log_err("Create socket failure: %s\n", strerror(errno));
|
return -2;
|
}
|
|
sock->fd = fd;
|
}
|
else
|
{
|
sock->fd = -1;
|
}
|
|
sock->status = SOCK_STAT_INIT;
|
|
return 0;
|
}
|
|
/* description: close socket
|
* input args: $sock: socket context
|
* return value: <0: failure 0: successfully
|
*/
|
int socket_close(socket_t *sock)
|
{
|
int force = 0;
|
|
if( !sock )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
if(sock->fd < 0)
|
{
|
log_nrml("socket already closed\n");
|
return 0;
|
}
|
|
log_nrml("start close socket[%d] on port[%d] to [%s:%d]\n", sock->fd, sock->lport, sock->raddr, sock->rport);
|
|
if(force)
|
{
|
/* If l_onoff is nonzero and l_linger is zero, TCP aborts the connection when it is closed.
|
* That is, TCP discards any data still remaining in the socket send buffer and sends an RST
|
* to the peer, not the normal four-packet connection termination sequence. */
|
|
struct linger so_linger;
|
so_linger.l_onoff = 1; /* Turn on linger */
|
so_linger.l_linger = 0; /* Set the timeout to 0 */
|
|
setsockopt (sock->fd, SOL_SOCKET, SO_LINGER, (char *) &so_linger, sizeof (struct linger));
|
}
|
|
if( SOCK_TYPE_ACCEPT==sock->type || SOCK_TYPE_CONNECT==sock->type )
|
{
|
shutdown(sock->fd, SHUT_RDWR);
|
}
|
|
close(sock->fd);
|
|
sock->fd = -1;
|
sock->status = SOCK_STAT_INIT;
|
|
return 0;
|
}
|
|
/* description: create socket and listen on port $port
|
* input args: $sock: socket context
|
* $ipaddr: listen IP address, NULL for any address
|
* $port: listen port
|
* return value: <0: failure 0: successfully
|
*/
|
int socket_listen(socket_t *sock, char *ipaddr, int port)
|
{
|
int rv = 0;
|
int fd = -1;
|
struct sockaddr_in addr;
|
int backlog = 13;
|
|
if( !sock )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
if( sock->status != SOCK_STAT_INIT )
|
{
|
socket_close(sock);
|
}
|
|
/* initial listen socket bind address */
|
memset(&addr, 0, sizeof(addr));
|
addr.sin_family = AF_INET;
|
addr.sin_port = htons(port);
|
if( !ipaddr )
|
{
|
addr.sin_addr.s_addr = htons(INADDR_ANY);
|
}
|
else
|
{
|
if( !inet_pton(AF_INET, ipaddr, &addr.sin_addr) )
|
{
|
log_err("Listen IP address '%s' not valid\n");
|
return -2;
|
}
|
}
|
|
/* initial socket context and create socket fd */
|
if( socket_ctx_init(sock, SOCK_TYPE_LISTEN, SOCK_CREATE) < 0)
|
{
|
log_err("socket context initial failure\n");
|
return -2;
|
}
|
|
strncpy(sock->laddr, (!ipaddr?"0.0.0.0":ipaddr), sizeof(sock->laddr));
|
sock->lport = port;
|
memset(&sock->raddr, 0, sizeof(sock->raddr));
|
sock->rport = 0;
|
|
log_dbg("initial listen socket context ok\n");
|
|
/* set listen port reuseable */
|
socket_set_reuseaddr(sock->fd);
|
|
if( bind(sock->fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
{
|
log_err("bind listen socket failure: %s\n", strerror(errno));
|
rv = -3;
|
goto cleanup;
|
}
|
|
if( listen(sock->fd, backlog) < 0 )
|
{
|
log_err("Listen on socket[%d] failed: %s\n", sock->fd, strerror(errno));
|
rv = -4;
|
goto cleanup;
|
}
|
|
log_nrml("create socket and listen on [%s:%d] already\n", sock->laddr, sock->lport);
|
|
cleanup:
|
if( rv )
|
{
|
log_err("Create socket listen on [%s:%d] failed\n", sock->laddr, sock->lport);
|
socket_close(sock);
|
}
|
else
|
{
|
sock->status = SOCK_STAT_LISTENED;
|
log_nrml("Create socket[%p:%d] listen [%s:%d] ok\n", sock, sock->fd, sock->laddr, sock->lport);
|
}
|
|
return rv;
|
}
|
|
|
|
/* description: create socket and connect to server
|
* input args: $sock: socket context
|
* $host: IP address and port, format as "host:port", such as "127.0.0.1:8000"
|
* $block: block mode(1) or non-block(0)
|
*return value: <0: error 0: connecing in non-block mode 1:connected
|
*/
|
int socket_connect(socket_t *sock, int lport, char *host, int block)
|
{
|
int rv = 0;
|
char service[20];
|
struct addrinfo hints, *rp;
|
struct addrinfo *res = NULL;
|
struct in_addr inaddr;
|
struct sockaddr_in addr;
|
int len = sizeof(addr);
|
|
if( !sock )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
/* socket already connected */
|
if( SOCK_STAT_CONNECTED == sock->status )
|
{
|
return 1;
|
}
|
/* socket connecting in none-block mode */
|
else if( SOCK_STAT_CONNECTING == sock->status )
|
{
|
log_dbg("socket continue connect to remote server [%s:%d]\n", sock->raddr, sock->rport);
|
rv = proc_sock_connect(sock);
|
goto out;
|
}
|
|
/* socket not initial before */
|
if( SOCK_STAT_UNINIT== sock->status )
|
{
|
if( socket_ctx_init(sock, SOCK_TYPE_LISTEN, SOCK_NOT_CREATE) < 0)
|
{
|
log_err("ERROR: initial socket context failure\n");
|
return -2;
|
}
|
|
if( parser_host_port(sock, host)<0 || sock->rport<=0 )
|
{
|
log_err("parser connect server hostname and port [%s] failure\n", host);
|
return -3;
|
}
|
|
if( lport>0 )
|
{
|
sock->lport = lport;
|
}
|
|
log_dbg("initial socket context ok\n");
|
}
|
|
log_nrml("start create socket connect to server [%s:%d] now\n", sock->raddr, sock->rport);
|
|
|
/*+--------------------------------------------------+
|
*| use getaddrinfo() to do domain name translation |
|
*+--------------------------------------------------+*/
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
hints.ai_family = AF_INET; /* Only support IPv4 */
|
hints.ai_socktype = SOCK_STREAM;
|
hints.ai_protocol = IPPROTO_TCP; /* TCP protocol */
|
|
/* If 'raddr' is a valid IP address, then don't use name resolution */
|
if( inet_aton(sock->raddr, &inaddr) )
|
{
|
log_info("%s is a valid IP address, don't use domain name resolution.\n", sock->raddr);
|
hints.ai_flags |= AI_NUMERICHOST;
|
}
|
|
/* Obtain address(es) matching host/port */
|
snprintf(service, sizeof(service), "%d", sock->rport);
|
if( (rv=getaddrinfo(sock->raddr, service, &hints, &res)) )
|
{
|
log_err("getaddrinfo() parser [%s:%s] failed: %s\n", sock->raddr, service, gai_strerror(rv));
|
return -3;
|
}
|
|
/* close any opened socket on it */
|
socket_close(sock);
|
|
/* getaddrinfo() returns a list of address structures. Try each
|
address until we successfully connect or bind */
|
for (rp=res; rp!=NULL; rp=rp->ai_next)
|
{
|
char ipaddr[INET_ADDRSTRLEN];
|
struct sockaddr_in *sp = (struct sockaddr_in *) rp->ai_addr;
|
|
|
/* check and print domain name translation result */
|
memset( ipaddr, 0, sizeof(ipaddr) );
|
if( inet_ntop(AF_INET, &sp->sin_addr, ipaddr, sizeof(ipaddr)) )
|
{
|
log_nrml("domain name resolution [%s->%s]\n", sock->raddr, ipaddr);
|
}
|
|
memcpy(&sock->saddr, rp->ai_addr, sizeof(sock->saddr));
|
|
/* Create the socket */
|
sock->fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
if( sock->fd < 0)
|
{
|
log_err("socket() create failed: %s\n", strerror(errno));
|
rv = -3;
|
continue;
|
}
|
|
log_info("socket[%d] for connect create ok\n", sock->fd);
|
|
/* bind local port to socket if needed */
|
if(sock->lport > 0)
|
{
|
memset(&addr, 0, len);
|
addr.sin_family = AF_INET;
|
addr.sin_port = htons ((u_short) sock->lport);
|
|
if ( bind(sock->fd, (struct sockaddr *)&addr, len) )
|
{
|
rv = -4;
|
close(sock->fd);
|
log_err("Bind port[%d] to connect socket [%d] failed: %s\n", sock->lport, sock->fd, strerror(errno));
|
continue;
|
}
|
else
|
{
|
rv = 0;
|
log_dbg("Bind local port[%d] to connect socket [%d] OK\n", lport, sock->fd);
|
}
|
}
|
|
/* Set socket options */
|
if( !block )
|
{
|
socket_set_nonblock(sock->fd);
|
}
|
socket_set_keepalive(sock->fd, sock->keepintvl, sock->keepcnt);
|
|
if( (rv=proc_sock_connect(sock)) >= 0 )
|
{
|
/* connecting or connected already */
|
break;
|
}
|
else
|
{
|
/* socket connect get error, try another IP address */
|
close(sock->fd);
|
continue;
|
}
|
}
|
|
freeaddrinfo(res);
|
|
out:
|
return rv;
|
}
|
|
/* description: connect() server and check the connect result
|
* input args: $sock: socket context
|
*return value: <0: Error 0: Connecing in non-block mode 1:Connected
|
*/
|
static int proc_sock_connect(socket_t *sock)
|
{
|
int rv;
|
int len = sizeof(struct sockaddr);
|
|
if( !sock || sock->fd<0 )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
log_nrml("socket[%d] try to connect to server [%s:%d] now...\n", sock->fd, sock->raddr, sock->rport);
|
|
rv = connect(sock->fd, &sock->saddr, len);
|
if( 0 == rv )
|
{
|
sock->status = SOCK_STAT_CONNECTED;
|
rv = 1;
|
goto out;
|
}
|
|
/* rv < 0, connect failure will continue to check */
|
switch (errno)
|
{
|
case EISCONN:
|
sock->status = SOCK_STAT_CONNECTED;
|
rv = 1;
|
break;
|
|
case EALREADY:
|
case EINPROGRESS:
|
sock->status = SOCK_STAT_CONNECTING;
|
rv = 0;
|
break;
|
|
default:
|
sock->status = SOCK_STAT_UNINIT;
|
rv = -7;
|
break;
|
}
|
|
out:
|
if( rv > 0 )
|
{
|
log_nrml("socket[%d] connected to remote server [%s:%d]\n", sock->fd, sock->raddr, sock->rport);
|
}
|
else if ( rv == 0 )
|
{
|
log_nrml("socket[%d] connect to remote server [%s:%d] in progressing\n", sock->fd, sock->raddr, sock->rport);
|
}
|
else
|
{
|
log_err("socket[%d] connect to remote [%s:%d] failed: %s\n", sock->fd, sock->raddr, sock->rport, strerror(errno));
|
socket_close(sock);
|
}
|
|
return rv;
|
}
|
|
|
/* description: accept a new client socket
|
* input args: $sock: socket context
|
* $listenfd: listen socket fd
|
* return value: <0: failure 0: successfully
|
*/
|
int socket_accept(socket_t *sock, int listenfd)
|
{
|
if( !sock )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
}
|
|
|
/* description: send data to the socket, make sure all data send over.
|
* input args: $sock: socket context
|
* $data: send data
|
* $bytes: data size
|
* return value: <0: failure 0: successfully
|
*/
|
int socket_send(socket_t *sock, char *data, int bytes)
|
{
|
int rv = 0;
|
int i = 0;
|
int left_bytes = bytes;
|
|
if( !sock || !data || bytes<=0 )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
while( left_bytes > 0 )
|
{
|
rv=write(sock->fd, &data[i], left_bytes);
|
if( rv < 0 )
|
{
|
log_info("socket[%d] write() failure: %s, close socket now\n", sock->fd, strerror(errno));
|
socket_close(sock);
|
return -2;
|
}
|
else if( rv == left_bytes )
|
{
|
log_info("socket send %d bytes data over\n", bytes);
|
return 0;
|
}
|
else
|
{
|
/* not send over this time, continue to send left data */
|
i += rv;
|
left_bytes -= rv;
|
continue;
|
}
|
}
|
}
|
|
/* description: receive data from the socket in some time
|
* input args: $sock: socket context
|
* $data: send data
|
* $bytes: data size
|
* $timeout: receive data time, <=0 will don't timeout
|
* return value: <0: error >=0: receive data bytes;
|
*/
|
int socket_recv(socket_t *sock, char *buf, int size, int timeout)
|
{
|
int rv = 0;
|
int i = 0;
|
fd_set fdsr;
|
int maxsock;
|
|
if( !sock || sock->fd<0 || !buf ||size<=0 )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
memset(buf, 0, size);
|
|
maxsock = sock->fd;
|
|
FD_ZERO(&fdsr);
|
FD_SET(sock->fd, &fdsr);
|
|
if( timeout <= 0 ) /* no timeout */
|
{
|
rv=select(maxsock+1, &fdsr, NULL, NULL, NULL);
|
}
|
else
|
{
|
struct timeval tv;
|
|
tv.tv_sec = timeout;
|
tv.tv_usec = 0;
|
rv=select(maxsock+1, &fdsr, NULL, NULL, &tv);
|
}
|
|
|
if( rv < 0 )
|
{
|
log_err("select() read from socket[%d] failure: %s\n", sock->fd, strerror(errno));
|
return -2;
|
}
|
else if( rv == 0 )
|
{
|
log_err("select() read from socket[%d] get timeout\n", sock->fd);
|
return 0;
|
}
|
else
|
{
|
rv = read(sock->fd, buf, size);
|
if( rv < 0 )
|
{
|
log_err("socket[%d] read() failure: %s, close socket now\n", sock->fd, strerror(errno));
|
socket_close(sock);
|
return -2;
|
}
|
else if( rv == 0 )
|
{
|
log_err("socket[%d] read() get peer disconnect, close socket now\n", sock->fd);
|
socket_close(sock);
|
return -2;
|
}
|
else
|
{
|
log_dbg("socket[%d] receive %d bytes data\n", sock->fd, rv);
|
logger_dump(LOG_LEVEL_INFO, buf, rv);
|
return rv;
|
}
|
}
|
}
|
|
|
|
/* description: parser hostname and port from $host and set it into $sock
|
* input args: $sock: socket context
|
* $host: connect hostname, format as "hostname:port"
|
*/
|
int parser_host_port(socket_t *sock, char *host)
|
{
|
char *ptr = NULL;
|
int len = 0;
|
|
if( !sock || !host )
|
{
|
log_err("Invalid input arguments\n");
|
return -1;
|
}
|
|
ptr = strchr(host, ':');
|
|
if( !ptr )
|
{
|
log_err("Invalid arguments for host, format should be 'hostname:port'\n");
|
return -1;
|
}
|
|
len = ptr-host;
|
if( len > sizeof(sock->raddr) )
|
len = sizeof(sock->raddr);
|
|
memcpy(sock->raddr, host, ptr-host);
|
sock->rport = atoi(ptr+1);
|
|
log_info("paser host[%s] to '%s:%d'\n", host, sock->raddr, sock->rport);
|
|
return 0;
|
}
|
|
|
int socket_set_reuseaddr(int sockfd)
|
{
|
int opt = 1;
|
int len = sizeof (int);
|
|
if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &opt, len))
|
{
|
log_err("Set socket[%d] option SO_REUSEADDR failed:%s\n", sockfd, strerror(errno));
|
return -1;
|
}
|
log_dbg("Set socket[%d] option SO_REUSEADDR ok\n", sockfd);
|
|
return 0;
|
}
|
|
|
int socket_set_nonblock(int sockfd)
|
{
|
int opts;
|
/*
|
* fcntl may set:
|
*
|
* EACCES, EAGAIN: Operation is prohibited by locks held by other
|
* processes. Or, operation is prohibited because the file has
|
* been memory-mapped by another process.
|
* EBADF: fd is not an open file descriptor, or the command was F_SETLK
|
* or F_SETLKW and the file descriptor open mode doesn't match
|
* with the type of lock requested.
|
* EDEADLK: It was detected that the specified F_SETLKW command would
|
* cause a deadlock.
|
* EFAULT: lock is outside your accessible address space.
|
* EINTR: For F_SETLKW, the command was interrupted by a signal. For
|
* F_GETLK and F_SETLK, the command was interrupted by a signal
|
* before the lock was checked or acquired. Most likely when
|
* locking a remote file (e.g. locking over NFS), but can
|
* sometimes happen locally.
|
* EINVAL: For F_DUPFD, arg is negative or is greater than the maximum
|
* allowable value. For F_SETSIG, arg is not an allowable signal
|
* number.
|
* EMFILE: For F_DUPFD, the process already has the maximum number of
|
* file descriptors open.
|
* ENOLCK: Too many segment locks open, lock table is full, or a remote
|
* locking protocol failed (e.g. locking over NFS).
|
* EPERM: Attempted to clear the O_APPEND flag on a file that has the
|
* append-only attribute set.
|
*/
|
opts = fcntl(sockfd, F_GETFL);
|
if (opts < 0)
|
{
|
log_warn("fcntl() get socket options failure: %s\n", strerror(errno));
|
return -1;
|
}
|
|
opts |= O_NONBLOCK;
|
|
if (fcntl(sockfd, F_SETFL, opts) < 0)
|
{
|
log_warn("fcntl() set socket options failure: %s\n", strerror(errno));
|
return -1;
|
}
|
|
log_dbg("Set socket[%d] none blocking\n", sockfd);
|
return opts;
|
}
|
|
int socket_set_buffer(int sockfd, int rsize, int ssize)
|
{
|
int opt;
|
socklen_t optlen = sizeof(opt);
|
|
if(sockfd < 0)
|
return -1;
|
|
/* Get system default receive buffer size, Linux X86: 85K */
|
if (getsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, (char *) &opt, &optlen))
|
{
|
log_warn("getsockopt() get receive buffer failure: %s\n", strerror(errno));
|
return -2;
|
}
|
|
/* Only when current receive buffer size larger than the default one will change it */
|
if(rsize > opt)
|
{
|
opt = (int) rsize;
|
if (setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, (char *) &opt, optlen))
|
{
|
log_warn("setsockopt() set receive buffer to %d failure: %s\n", opt, strerror(errno));
|
return -2;
|
}
|
}
|
|
/* Get system default send buffer size, Linux X86: 16K */
|
if (getsockopt (sockfd, SOL_SOCKET, SO_SNDBUF, (char *) &opt, &optlen))
|
{
|
log_warn("getsockopt() get send buffer failure: %s\n", strerror(errno));
|
return -3;
|
}
|
|
/* Only when current receive buffer size larger than the default one will change it */
|
if(ssize > opt)
|
{
|
opt = (int) ssize;
|
if (setsockopt (sockfd, SOL_SOCKET, SO_SNDBUF, (char *) &opt, optlen))
|
{
|
log_warn("setsockopt() set send buffer to %d failure: %s\n", opt, strerror(errno));
|
return -3;
|
}
|
}
|
|
log_info("Set socket[%d] RCVBUF size:%d SNDBUF size:%d\n", sockfd, rsize, ssize);
|
return 0;
|
}
|
|
/*
|
* Enable socket SO_KEEPALIVE, if the connection disconnected, any system call on socket
|
* will return immediately and errno will be set to "WSAENOTCONN"
|
*
|
* keepalive is not program related, but socket related, * so if you have multiple sockets,
|
* you can handle keepalive for each of them separately.
|
*
|
* Reference: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
|
*/
|
int socket_set_keepalive(int sockfd, int keepintvl, int keepcnt)
|
{
|
int opt;
|
|
if(sockfd < 0)
|
return -1;
|
|
/* Enable the KEEPALIVE flag */
|
opt = 1;
|
if (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof (opt)))
|
{
|
log_warn("setsockopt() enable SO_KEEPALIVE failure: %s\n", strerror(errno));
|
return -2;
|
}
|
|
if(keepintvl || keepcnt)
|
{
|
/*
|
* The tcp_keepidle parameter specifies the interval between the last data packet sent
|
* (simple ACKs are not considered data) and the first keepalive probe; after the
|
* connection is marked to need keepalive, this counter is not used any further.
|
* ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_time
|
* 7200
|
*/
|
opt = 3; /* 3 seconds */
|
if (setsockopt (sockfd, SOL_TCP, TCP_KEEPIDLE, (char *) &opt, sizeof (opt)))
|
{
|
log_err("setsockopt() set TCP_KEEPIDLE to %d seconds failure: %s\n", opt, strerror(errno));
|
return -3;
|
}
|
|
if((opt=keepintvl) > 0)
|
{
|
/*
|
* The tcp_keepintvl parameter specifies the interval between subsequential keepalive
|
* probes, regardless of what the connection has exchanged in the meantime.
|
* ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_intvl
|
* 75
|
*/
|
if (setsockopt (sockfd, SOL_TCP, TCP_KEEPINTVL, (char *) &opt, sizeof (opt)))
|
{
|
log_err("setsockopt() set TCP_KEEPINTVL to %d failure: %s\n", opt, strerror(errno));
|
return -4;
|
}
|
}
|
|
if((opt=keepcnt) > 0)
|
{
|
/*
|
* The TCP_KEEPCNT option specifies the maximum number of unacknowledged probes to
|
* send before considering the connection dead and notifying the application layer
|
* probes to be sent. The value of TCP_KEEPCNT is an integer value between 1 and n,
|
* where n is the value of the systemwide tcp_keepcnt parameter.
|
* ~ >: cat /proc/sys/net/ipv4/tcp_keepalive_probes
|
* 9
|
*/
|
if (setsockopt (sockfd, SOL_TCP, TCP_KEEPCNT, (char *) &opt, sizeof (opt)))
|
{
|
log_err("setsockopt() set TCP_KEEPCNT to %d failure: %s\n", opt, strerror(errno));
|
return -5;
|
}
|
}
|
}
|
|
log_dbg("Set socket[%d] KEEPINTVL:%d KEEPCNT:%d\n", sockfd, keepintvl, keepcnt);
|
return 0;
|
}
|