简体   繁体   中英

TCP client / server : when stop read socket

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);
}

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM