add socket snippet

This commit is contained in:
Edward Emelianov 2022-03-11 17:01:59 +03:00
parent 0da6bce6e9
commit 76a1ec0e81
21 changed files with 1071 additions and 0 deletions

4
Socket_CAN/socketcan0 Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
ip link set can0 type can bitrate 100000 triple-sampling on
ifconfig can0 up

45
Socket_snippet/Makefile Normal file
View File

@ -0,0 +1,45 @@
# run `make DEF=...` to add extra defines
PROGRAM := socket
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all
LDFLAGS += -lusefull_macros -lm -L/usr/local/lib/
SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
OBJDIR := mk
CFLAGS += -O3 -Wno-trampolines -std=gnu99
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
DEPS := $(OBJS:.o=.d)
CC = gcc
#CXX = g++
all : $(PROGRAM)
debug: CFLAGS += -DEBUG -Werror -Wall -Wextra
debug: all
$(OBJS): $(OBJDIR)
$(PROGRAM) : $(OBJS)
@echo -e "\t\tLD $(PROGRAM)"
$(CC) $(OBJS) $(LDFLAGS) -o $(PROGRAM)
$(OBJDIR):
mkdir $(OBJDIR)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
$(OBJDIR)/%.o: %.c
@echo -e "\t\tCC $<"
$(CC) $< -MD -c $(CFLAGS) $(DEFINES) -o $@
clean:
@echo -e "\t\tCLEAN"
@rm -f $(OBJS) $(DEPS)
@rmdir $(OBJDIR) 2>/dev/null || true
xclean: clean
@rm -f $(PROGRAM)
.PHONY: clean xclean

7
Socket_snippet/Readme.md Normal file
View File

@ -0,0 +1,7 @@
Socket server and client snippet
================================
This snippet allows to create some utilities than can be run both in client or server mode.
The sockets are **local** TCP or UNIX sockets. Server-side polling use `poll()`.
The pieces of user code may be in comments marked `USERCODE`

120
Socket_snippet/client.c Normal file
View File

@ -0,0 +1,120 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// client-side functions
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <usefull_macros.h>
#include "client.h"
#include "socket.h"
/**
* @brief processData - process here some actions and make messages for server
* @param buf (o) - buffer for answer
* @param l - max length of buf
* @return amount of answer bytes
*/
static int process_data(char *buf, int l){
if(!buf || !l) return 0;
/* USERCODE: get here some data to send */
static double t0 = 0.;
if(dtime() - t0 > 1.){
t0 = dtime();
// send random commands each 1 second
int x = rand() % 600;
const char *cmd = NULL;
if(x < 100) cmd = "time";
else if(x < 200) cmd = "getval1";
else if(x < 300) cmd = "getval2";
else if(x < 400) cmd = "ping";
if(cmd) snprintf(buf, l, "%s", cmd);
else{
if(x < 500) snprintf(buf, l, "setval1=%d", rand() & 0xff);
else snprintf(buf, l, "setval2=%d", rand() & 0xff);
}
return strlen(buf);
}
return 0;
}
/**
* @brief process_server_message - parse messages from server and make an answer
* @param buf (io) - incoming message (the answer will be in this buffer)
* @param l - buff max length
* @return amount of answer bytes
*/
static int process_server_message(char *buf, int l){
/* USERCODE inside this funtion */
if(!buf || !l) return 0;
// just show on screen
green("SERVER send: %s\n", buf);
return 0;
}
/**
* check data from fd (polling function for client)
* @param fd - file descriptor
* @return 0 in case of timeout, 1 in case of fd have data, -1 if error
*/
static int canberead(int fd){
fd_set fds;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100;
FD_ZERO(&fds);
FD_SET(fd, &fds);
do{
int rc = select(fd+1, &fds, NULL, NULL, &timeout);
if(rc < 0){
if(errno != EINTR){
LOGWARN("select()");
WARN("select()");
return -1;
}
continue;
}
break;
}while(1);
if(FD_ISSET(fd, &fds)){
//DBG("FD_ISSET");
return 1;
}
return 0;
}
void client(int sock){
char buf[BUFLEN];
while(1){
int l = process_data(buf, BUFLEN-1);
if(l) sendmessage(sock, buf, l);
if(1 != canberead(sock)) continue;
int n = read(sock, buf, BUFLEN-1);
if(n == 0){
WARNX("Server disconnected");
signals(0);
}
buf[n] = 0;
DBG("Got from server: %s", buf);
LOGMSG("Got from server: %s", buf);
l = process_server_message(buf, n);
if(l) sendmessage(sock, buf, l);
}
}

26
Socket_snippet/client.h Normal file
View File

@ -0,0 +1,26 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef CLIENT_H__
#define CLIENT_H__
// client-side functions
void client(int fd);
#endif // CLIENT_H__

View File

@ -0,0 +1,83 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include "cmdlnopts.h"
#include "usefull_macros.h"
// default PID filename:
#define DEFAULT_PIDFILE "/tmp/usbsock.pid"
static int help;
static glob_pars G = {
.pidfile = DEFAULT_PIDFILE,
};
glob_pars *GP = &G;
/*
* Define command line options by filling structure:
* name has_arg flag val type argptr help
*/
static myoption cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), _("file to save logs (default: none)")},
{"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), _("pidfile (default: " DEFAULT_PIDFILE ")")},
{"client", NO_ARGS, NULL, 'c', arg_int, APTR(&G.client), _("run as client")},
{"path", NEED_ARG, NULL, 'N', arg_string, APTR(&G.path), _("UNIX socket path/name (start from \\0 for no files)")},
{"port", NEED_ARG, NULL, 'P', arg_string, APTR(&G.port), _("port to connect (for local TCP socket)")},
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), _("increase log verbose level (default: LOG_WARN) and messages (default: none)")},
end_option
};
/**
* Parse command line options and return dynamically allocated structure
* to global parameters
* @param argc - copy of argc from main
* @param argv - copy of argv from main
* @return allocated structure with global parameters
*/
void parse_args(int argc, char **argv){
int i;
size_t hlen = 1024;
char helpstring[1024], *hptr = helpstring;
snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n");
// format of help: "Usage: progname [args]\n"
change_helpstring(helpstring);
// parse arguments
parseargs(&argc, &argv, cmdlnopts);
for(i = 0; i < argc; i++)
printf("Ignore parameter\t%s\n", argv[i]);
if(help) showhelp(-1, cmdlnopts);
}
/**
* @brief verbose - print additional messages depending of G.verbose (add '\n' at end)
* @param levl - message level
* @param fmt - message
*/
void verbose(int levl, const char *fmt, ...){
va_list ar;
if(levl > G.verbose) return;
va_start(ar, fmt);
vprintf(fmt, ar);
va_end(ar);
printf("\n");
}

View File

@ -0,0 +1,40 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef CMDLNOPTS_H__
#define CMDLNOPTS_H__
/*
* here are some typedef's for global data
*/
typedef struct{
char *pidfile; // name of PID file
char *logfile; // logging to this file
char *path; // path to UNIX-socket file
char *port; // local TCP socket port
int verbose; // verbose level: for messages & logging
int client; // ==1 if application runs in client mode
} glob_pars;
extern glob_pars *GP;
void parse_args(int argc, char **argv);
void verbose(int levl, const char *fmt, ...);
#endif // CMDLNOPTS_H__

108
Socket_snippet/main.c Normal file
View File

@ -0,0 +1,108 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <usefull_macros.h>
#include "cmdlnopts.h"
#include "socket.h"
static pid_t childpid = 0;
static int isserver = 1;
void signals(int sig){
if(childpid){ // slave process
DBG("Child killed with sig=%d", sig);
LOGWARN("Child killed with sig=%d", sig);
exit(sig);
}
// master process
DBG("Master process");
if(sig){
DBG("Exit with signal %d", sig);
signal(sig, SIG_IGN);
LOGERR("Exit with signal %d", sig);
}else LOGERR("Exit");
if(GP->pidfile && isserver){
DBG("Unlink pid file");
unlink(GP->pidfile);
}
exit(sig);
}
int main(int argc, char **argv){
char *self = strdup(argv[0]);
initial_setup();
parse_args(argc, argv);
if(GP->logfile){
int lvl = LOGLEVEL_WARN + GP->verbose;
DBG("level = %d", lvl);
if(lvl > LOGLEVEL_ANY) lvl = LOGLEVEL_ANY;
verbose(1, "Log file %s @ level %d\n", GP->logfile, lvl);
OPENLOG(GP->logfile, lvl, 1);
if(!globlog) WARNX("Can't create log file");
}
if(GP->client) isserver = 0;
if(GP->port){
if(GP->path){
WARNX("Options `port` and `path` can't be used together! Point `port` for TCP socket or `path` for UNIX.");
return 1;
}
int port = atoi(GP->port);
if(port < PORTN_MIN || port > PORTN_MAX){
WARNX("Wrong port value: %d", port);
return 1;
}
}else if(!GP->path) ERRX("You should point option `port` or `path`!");
if(isserver) check4running(self, GP->pidfile);
// signal reactions:
signal(SIGTERM, signals); // kill (-15) - quit
signal(SIGHUP, SIG_IGN); // hup - ignore
signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
LOGMSG("Started");
#ifndef EBUG
if(isserver){
unsigned int pause = 5;
while(1){
childpid = fork();
if(childpid){ // master
double t0 = dtime();
LOGMSG("Created child with pid %d", childpid);
wait(NULL);
LOGWARN("Child %d died", childpid);
if(dtime() - t0 < 1.) pause += 5;
else pause = 1;
if(pause > 900) pause = 900;
sleep(pause); // wait a little before respawn
}else{ // slave
prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies
break;
}
}
}
#endif
if(GP->path) return start_socket(isserver, GP->path, FALSE);
if(GP->port) return start_socket(isserver, GP->port, TRUE);
}

