Here's some code that I originally worked on for a now closed question: https://stackoverflow.com/questions/66232128/request-a-file-from-server-to-client-using-c-sockets
It was originally TCP only, but I [just] added [rudimentary] UDP support.
It prefixes all communication with a fixed size struct
that specifies the length of the payload. The struct
also has a command/type field that describes what needs to be done.
Note that I've tested the UDP mode.
But ... The code [originally being stream oriented] did separate send/recv
calls for the struct and the payload.
This won't work too well with multiple clients interspersing packets. So, you'll need to think about how to enhance this.
Sending the struct
and the payload in a single outgoing message [using sendmsg
instead of sendto
] with a fixed maximum payload buffer size is a start.
Also, you'll have to decide whether the server will maintain some per-client state (e.g.) the open file descriptor and file position for the file being transferred.
The code is in a few separate files:
#!/usr/bin/perl
# FILE: ovcbin/ovcext.pm 755
# ovcbin/ovcext.pm -- ovrcat archive extractor
#
# this is a self extracting archive
# after the __DATA__ line, files are separated by:
# % filename
ovcext_cmd(@ARGV);
exit(0);
sub ovcext_cmd
{
my(@argv) = @_;
local($xfdata);
local($xfdiv,$divcur,%ovcdiv_lookup);
$pgmtail = "ovcext";
ovcinit();
ovcopt(\@argv,qw(opt_go opt_f opt_t));
$xfdata = "ovrcat::DATA";
$xfdata = \*$xfdata;
ovceval($xfdata);
ovcfifo($zipflg_all);
ovcline($xfdata);
$code = ovcwait();
ovcclose(\$xfdata);
ovcdiv();
ovczipd_spl()
if ($zipflg_spl);
}
sub ovceval
{
my($xfdata) = @_;
my($buf,$err);
{
$buf = <$xfdata>;
chomp($buf);
last unless ($buf =~ s/^%\s+([\@\$;])/$1/);
eval($buf);
$err = $@;
unless ($err) {
undef($buf);
last;
}
chomp($err);
$err = " (" . $err . ")"
}
sysfault("ovceval: bad options line -- '%s'%s\n",$buf,$err)
if (defined($buf));
}
sub ovcline
{
my($xfdata) = @_;
my($buf);
my($tail);
while ($buf = <$xfdata>) {
chomp($buf);
if ($buf =~ /^%\s+(.+)$/) {
$tail = $1;
ovcdiv($tail);
next;
}
print($xfdiv $buf,"\n")
if (ref($xfdiv));
}
}
sub ovcdiv
{
my($ofile) = @_;
my($mode);
my($xfcur);
my($err,$prt);
($ofile,$mode) = split(" ",$ofile);
$mode = oct($mode);
$mode &= 0777;
{
unless (defined($ofile)) {
while ((undef,$divcur) = each(%ovcdiv_lookup)) {
close($divcur->{div_xfdst});
}
last;
}
$ofile = ovctail($ofile);
$divcur = $ovcdiv_lookup{$ofile};
if (ref($divcur)) {
$xfdiv = $divcur->{div_xfdst};
last;
}
undef($xfdiv);
if (-e $ofile) {
msg("ovcdiv: file '%s' already exists -- ",$ofile);
unless ($opt_f) {
msg("rerun with -f to force\n");
last;
}
msg("overwriting!\n");
}
unless (defined($err)) {
ovcmkdir($1)
if ($ofile =~ m,^(.+)/[^/]+$,);
}
msg("$pgmtail: %s %s",ovcnogo("extracting"),$ofile);
msg(" chmod %3.3o",$mode)
if ($mode);
msg("\n");
last unless ($opt_go);
last if (defined($err));
$xfcur = ovcopen(">$ofile");
$divcur = {};
$ovcdiv_lookup{$ofile} = $divcur;
if ($mode) {
chmod($mode,$xfcur);
$divcur->{div_mode} = $mode;
}
$divcur->{div_xfdst} = $xfcur;
$xfdiv = $xfcur;
}
}
sub ovcinit
{
{
last if (defined($ztmp));
$ztmp = "/tmp/ovrcat_zip";
$PWD = $ENV{PWD};
$quo_2 = '"';
$ztmp_inp = $ztmp . "_0";
$ztmp_out = $ztmp . "_1";
$ztmp_perl = $ztmp . "_perl";
ovcunlink();
$ovcdbg = ($ENV{"ZPXHOWOVC"} != 0);
}
}
sub ovcunlink
{
_ovcunlink($ztmp_inp,1);
_ovcunlink($ztmp_out,1);
_ovcunlink($ztmp_perl,($pgmtail ne "ovcext") || $opt_go);
}
sub _ovcunlink
{
my($file,$rmflg) = @_;
my($found,$tag);
{
last unless (defined($file));
$found = (-e $file);
$tag //= "notfound"
unless ($found);
$tag //= $rmflg ? "cleaning" : "keeping";
msg("ovcunlink: %s %s ...\n",$tag,$file)
if (($found or $ovcdbg) and (! $ovcunlink_quiet));
unlink($file)
if ($rmflg and $found);
}
}
sub ovcopt
{
my($argv) = @_;
my($opt);
while (1) {
$opt = $argv->[0];
last unless ($opt =~ s/^-/opt_/);
shift(@$argv);
$$opt = 1;
}
}
sub ovctail
{
my($file,$sub) = @_;
my(@file);
$file =~ s,^/,,;
@file = split("/",$file);
$sub //= 2;
@file = splice(@file,-$sub)
if (@file >= $sub);
$file = join("/",@file);
$file;
}
sub ovcmkdir
{
my($odir) = @_;
my(@lhs,@rhs);
@rhs = split("/",$odir);
foreach $rhs (@rhs) {
push(@lhs,$rhs);
$odir = join("/",@lhs);
if ($opt_go) {
next if (-d $odir);
}
else {
next if ($ovcmkdir{$odir});
$ovcmkdir{$odir} = 1;
}
msg("$pgmtail: %s %s ...\n",ovcnogo("mkdir"),$odir);
next unless ($opt_go);
mkdir($odir) or
sysfault("$pgmtail: unable to mkdir '%s' -- $!\n",$odir);
}
}
sub ovcopen
{
my($file,$who) = @_;
my($xf);
$who //= $pgmtail;
$who //= "ovcopen";
open($xf,$file) or
sysfault("$who: unable to open '%s' -- $!\n",$file);
$xf;
}
sub ovcclose
{
my($xfp) = @_;
my($ref);
my($xf);
{
$ref = ref($xfp);
last unless ($ref);
if ($ref eq "GLOB") {
close($xfp);
last;
}
if ($ref eq "REF") {
$xf = $$xfp;
if (ref($xf) eq "GLOB") {
close($xf);
undef($$xfp);
}
}
}
undef($xf);
$xf;
}
sub ovcnogo
{
my($str) = @_;
unless ($opt_go) {
$str = "NOGO-$str";
$nogo_msg = 1;
}
$str;
}
sub ovcdbg
{
if ($ovcdbg) {
printf(STDERR @_);
}
}
sub msg
{
printf(STDERR @_);
}
sub msgv
{
$_ = join(" ",@_);
print(STDERR $_,"\n");
}
sub sysfault
{
printf(STDERR @_);
exit(1);
}
sub ovcfifo
{
}
sub ovcwait
{
my($code);
if ($pid_fifo) {
waitpid($pid_fifo,0);
$code = $? >> 8;
}
$code;
}
sub prtstr
{
my($val,$fmtpos,$fmtneg) = @_;
{
unless (defined($val)) {
$val = "undef";
last;
}
if (ref($val)) {
$val = sprintf("(%s)",$val);
last;
}
$fmtpos //= "'%s'";
if (defined($fmtneg) && ($val <= 0)) {
$val = sprintf($fmtneg,$val);
last;
}
$val = sprintf($fmtpos,$val);
}
$val;
}
sub prtnum
{
my($val) = @_;
$val = prtstr($val,"%d");
$val;
}
END {
msg("$pgmtail: rerun with -go to actually do it\n")
if ($nogo_msg);
ovcunlink();
}
1;
package ovrcat;
__DATA__
% ;
% inetsftp/client.c
// client.c -- client program
#include <inetsftp/inetsftp.h>
int
cli_upload(int argc,char **argv)
{
int ridx = 0;
int lidx = 0;
struct stat st;
char *file;
int code = 0;
if (argc >= 2)
ridx = 1;
do {
file = argv[lidx];
if (stat(file,&st) < 0) {
code = errno;
fprintf(stderr,"cli_upload: '%s' -- %s\n",file,strerror(errno));
break;
}
file = argv[ridx];
int len = strlen(file) + 1;
xmsgsnd(CMD_UPLOAD,file,len);
file = argv[lidx];
return send_file(file);
} while (0);
return code;
}
int
cli_dnload(int argc,char **argv)
{
int ridx = 0;
int lidx = 0;
struct stat st;
char *file;
int code = 0;
if (argc >= 2)
lidx = 1;
do {
file = argv[lidx];
if (stat(file,&st) >= 0) {
code = EEXIST;
fprintf(stderr,"cli_dnload: local '%s' -- %s\n",
file,strerror(code));
break;
}
file = argv[ridx];
int len = strlen(file) + 1;
xmsgsnd(CMD_DNLOAD,file,len);
file = argv[lidx];
return recv_file(file);
} while (0);
return code;
}
int
cli_doexec(int argc,char **argv)
{
int sep = 0;
const char *src;
char *dst;
char buf[SIZE];
xmsg_t xmsg;
int code = 0;
dst = buf;
for (; argc > 0; --argc, ++argv) {
src = *argv;
if (sep)
*dst++ = ' ';
sep = 1;
strcpy(dst,src);
dst += strlen(src);
}
*dst++ = 0;
xmsgsnd(CMD_EXEC,buf,dst - buf);
while (1) {
xmsgrcv(&xmsg,buf);
if (xmsg.xmsg_cmd == CMD_EOF) {
code = xmsg.xmsg_len;
break;
}
#if 1
fputs(buf,stdout);
#else
fwrite(buf,1,xmsg.xmsg_len - 1,stdout);
#endif
}
return code;
}
int
shcmd(int argc,char **argv)
{
char *cmd = *argv;
int code = 1;
do {
if (argc <= 0) {
code = 0;
break;
}
dbgprt("shcmd: ARGC %d\n",argc);
for (char **av = argv; *av != NULL; ++av)
dbgprt("shcmd: ARGV '%s'\n",*av);
if (strcmp(cmd,"put") == 0) {
argc = avslide(argc,argv,0);
if (argc < 2) {
fprintf(stderr,"shcmd: not enough arguments\n");
break;
}
code = cli_upload(argc,argv);
break;
}
if (strcmp(cmd,"get") == 0) {
argc = avslide(argc,argv,0);
if (argc < 2) {
fprintf(stderr,"shcmd: not enough arguments\n");
break;
}
code = cli_dnload(argc,argv);
break;
}
code = cli_doexec(argc,argv);
} while (0);
return code;
}
int
doshell(void)
{
char *cp;
int argc;
char **av;
char *argv[1000];
char buf[SIZE];
while (1) {
printf("cmd> ");
fflush(stdout);
cp = fgets(buf,sizeof(buf),stdin);
if (cp == NULL)
break;
av = argv;
while (1) {
cp = strtok((av == argv) ? buf : NULL," \t\n");
if (cp == NULL)
break;
*av++ = cp;
}
*av = NULL;
argc = av - argv;
shcmd(argc,argv);
}
return 0;
}
int
client(int argc,char **argv)
{
char *ip = "127.0.0.1";
int port = 8080;
int c;
// create socket for client with data type of stream for TCP
// and check if the file descriptor in below zero
sock_fd = socket(AF_INET, opt_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("Socket error");
exit(1);
}
dbgprt("Socket created\n");
// family of the address and port number
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = port;
// ip address of the host
sock_addr.sin_addr.s_addr = inet_addr(ip);
// connect to the server
addr_size = sizeof(sock_addr);
c = connect(sock_fd, (struct sockaddr *) &sock_addr, addr_size);
// check the connection
if (c == -1) {
perror("Connection error");
exit(1);
}
dbgprt("Connected to server\n");
if (argc > 0)
shcmd(argc,argv);
else
doshell();
return 0;
}
int
main(int argc,char **argv)
{
argc = initopt(argc,argv);
return client(argc,argv);
}
% inetsftp/common.c
// common.c -- common code
#define _INETSFTP_GLO_
#include <inetsftp/inetsftp.h>
ssize_t
xerr(const char *reason)
{
int err;
err = errno;
fprintf(stderr,"%s: error -- %s\n",reason,strerror(err));
ipshow((struct sockaddr *) &sock_addr,addr_size);
errno = err;
err = -err;
return err;
}
void
ipshow(const struct sockaddr *sock_addr,socklen_t sock_size)
{
unsigned char *raw = (unsigned char *) sock_addr;
fprintf(stderr,"ipshow: sock_size=%u sock_addr=",sock_size);
for (int idx = 0; idx < sizeof(*sock_addr); ++idx) {
if (idx > 0)
fprintf(stderr,".");
fprintf(stderr,"%2.2X",raw[idx]);
}
fprintf(stderr,"\n");
}
ssize_t
xrecv(void *buf,size_t buflen)
{
ssize_t xlen;
ssize_t totlen = 0;
for (; buflen > 0; buflen -= xlen, buf += xlen, totlen += xlen) {
if (opt_udp)
xlen = recvfrom(sock_fd,buf,buflen,0,
(struct sockaddr *) &sock_addr,&addr_size);
else
xlen = recv(sock_fd,buf,buflen,0);
if (xlen == 0)
break;
if (xlen < 0) {
totlen = xerr("xrecv");
break;
}
}
return totlen;
}
ssize_t
xsend(const void *buf,size_t buflen)
{
ssize_t xlen;
ssize_t totlen = 0;
for (; buflen > 0; buflen -= xlen, buf += xlen, totlen += xlen) {
if (opt_udp)
xlen = sendto(sock_fd,buf,buflen,0,
(struct sockaddr *) &sock_addr,addr_size);
else
xlen = send(sock_fd,buf,buflen,0);
if (xlen == 0)
break;
if (xlen < 0) {
totlen = xerr("xsend");
break;
}
}
return totlen;
}
ssize_t
xmsgrcv(xmsg_t *xmsg,void *buf)
{
ssize_t len;
char junk[SIZE];
len = xrecv(xmsg,sizeof(*xmsg));
if (len <= 0) {
printf("xmsgrcv: EXIT\n");
exit(1);
}
do {
len = xmsg->xmsg_len;
if (len <= 0)
break;
switch (xmsg->xmsg_cmd) {
case CMD_EOF: // len is error code
break;
default:
if (buf == NULL)
buf = junk;
xrecv(buf,len);
break;
}
} while (0);
return len;
}
ssize_t
xmsgsnd(int cmd,const void *buf,int len)
{
xmsg_t xmsg = { 0 };
xmsg.xmsg_cmd = cmd;
xmsg.xmsg_len = len;
xsend(&xmsg,sizeof(xmsg));
do {
if (len == 0)
break;
if (buf == NULL)
break;
if (cmd == CMD_EOF) {
len = 0;
break;
}
len = xsend(buf,len);
} while (0);
return len;
}
int
send_file(const char *file)
{
FILE *xfsrc;
ssize_t xlen;
unsigned char buf[SIZE];
int code = 0;
do {
xfsrc = fopen(file,"r");
if (xfsrc == NULL) {
xmsgsnd(CMD_NOEXIST,NULL,0);
break;
}
while (1) {
xlen = fread(buf,1,SIZE,xfsrc);
if (xlen <= 0)
break;
xmsgsnd(CMD_PAYLOAD,buf,xlen);
}
xmsgsnd(CMD_EOF,NULL,0);
fclose(xfsrc);
} while (0);
return code;
}
int
recv_file(const char *file)
{
FILE *xfdst;
xmsg_t xmsg;
unsigned char buf[SIZE];
int code = 0;
xfdst = fopen(file,"w");
while (1) {
xmsgrcv(&xmsg,buf);
if (xmsg.xmsg_cmd == CMD_EOF)
break;
if (xmsg.xmsg_cmd == CMD_NOEXIST) {
unlink(file);
code = ENOENT;
break;
}
if (xfdst != NULL)
fwrite(buf,1,xmsg.xmsg_len,xfdst);
}
if (xfdst != NULL)
fclose(xfdst);
return code;
}
int
initopt(int argc,char **argv)
{
// skip program name
argc = avslide(argc,argv,0);
while (argc > 0) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'u':
opt_udp = ! opt_udp;
break;
}
argc = avslide(argc,argv,0);
}
return argc;
}
int
avslide(int argc,char **argv,int argidx)
{
--argc;
for (; argidx < argc; ++argidx)
argv[argidx] = argv[argidx + 1];
argv[argidx] = NULL;
return argc;
}
% inetsftp/server.c
// server.c -- server program
#include <inetsftp/inetsftp.h>
cld_t *
reapall(void)
{
cld_t *cldcur;
cld_t *cldfree = NULL;
int status;
// reap all completed children
while (1) {
pid_t pid = wait(&status);
if (pid <= 0)
break;
for (FORALLCLD(cldcur)) {
if (cldcur->cld_pid == pid) {
cldcur->cld_status = status;
cldcur->cld_pid = 0;
break;
}
}
}
// find free child slot
for (FORALLCLD(cldcur)) {
if (cldcur->cld_pid == 0) {
cldfree = cldcur;
break;
}
}
return cldfree;
}
int
svr_doexec(char *cmd)
{
FILE *xfsrc;
char *cp;
char buf[SIZE];
int code = 0;
do {
xfsrc = popen(cmd,"r");
if (xfsrc == NULL) {
code = errno;
break;
}
while (1) {
cp = fgets(buf,sizeof(buf),xfsrc);
if (cp == NULL)
break;
int len = strlen(buf) + 1;
xmsgsnd(CMD_PAYLOAD,buf,len);
}
fclose(xfsrc);
} while (0);
xmsgsnd(CMD_EOF,NULL,code);
return code;
}
int
docld(void)
{
xmsg_t xmsg;
char payload[SIZE];
int stopflg = 0;
cldcur->cld_pid = getpid();
while (1) {
if (stopflg)
break;
xmsgrcv(&xmsg,payload);
stopflg = opt_udp;
switch (xmsg.xmsg_cmd) {
case CMD_UPLOAD:
recv_file(payload);
break;
case CMD_DNLOAD:
send_file(payload);
break;
case CMD_EXEC:
svr_doexec(payload);
break;
case CMD_STOP:
printf("stopping ...\n");
stopflg = 1;
break;
}
}
return stopflg;
}
void
server_tcp(void)
{
int b;
// now the socket is created and the binding is done, the server must listen
// for connection requests from clients
// listen funtion with argument socket file descriptor and set to maximum 10
// clients
b = listen(server_fd, MAXCONN);
// if the listening doesn't work, it must be an error in the binding process
if (b == 0) {
printf("Listening...\n");
}
else {
perror("Listening error");
exit(1);
}
while (1) {
// accept a new connection on the socket with the specified socket file
// descriptor
addr_size = sizeof(sock_addr);
sock_fd = accept(server_fd, (struct sockaddr *) &sock_addr, &addr_size);
cldcur = reapall();
cldcur->cld_address = sock_addr;
cldcur->cld_addrsize = addr_size;
cldcur->cld_pid = fork();
if (cldcur->cld_pid == 0) {
docld();
exit(0);
break;
}
}
}
void
server_udp(void)
{
sock_fd = server_fd;
cldcur = reapall();
while (1) {
docld();
}
}
int
server(int argc,char **argv)
{
// ip address
char *ip = "127.0.0.1";
// port number
int port = 8080;
int b;
// socket file descriptor
setlinebuf(stdout);
setlinebuf(stderr);
// TCP socket, because data type is stream
server_fd = socket(AF_INET, opt_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
// if the socket file descriptor is negative is an error in creation of the
// socket
// output an error message
if (server_fd < 0) {
perror("Error in creating socket");
exit(1);
}
printf("Socket server created\n");
// family of the server address IPv4
server_addr.sin_family = AF_INET;
// port of the server
server_addr.sin_port = port;
server_addr.sin_addr.s_addr = inet_addr(ip);
// bind the socket (port number to ip address)
b = bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (b < 0) {
perror("Binding error");
exit(1);
}
printf("Binding accomplished\n");
if (opt_udp)
server_udp();
else
server_tcp();
return 0;
}
int
main(int argc,char **argv)
{
argc = initopt(argc,argv);
return server(argc,argv);
}
% inetsftp/inetsftp.h
// inetsftp.h -- file transfer
#ifndef _inetsftp_h_
#define _inetsftp_h_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/wait.h>
#ifdef _INETSFTP_GLO_
#define EXTRN_INETSFTP /**/
#else
#define EXTRN_INETSFTP extern
#endif
#if DEBUG || _USE_ZPRT_
#define dbgprt(_fmt...) \
printf(_fmt)
#else
#define dbgprt(_fmt...) \
do { } while (0)
#endif
#define SIZE 4096
#define MAXCONN 10
// message header
typedef struct {
int xmsg_cmd;
int xmsg_len;
} xmsg_t;
enum {
CMD_NONE,
CMD_UPLOAD, // upload file
CMD_DNLOAD, // download file
CMD_PAYLOAD, // packet has payload data
CMD_NOEXIST, // file does not exist
CMD_EXEC, // execute command on server
CMD_EOF, // end of payload
CMD_STOP // stop request
};
typedef struct {
pid_t cld_pid;
int cld_status;
struct sockaddr_in cld_address;
socklen_t cld_addrsize;
} cld_t;
EXTRN_INETSFTP cld_t *cldcur;
EXTRN_INETSFTP cld_t cldlist[MAXCONN];
#define FORALLCLD(_cld) \
_cld = &cldlist[0]; _cld < &cldlist[MAXCONN]; ++_cld
EXTRN_INETSFTP int opt_udp;
EXTRN_INETSFTP int server_fd;
EXTRN_INETSFTP struct sockaddr_in server_addr;
EXTRN_INETSFTP int sock_fd;
EXTRN_INETSFTP struct sockaddr_in sock_addr;
EXTRN_INETSFTP socklen_t addr_size;
#include <inetsftp/inetsftp.proto>
#endif
% inetsftp/inetsftp.proto
// /home/cae/OBJ/ovrgen/inetsftp/inetsftp.proto -- prototypes
// FILE: /home/cae/preserve/ovrbnc/inetsftp/common.c
// common.c -- common code
ssize_t
xerr(const char *reason);
void
ipshow(const struct sockaddr *sock_addr,socklen_t sock_size);
ssize_t
xrecv(void *buf,size_t buflen);
ssize_t
xsend(const void *buf,size_t buflen);
ssize_t
xmsgrcv(xmsg_t *xmsg,void *buf);
ssize_t
xmsgsnd(int cmd,const void *buf,int len);
int
send_file(const char *file);
int
recv_file(const char *file);
int
initopt(int argc,char **argv);
int
avslide(int argc,char **argv,int argidx);
% inetsftp/Makefile
# /home/cae/preserve/ovrbnc/inetsftp -- makefile
PGMTGT += client
PGMTGT += server
CURSRC += common.c
ifndef COPTS
COPTS += -O2
endif
CFLAGS += $(COPTS)
CFLAGS += -g
CFLAGS += -Wall
CFLAGS += -Werror
CFLAGS += -I..
all: $(PGMTGT)
client: client.c $(CURSRC) $(LIBSRC)
cc -o client $(CFLAGS) client.c $(CURSRC) $(LIBSRC)
server: server.c $(CURSRC) $(LIBSRC)
cc -o server $(CFLAGS) server.c $(CURSRC) $(LIBSRC)
clean:
rm -f $(PGMTGT)