0

I am trying to implement a UDP version for file transfer ( for learning purposes, i do not care about reliability :) ).

Currently in the TCP version, I have the following code on the server side :

while (1)
{
    /* read file in chunks of 256 bytes */
    unsigned char buff[256] = {0};
    int nread = fread(buff, 1, 256, fp);
    /* If read was success, send data. */
    if (nread > 0)
    {
        printf("Sending \n");
        send(connfd, buff, nread, 0);
    }
    if (nread < 256)
    {
        if (feof(fp))
            printf("End of file\n");
        if (ferror(fp))
            printf("Error reading\n");
        break;
    }
}
close(connfd); // client-specific socket
close(serverfd); // listening socket

and on the client side of the TCP version, I have this

while ((bytesReceived = recv(sockfd, recvBuff, sizeof(recvBuff), 0)) > 0) 
{
    fwrite(recvBuff, 1, bytesReceived, fp); 
}

This code runs perfectly. The reason I believe that the while loop on the client side terminates is that recv() returns 0 when the server process shuts down orderly on calling close(serverfd). On removing 'close(serverfd)', the client process hangs which I think is due to recv() being blocking by default.

Now coming to my UDP implementation:

Server side:

char send_buf[256];
while((bytes_read = fread(send_buf, sizeof(char), 256, fp))>0)
{
    res = sendto(server_socket, send_buf, sizeof(send_buf), 0, (struct sockaddr*)&si_server, sizeof(si_server));
    if(res == -1) die("send failed");
}
if(!feof(fp)) die("error in reading file");

close(server_socket);

Client side:

while((bytes_rcvd = recvfrom(client_socket, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&si_server, &si_server_len)) > 0)
{
    fwrite(recv_buf, 1, bytes_rcvd, fp);
}

close(client_socket);

In this UDP version, the client side always hangs. I am not able to figure out, what would be the termination condition for the while loop in the client side. Apparently for UDP, upon closing the server socket, recfrom() does not return 0 (unlike for TCP). How do I figure out when to stop calling recvfrom() ?

Thanks!!

rohit_r
  • 623
  • 1
  • 6
  • 18
  • 4
    You need to design a method in your protocol for the sender to tell the receiver when it's done. Take a look at the design of the TFTP protocol to see one way to do this. – Barmar Apr 01 '21 at 18:40
  • 3
    UDP doesn't have connections, there's no way for the receiver to know when the sender has closed their socket. You need to implement something in the application protocol for this. – Barmar Apr 01 '21 at 18:41
  • 3
    You can implement some kind of protocol on top of UDP, for example using a fixed-sized header field containing the total size of the data to be sent. Or use a packet structure that indicates if there are more packets coming or not. Or have a special packet meaning "end of data". – Some programmer dude Apr 01 '21 at 18:41
  • 4
    In fact, maybe you should just try implementing the TFTP protocol. The first T stands for Trivial, so it's not too hard to implement. – Barmar Apr 01 '21 at 18:42
  • @Barmar and someprogrammerdude, thanks for your suggestions! I shall give those a try. Also may I know if the reason for termination in case of TCP is indeed the one I mentioned in my question? Do not like to have any loopholes in my understanding ;). Thanks again. – rohit_r Apr 01 '21 at 18:45
  • 2
    Your TCP understanding is correct. TCP is connection-oriented, and when the client closes the connection, the server receives an EOF indication. – Barmar Apr 01 '21 at 18:47
  • 1
    You could send a final `sendto` with a zero-sized buffer. The other end will receive the zero-sized message and could interpret that as "no more data". – Mark Tolonen Apr 01 '21 at 23:50

2 Answers2

1

There are three approaches to size management with UDP:

  • Give each message a size, and prefix your submissions with such size - you will keep reading until you have read size bytes.
  • Send the whole message in the single UDP packet - quite feasible if message size is less than MTU size (best avoided for messages longer than MTU and impossible for messages longer than max unsigned short)
  • Introduce a terminator in the message, which delineates end of message. Often impractical.

In your case, option 2 seems feasible. Your message size is 256 bytes - well within standard MTU - and could be send in a single go. You will have to first assemble the message in the buffer, and than send the whole buffer.

Please note, your loop for data reading from the file seems broken to me. You can only read less bytes than requested if there is less bytes in the file, but in this case your eof check will return true. I would be very surprised to learn that you have observed your file reading loop to execute more than once.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
1

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)
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • You can use `sendmsg()` to send the header and data in one datagram, if Perl has it, which it surely does; and `recvmsg()` on the receiving end to split them. – user207421 Apr 02 '21 at 01:24
  • @user207421 I'll have to be more clear with the header info in my archive format. The actual code is `c` [_not_ `perl`--the perl code just extracts the `.c`, `.h`, and `Makefile` files]. I believe I did mention using `sendmsg` to send everything at once. – Craig Estey Apr 02 '21 at 05:17