简体   繁体   中英

Output is delayed with UNIX domain socket

I am trying to write a simple server and client that will act as a shell, in C. The server receives data from the client in the form of system commands, executes them and redirects the output to the socket. The client then reads the socket, and prints it to stdout. It works well when I input existing system commands like ls or df , and it works with arguments as well. So ls -l works just fine. However, when I enter a command that isn't recognized, either the server or client (I don't know which one) slips and the output is somehow delayed. I'll show you what I mean.

This is my server.c :

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SOCK_PATH "echo_socket"

int main(void)
{
    int s, s2, t, len;
    struct sockaddr_un local, remote;
    char str[1024];

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    local.sun_family = AF_UNIX;
    strcpy(local.sun_path, SOCK_PATH);
    unlink(local.sun_path);
    len = strlen(local.sun_path) + sizeof(local.sun_family);
    if (bind(s, (struct sockaddr *)&local, len) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(s, 5) == -1) {
        perror("listen");
        exit(1);
    }

    for(;;) {
        int done, n;
        printf("Waiting for a connection...\n");
        t = sizeof(remote);
        if ((s2 = accept(s, (struct sockaddr *)&remote, &t)) == -1) {
            perror("accept");
            exit(1);
        }

        printf("Connected.\n");

        done = 0;
        do {
            memset(str,0,strlen(str));
            n = recv(s2, str, 1024, 0);
            if (n <= 0) {
                if (n < 0) perror("recv");
                done = 1;
            }
            int cpid = fork();
            int e;
            if (cpid == 0)
            {
                printf("executing: %s\n", str);                
                dup2(s2, STDOUT_FILENO);
                dup2(s2, STDERR_FILENO);
                //system(str);
                execl("/bin/sh", "sh", "-c", str, (char *) 0)
                str[0] = 0;
                exit(0); 
            }
            wait();
        } while (!done);

        close(s2);
    }

    return 0;
}

And this is my client.c :

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_PATH "echo_socket"

int main(void)
{
    int s, t, len;
    struct sockaddr_un remote;
    char str[1024];

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    printf("Trying to connect...\n");

    remote.sun_family = AF_UNIX;
    strcpy(remote.sun_path, SOCK_PATH);
    len = strlen(remote.sun_path) + sizeof(remote.sun_family);
    if (connect(s, (struct sockaddr *)&remote, len) == -1) {
        perror("connect");
        exit(1);
    }

    printf("Connected.\n");

    while(str[0] = '\0', printf("Server shell> "), fgets(str, 1024, stdin), !feof(stdin)) {
    if (str[0] == '\n') continue;
    str[strlen(str) - 1] = '\0';
    if (send(s, str, strlen(str), 0) == -1) {
            perror("send");
            exit(1);
        }
        str[0] = '\0';
        if ((t=recv(s, str, 1024, 0)) > 0) {
            str[t] = '\0';
            printf("%s\n", str);
        } else {
            if (t < 0) perror("recv");
            else printf("Server closed connection\n");
            exit(1);
        }
    }

    close(s);

    return 0;
}

Here is an example of usage and output:

Trying to connect...
Connected.
Server shell> ls
client
client.c
echo_socket
server
server.c

Server shell> m
sh: 1:
Server shell> df
m: not found

Server shell> df
Filesystem      1K-blocks       Used Available Use% Mounted on
/dev/sda1         3596128    2655360    738376  79% /
udev                10240          0     10240   0% /dev
tmpfs              204880       4872    200008   3% /run
tmpfs              512196      39624    472572   8% /dev/shm
tmpfs                5120          4      5116   1% /run/lock
tmpfs              512196          0    512196   0% /sys/fs/cgroup
/dev/sda8         3750424     215924   3324276   7% /home
/dev/sda7          365639      65527    276714  20% /tmp
/dev/sda5         1791524     296880   1385588  18% /var
tmpfs              102440          4    102436   1% /run/user/116
tmpfs              102440          8    102432   1% /run/user/1000
/dev/sr0            57632      57632         0 100% /media/cdrom0
Operativsystem 1953512444 1804472304 149040140  93% /media/sf_Operativsystem

As you can see, it works fine until I input m , which isn't recognized. Could someone help me out? If you need more information, please let me know.

Your client is receiving the response incorrectly.

recv is allowed to receive any number of bytes - anything from one byte, to all the data that's available.

You are only calling recv once, which means you might not read the entire response. You need to keep calling it until you get to the end of the response. But how do you know when that is?

Typical ways are to either send the length of the response before sending the response, or to send a marker after the response that means "the response is done". The former is difficult when you don't know the response in advance, so you could go with the latter.

I'd suggest using a NUL byte ( '\\0' ) to mark the end of the response, as it should never appear in text. The server should send one after it executes the program, and the client should keep receiving and printing until it sees a NUL byte. (And then it should print everything up to the NUL byte; you might receive part of the response in the same call as you receive the NUL byte!)

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