150
Socket_snippet/server.c Normal file
View File

@ -0,0 +1,150 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <usefull_macros.h>
#include "server.h"
#include "socket.h"
// server-side functions
// command handlers
hresult pinghandler(int fd, _U_ const char *key, _U_ const char *val){
sendstrmessage(fd, "PING");
return RESULT_SILENCE;
}
static int sendtime = 0;
hresult timehandler(_U_ int fd, _U_ const char *key, _U_ const char *val){
sendtime = 1;
return RESULT_OK;
}
static int value1 = 0, value2 = 0;
hresult valsethandler(_U_ int fd, const char *key, const char *val){
if(!val || !*val) return RESULT_BADVAL;
int i = atoi(val);
if(i < -65535 || i > 65535) return RESULT_BADVAL;
if(strcmp(key, "setval1") == 0) value1 = i;
else value2 = i;
return RESULT_OK;
}
hresult valgethandler(int fd, const char *key, _U_ const char *val){
char buf[32];
if(strcmp(key, "getval1") == 0) snprintf(buf, 32, "VAL1=%d", value1);
else snprintf(buf, 32, "VAL2=%d", value2);
sendstrmessage(fd, buf);
return RESULT_SILENCE;
}
/*
hresult handler(_U_ int fd, _U_ const char *key, _U_ const char *val){
;
}
*/
static handleritem items[] = {
{pinghandler, "ping"},
{timehandler, "time"},
{valsethandler, "setval1"},
{valsethandler, "setval2"},
{valgethandler, "getval1"},
{valgethandler, "getval2"},
{NULL, NULL},
};
/**
* @brief processData - process here some actions and make messages for all clients
* @param buf (o) - buffer for answer
* @param l - max length of buf
* @return amount of answer bytes
*/
static int process_data(char *buf, int l){
if(!buf || !l) return 0;
/* USERCODE: get here some data to send all clients */
if(sendtime){
snprintf(buf, l, "TIME=%.2f", dtime());
sendtime = 0;
return strlen(buf);
}
return 0;
}
#define CLBUFSZ BUFSIZ
void server(int sock){
if(listen(sock, MAXCLIENTS) == -1){
WARN("listen");
LOGWARN("listen");
return;
}
int nfd = 1; // only one socket @start
struct pollfd poll_set[MAXCLIENTS+1];
char buffers[MAXCLIENTS][CLBUFSZ]; // buffers for data reading
bzero(poll_set, sizeof(poll_set));
// ZERO - listening server socket
poll_set[0].fd = sock;
poll_set[0].events = POLLIN;
while(1){
poll(poll_set, nfd, 1); // max timeout - 1ms
if(poll_set[0].revents & POLLIN){ // check main for accept()
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
int client = accept(sock, (struct sockaddr*)&addr, &len);
DBG("New connection");
LOGMSG("SERVER got connection, fd=%d", client);
if(nfd == MAXCLIENTS + 1){
LOGWARN("Max amount of connections, disconnect fd=%d", client);
WARNX("Limit of connections reached");
close(client);
}else{
memset(&poll_set[nfd], 0, sizeof(struct pollfd));
poll_set[nfd].fd = client;
poll_set[nfd].events = POLLIN;
++nfd;
}
}
char buff[BUFLEN];
int l = 0;
// process some data & send messages to ALL
if((l = process_data(buff, BUFLEN-1))){
DBG("Send to %d clients", nfd - 1);
for(int i = 1; i < nfd; ++i)
sendmessage(poll_set[i].fd, buff, l);
}
// scan connections
for(int fdidx = 1; fdidx < nfd; ++fdidx){
if((poll_set[fdidx].revents & POLLIN) == 0) continue;
int fd = poll_set[fdidx].fd;
if(!processData(fd, items, buffers[fdidx-1], CLBUFSZ)){ // socket closed
DBG("Client fd=%d disconnected", fd);
LOGMSG("SERVER client fd=%d disconnected", fd);
buffers[fdidx-1][0] = 0; // clear rest of data in buffer
close(fd);
// move last FD to current position
poll_set[fdidx] = poll_set[nfd - 1];
--nfd;
}
}
}
}

26
Socket_snippet/server.h Normal file
View File

@ -0,0 +1,26 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef SERVER_H__
#define SERVER_H__
// server-side functions
void server(int fd);
#endif // SERVER_H__

BIN
Socket_snippet/socket Executable file

Binary file not shown.

213
Socket_snippet/socket.c Normal file
View File

@ -0,0 +1,213 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ctype.h> // isspace
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/un.h> // unix socket
#include <usefull_macros.h>
#include "cmdlnopts.h"
#include "client.h"
#include "server.h"
#include "socket.h"
/**
* @brief start_socket - create socket and run client or server
* @param isserver - TRUE for server, FALSE for client
* @param path - UNIX-socket path or local INET socket port
* @param isnet - TRUE for INET socket, FALSE for UNIX
* @return 0 if OK
*/
int start_socket(int isserver, char *path, int isnet){
if(!path) return 1;
DBG("path/port: %s", path);
int sock = -1;
struct addrinfo hints = {0}, *res;
struct sockaddr_un unaddr = {0};
if(isnet){
DBG("Network socket");
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if(getaddrinfo("127.0.0.1", path, &hints, &res) != 0){
ERR("getaddrinfo");
}
}else{
DBG("UNIX socket");
char apath[128];
if(*path == 0){
DBG("convert name");
apath[0] = 0;
strncpy(apath+1, path+1, 126);
}else if(strncmp("\\0", path, 2) == 0){
DBG("convert name");
apath[0] = 0;
strncpy(apath+1, path+2, 126);
}else strcpy(apath, path);
unaddr.sun_family = AF_UNIX;
hints.ai_addr = (struct sockaddr*) &unaddr;
hints.ai_addrlen = sizeof(unaddr);
memcpy(unaddr.sun_path, apath, 106); // if sun_path[0] == 0 we don't create a file
hints.ai_family = AF_UNIX;
hints.ai_socktype = SOCK_SEQPACKET;
res = &hints;
}
for(struct addrinfo *p = res; p; p = p->ai_next){
if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0){ // or SOCK_STREAM?
LOGWARN("socket()");
WARN("socket()");
continue;
}
if(isserver){
int reuseaddr = 1;
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){
LOGWARN("setsockopt()");
WARN("setsockopt()");
close(sock); sock = -1;
continue;
}
if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){
LOGWARN("bind()");
WARN("bind()");
close(sock); sock = -1;
continue;
}
int enable = 1;
if(ioctl(sock, FIONBIO, (void *)&enable) < 0){ // make socket nonblocking
LOGERR("Can't make socket nonblocking");
ERRX("ioctl()");
}
}else{
if(connect(sock, p->ai_addr, p->ai_addrlen) == -1){
LOGWARN("connect()");
WARN("connect()");
close(sock); sock = -1;
}
}
break;
}
if(sock < 0){
LOGERR("Can't open socket");
ERRX("Can't open socket");
}
if(isnet) freeaddrinfo(res);
if(isserver) server(sock);
else client(sock);
close(sock);
signals(0);
return 0;
}
// simple wrapper over write: add missed newline and log data
void sendmessage(int fd, const char *msg, int l){
if(fd < 1 || !msg || l < 1) return;
DBG("send to fd %d: %s [%d]", fd, msg, l);
char *tmpbuf = MALLOC(char, l+1);
memcpy(tmpbuf, msg, l);
if(msg[l-1] != '\n') tmpbuf[l++] = '\n';
if(l != write(fd, tmpbuf, l)){
LOGWARN("write()");
WARN("write()");
}else{
if(globlog){ // logging turned ON
tmpbuf[l-1] = 0; // remove trailing '\n' for logging
LOGMSG("SEND to fd %d: %s", fd, tmpbuf);
}
}
FREE(tmpbuf);
}
void sendstrmessage(int fd, const char *msg){
if(fd < 1 || !msg) return;
int l = strlen(msg);
sendmessage(fd, msg, l);
}
// text messages for `hresult`
static const char *resmessages[] = {
[RESULT_OK] = "OK",
[RESULT_FAIL] = "FAIL",
[RESULT_BADKEY] = "BADKEY",
[RESULT_BADVAL] = "BADVAL",
[RESULT_SILENCE] = "",
};
const char *hresult2str(hresult r){
if(r >= RESULT_NUM) return "BADRESULT";
return resmessages[r];
}
// parse string of data (command or key=val)
// the CONTENT of buffer `str` WILL BE BROKEN!
static void parsestring(int fd, handleritem *handlers, char *str){
if(fd < 1 || !handlers || !handlers->key || !str || !*str) return;
DBG("Got string %s", str);
// remove starting spaces in key
while(isspace(*str)) ++str;
char *val = strchr(str, '=');
if(val){ // get value: remove starting spaces in val
*val++ = 0;
while(isspace(*val)) ++val;
}
// remove trailing spaces in key
char *e = str + strlen(str) - 1; // last key symbol
while(isspace(*e) && e > str) --e;
e[1] = 0;
// now we have key (`str`) and val (or NULL)
DBG("key=%s, val=%s", str, val);
for(handleritem *h = handlers; h->handler; ++h){
if(strcmp(str, h->key) == 0){ // found command
if(h->handler){
hresult r = h->handler(fd, str, val);
sendstrmessage(fd, hresult2str(r));
}else sendstrmessage(fd, resmessages[RESULT_FAIL]);
return;
}
}
sendstrmessage(fd, resmessages[RESULT_BADKEY]);
}
/**
* @brief processData - read (if available) data from fd and run processing, sending to fd messages for each command
* @param fd - socket file descriptor
* @param handlers - NULL-terminated array of handlers
* @param buf (io) - zero-terminated buffer for storing rest of data (without newline), its content will be changed
* @param buflen - its length
* @return FALSE if client closed (nothing to read)
*/
int processData(int fd, handleritem *handlers, char *buf, int buflen){
int curlen = strlen(buf);
if(curlen == buflen-1) curlen = 0; // buffer overflow - clear old content
ssize_t rd = read(fd, buf + curlen, buflen-1 - curlen);
if(rd <= 0) return FALSE;
DBG("got %s[%zd] from %d", buf, rd, fd);
char *restofdata = buf, *eptr = buf + curlen + rd;
*eptr = 0;
do{
char *nl = strchr(restofdata, '\n');
if(!nl) break;
*nl++ = 0;
parsestring(fd, handlers, restofdata);
restofdata = nl;
DBG("rest of data: %s", restofdata);
}while(1);
if(restofdata != buf) memmove(buf, restofdata, eptr - restofdata + 1);
return TRUE;
}

