-1

I am facing multiple problem creating a small ftp like client / server (tcp)

the client have a prompt.

the How to stop receiving issue. Sending data throught my socket from my client to server or vise-versa.

For example, from the server sending some string to the client. How the client know when to stop reading the socket and leave the recv() loop to print back the prompt

That why i created transfert functions to know when to stop by pre-send if it's the last send or not (CONT - DONE) and it work great. (code below)

Then i needed to execute command like ls on the server and send the result to the client, so i thought 2 options.

  • dup execv() in a char* and use my transfert functions.
  • dup execv() output directly in my socket.

The second options is cleaner, but it's make me facing the first issue about how to stop the recv() loop.

bonus question: how to dup execv() to a string. i thought using mmap thanks to the fd parameter, but i still need to know the size in advance.

# define BUFFSIZE 512
# define DONE "DONE"
# define CONT "CONT"

int     send_to_socket(int sock, char *msg)
{
    size_t  len;
    int     ret[2];
    char    buff[BUFFSIZE+1];

    len = strlen(msg);
    bzero(buff, BUFFSIZE+1);
    strncpy(buff, msg, (len <= BUFFSIZE) ? len : BUFFSIZE);
    /*strncpy(buff, msg, BUFFSIZE);*/
    ret[0] = send(sock, (len <= BUFFSIZE) ? DONE : CONT, 4, 0);
    ret[1] = send(sock, buff, BUFFSIZE, 0);
    if (ret[0] <= 0 || ret[1] <= 0)
    {
        perror("send_to_socket");
        return (-1);
    }
    // recursive call
    if (len > BUFFSIZE)
        return (send_to_socket(sock, msg + BUFFSIZE));
    return (1);
}

char    *recv_from_socket(int cs)
{
    char    state[5];
    char    buff[BUFFSIZE+1];
    char    *msg;
    int     ret[2];

    msg = NULL;
    while (42)
    {
        bzero(state, 5);
        bzero(buff, BUFFSIZE+1);
        ret[0] = recv(cs, state, 4, 0);
        ret[1] = recv(cs, buff, BUFFSIZE, 0);
        if (ret[0] <= 0 || ret[1] <= 0)
        {
            perror("recv_from_socket");
            return (NULL);
        }
        // strfljoin(); concat the strings and free the left parameter
        msg = (msg) ? strfljoin(msg, buff) : strdup(buff);
        if (strnequ(state, DONE, 4))
            break ;
    }
    return (msg);
}
albttx
  • 3,444
  • 4
  • 23
  • 42
  • 1
    Given that it is impossible for `msg` to be non-null in `msg = (msg) ? strfljoin(msg, buff) : strdup(buff);` I suspect that either you haven't posted your actual code, or you haven't though about this much yourself. – EOF Feb 07 '17 at 18:24
  • @EOF msg is init to NULL, so the first loop will init msg with an alloc'ed string, needed then to strfljoin() to be able to `free` the left parameter. – albttx Feb 07 '17 at 18:29
  • I'm having trouble following what you mean by your two options. Are they to capture the output of the command in memory, and then to send it, *vs*. duping the socket file descriptor onto the external program's standard output? – John Bollinger Feb 07 '17 at 18:45
  • @JohnBollinger update question, hope it's easier to understand :) – albttx Feb 07 '17 at 18:58
  • 1
    Given that this problem is already completely solved in the FTP protocol, and given that you're writing an 'FTP-like' system, why not *use* the FTP protocol? 'Dup `execv()` to a string is meaningless. – user207421 Feb 07 '17 at 21:59

2 Answers2

1

You have judged rightly that to communicate anything other than an undifferentiated stream over a stream-oriented socket, you need to apply some sort of application-layer protocol between the communicating parties. That's what your send_to_socket() and recv_from_socket() functions are doing, though they are flawed.*

Supposing that you do require the use of an application-layer protocol, it simply is not an option to make the child process write directly to the socket, unless your particular ALP can accommodate encapsulating the entire program output as a single chunk, which the one you're using cannot do.

With that said, you have at least one other option you have not considered: have the parent send the child's output out the socket as the child produces it, rather than collecting all of it and sending it only afterward. This would involve establishing a pipe between child and parent, and probably a separate version of send_to_socket() that reads the data to send from a FD instead of from a string. You would presumably accumulate it one modest-sized bufferful at a time. This approach would be my recommendation.

bonus question: how to dup execv() to a string. i thought using mmap thanks to the fd parameter, but i still need to know the size in advance.

mmap() takes a file descriptor argument designating the file to map, but that does not mean it works with just any file descriptor. It is only guaranteed to work with FDs designating regular files and shared-memory objects, and you cannot expect it to work for FDs designating transient data conduits. To capture the output of the child process in memory, you would need to operate much like I described for your third option, but store the data read in a dynamically-allocated (and reallocated as needed) buffer instead of sending it to the client as it is read. This is potentially both expensive and messy.


* Flaws in your functions:

  1. They assume that the send() and recv() functions can be relied upon to either transfer exactly the requested number of bytes, or fail.

In fact, send() and recv() may both perform partial transfers. To avoid losing data, and / or falling out of sync, you must compare the return values of these functions with the number of bytes you tried to transfer to detect partial transfers, and in the event that a partial transfer occurs, you must issue another call to send the balance of the data. Since the same thing can happen again, you generally need to put the whole thing in a loop that keeps calling send() or recv() as appropriate until all the data are sent or a bona fide failure occurs.

  1. Recursion is a poor implementation choice for the send function. If you have a very large amount of data to send then you risk exhausting your stack, plus each recursive function call has a lot more overhead than just looping back.

  2. Sending the data in fixed-length blocks is unnecessary, and it requires the overhead of copying the data to a separate buffer before sending it.

Consider instead sending a message length instead of "CONT" or "DONE", followed by that many bytes (but see above). You could furthermore incorporate flag bits into the message length to convey additional information -- for instance, a bit that signals whether the current chunk is the last one.

  1. It is possible for your send() and recv() calls to fail for reasons unrelated to the connection and its continued viability. For example, they can be interrupted by a signal. Since you've no way to re-sync communication between sender and receiver if it is interrupted, you should be sure to terminate communication in the event of error, though that doesn't actually have to be handled by your send and recv functions themselves.
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

What is the point of while(42)

It is no different than while(1) If you simply wish it to loop forever

If I understand correctly you want to use execv to execute 'ls' and capture the output so you can send it.

Have a look at the 'popen' function instead, it will create a pipe file descriptor (and return it), then execute the command with the output connected to the pipe.

Then you can use regular read() system call to read the output from the file descriptor.

Something like this

#include <stdio.h>
FILE *f
char buf[BUF_MAX]
f = popen("ls", "r")

if (!f) 
    // send error message or whatever
    return

while (fgets(buf, BUF_MAX, f)) 
    // send buf with your other socket function

pclose(f);

If you don't want to use libc stdio, just low level system routines then the thing to do would be to check out fork() , pipe() and dup() system calls,

Use pipe() (gives you two file descriptor one for each end) first

Then fork()

Then close unused file descriptors(),

Then use dup() or dup2() to change fd 1 in the new process only, to the output fd number from the pipe call earlier.

Then finally you execv() to run the " ls" command and its output will to into the pipe

And in the original process you read() from the first fd returned by the pipe() call earlier

Since you want the client to know when the server had finished, you could do something like:

const int MSG_DATA = 1;
const int MSG_DONE = 2;
strict message {
  int messagetype;
  int len;
}
char buf[BUF_SIZE]


message msg;
// Put data into buf
// set msg.len
msg.messagetype= MSG_DATA;
send(msg,size of(msg))
send (buf,msg.len)

Then when you've finished.

msg.messagetype=MSG_DONE
msg.len = 0
send(msg,sizeof(msg))

On client:

Message msg;
Char buf[]

while()
   ...
   recv(&msg)
   ...
   if(msg.messagetype == MSG_DATA)
      recv(buf)
   elif msg.message type == MSG_DONE)
      DoSomething()

But, remember that your client should always be receiving packets anyway.

If it's not for a school exercise where you can't just do what you want, it's better to use an existing library like for example a message queuing library, zeromq perhaps, instead of doing it the hard way.

Also there's simple curl C library, you could use that to send HTTP, which has sorted out all these things already and can also do indeterminate length content.

Also (and I'm assuming you're using a TCP socket, not UDP), neither end of the connection can send N bytes and expect that the recv on the other site will get N bytes. The recv will only get what has currently been received and buffered by the network stack of the operating system, in its TCP buffers. And the receiver can also get several chunks that have been sent, in one recv. The packet size on the network is likely around 1400-1500 bytes, and your server may send a message which is several kB which will be split into several packets and may be processed by the receiver after the first packet, before the remainder come in, or your server may send several small messages with your DONE or CONT header, and the receiver may get all of these in one recv(). Even if you're lucky and the DONE or CONT was actually at the start of the TCP buffer, your code will read everything that is in the OS TCP buffer and will only check the First 4 bytes to see the DONE or CONT from the first message, and will treat all the rest as data. So you really need to send a length field. You could scrap the DONE or CONT completely and send the length instead, and send a 0 length to represent DONE. Then your client when it receives, can recv() everything it can get, into your buffer, then use the length field to process each message that is contained in that buffer in turn.

Chunko
  • 352
  • 1
  • 8
  • I know it's the same, just while 42 is more cool ;) it's a nice option but i need to use `execv()` and the problem is still that my client don't know when to stop reading the socket and print back the prompt – albttx Feb 07 '17 at 19:03
  • 1
    I see! Hitchikers guide. 42 is the answer to life, the universe, and everything! – Chunko Feb 07 '17 at 19:06
  • Thanks for the update, but it's now explaining how to stop the client `recv()` loop when server is done sending – albttx Feb 07 '17 at 19:16
  • If you've finished sending, you use the socket shutdown() method on the server side. That makes the operating system send a TCP half-close. The client at the other end can still read till there's no more data and then close it – Chunko Feb 07 '17 at 19:20
  • But it's not ! it's finished sending this data, but the socket need to stay open! for the next command asked by the client. – albttx Feb 07 '17 at 19:23
  • then you need to send something that the client can recognise is the end of the output, or else you capture the length of the output first before sending it and send the length first. The client then expects to received that many bytes. Examples would be like in POP3 protocol. Or do something like in SMTP, where two blank lines end a message, but when you send content with two blank lines you have to escape it. Alternatively use a compression library like gzip (not sure) that can recognise the end of a data stream, – Chunko Feb 07 '17 at 19:24
  • Ah I see the CONT and DONE now. And I think I might see the problem. Because you're not sending the size of the data with each CONT or DONE, the problem is that if this is TCP (not a datagram protocol), the sent and received packrts aren't necessarily the same size as what you send() and recv() there's a TCP buffer on both sides. Your client might read: CONT:x CONT:y DONE all in a single recv() – Chunko Feb 07 '17 at 20:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/135112/discussion-between-chunko-and-ale-batt). – Chunko Feb 07 '17 at 20:21