56
Socket_snippet/socket.h Normal file
View File

@ -0,0 +1,56 @@
/*
* This file is part of the socksnippet project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef SERSOCK_H__
#define SERSOCK_H__
// max & min TCP socket port number
#define PORTN_MAX (65535)
#define PORTN_MIN (1024)
#define BUFLEN (1024)
// Max amount of connections
#define MAXCLIENTS (30)
typedef enum{
RESULT_OK, // all OK
RESULT_FAIL, // failed running command
RESULT_BADVAL, // bad key's value
RESULT_BADKEY, // bad key
RESULT_SILENCE, // send nothing to client
RESULT_NUM
} hresult;
const char *hresult2str(hresult r);
// fd - socket fd to send private messages, key, val - key and its value
typedef hresult (*mesghandler)(int fd, const char *key, const char *val);
typedef struct{
mesghandler handler;
const char *key;
} handleritem;
int start_socket(int server, char *path, int isnet);
void sendmessage(int fd, const char *msg, int l);
void sendstrmessage(int fd, const char *msg);
int processData(int fd, handleritem *handlers, char *buf, int buflen);
#endif // SERSOCK_H__

View File

@ -0,0 +1 @@
-std=c17

View File

@ -0,0 +1,6 @@
// Add predefined macros for your project here. For example:
// #define THE_ANSWER 42
#define EBUG
#define _GNU_SOURCE
#define _XOPEN_SOURCE=1111

View File

@ -0,0 +1 @@
[General]

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 6.0.0, 2022-02-02T17:26:10. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">2</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<valuemap type="QVariantMap" key="CppEditor.QuickFix">
<value type="bool" key="UseGlobalSettings">true</value>
</valuemap>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/ELECTRONICS/CAN_controller/Socket_CANserver</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@ -0,0 +1 @@
-std=c++17

View File

@ -0,0 +1,9 @@
client.c
client.h
cmdlnopts.c
cmdlnopts.h
main.c
server.c
server.h
socket.c
socket.h

View File

@ -0,0 +1 @@
.

View File

@ -0,0 +1,11 @@
time
getval1
setval1 = 56
getval2
setval2 = 189
getval1 = asdf
getval2 = 000000asdffd fdassdf fda
time
a
getval1
getval2