2
votes

Socket Linux TCP: le client a envoyé des données mais le serveur se bloque toujours en lecture ()

J'ai un exemple simple client-serveur utilisant le socket TCP sur Linux. Le serveur écoute l'adresse de bouclage. Le client se connecte au serveur et envoie un entier plus une chaîne "END" pour marquer la fin des données. Le serveur lit les nombres, les ajoute tous et renvoie la somme totale. Cependant, mon serveur bloque parfois sur read() même si le client a envoyé toutes les données avec succès.

Voici le code:

server.c:

$ ./server
Waiting to accept a connection...
Accepted socket fd = 4
Read data: 3
Read data: 4
Read data: 5
Read data: 6
Read data: END
Waiting to accept a connection...
Accepted socket fd = 4
Read data: 3

client.c:

$ for i in {1..100}; do ./client 3 4 5 6; done
write END to socket, ret = 4
Result = 18
write END to socket, ret = 4

Exécutez le client plusieurs fois et le serveur bloquera l'appel read() à certains moments:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]) {
    struct sockaddr_in addr;
    int ret;
    int data_socket;
    char buf[100] = {0};
    int i = 0;

    data_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == data_socket) {
        perror("Create client socket error");
        exit(EXIT_FAILURE);
    }

    /* Connect to server socket */
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    ret = connect(data_socket, (const struct sockaddr *) &addr, sizeof(addr));
    if (-1 == ret) {
        perror("Connect error");
        exit(EXIT_FAILURE);
    }

    /* Send arguments */
    for (i = 1; i < argc; i++) {
        ret = write(data_socket, argv[i], strlen(argv[i]) + 1);
        if (-1 == ret) {
            perror("Write error");
            break;
        }
    }
    strcpy(buf, "END");
    ret = write(data_socket, buf, strlen(buf) + 1);
    printf("write %s to socket, ret = %d\n", buf, ret);
    if (-1 == ret) {
        perror("Write to socket error");
        exit(EXIT_FAILURE);
    }
    /* Read the result */
    memset(buf, 0, sizeof(buf));
    ret = read(data_socket, buf, sizeof(buf));
    if (-1 == ret) {
        perror("Read from client socket error");
        exit(EXIT_FAILURE);
    }
    buf[sizeof(buf) - 1] = 0;
    printf("Result = %s\n", buf);
    close(data_socket);
    exit(EXIT_SUCCESS);
}

Sortie serveur:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BACKLOG 5

int main(int argc, char *argv[]) {
    struct sockaddr_in addr;
    int down_flag = 0;
    int result = 0;
    int ret = 0;

    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("Create server socket error: %s\n");
        return 0;
    }

    /* Bind socket to loopback address */
    memset((void *) &addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1) {
        perror("Bind server socket failed");
        goto _exit;
    }

    if (listen(sfd, BACKLOG) == -1) {
        perror("Listen failed");
        goto _exit;
    }

    ssize_t num_rd = 0;
    char buf[100] = {0};
    for (;;)
    {
        printf("Waiting to accept a connection...\n");
        int cfd = accept(sfd, NULL, NULL);
        printf("Accepted socket fd = %d\n", cfd);
        result = 0;
        while ((num_rd = read(cfd, buf, sizeof(buf))) > 0) {
            /* Ensure the buffer is 0-terminated */
            buf[sizeof(buf) - 1] = 0;
            printf("Read data: %s\n", buf);

            /* Handle commands */
            if (!strncmp(buf, "DOWN", sizeof(buf))) {
                down_flag = 1;
                break;
            }
            if (!strncmp(buf, "END", sizeof(buf))) {
                break;
            }
            /* Add received summand */
            result += atoi(buf);
        }
        if (-1 == num_rd) {
            perror("Read error");
        }

        /* Send result */
        sprintf(buf, "%d", result);
        ret = write(cfd, buf, sizeof(buf));
        if (-1 == ret) {
            perror("Write error\n");
            goto _exit;
        }
        close(cfd);
        /* Quit on DOWN command */
        if (down_flag) {
            break;
        }
    }
_exit:
    close(sfd);
    return 0;
}

Le serveur bloque sur la ligne while ((num_rd = read(cfd, buf, sizeof(buf))) > 0) .

Edit: Ma question est de savoir pourquoi les blocs read() . AFAIK, read() bloquera jusqu'à ce qu'il y ait au moins 1 octet de données à lire à partir du socket. Dans ce cas, le client a envoyé plus de données que le serveur n'en a lu, donc je pense qu'il y a des données disponibles à lire à partir du socket. Alors pourquoi read() bloque-t-il toujours?


2 commentaires

Enregistrez le nombre total d'octets envoyés et le nombre total d'octets reçus. Vous constaterez qu'ils sont égaux. La read doit donc se bloquer puisque toutes les données envoyées ont été reçues.


Pourquoi ne pas utiliser les appels send et recv au lieu de read and write.


3 Réponses :


3
votes

Le cœur du problème est que le code teste le premier message dans un tampon et ignore la possibilité que le même tampon puisse contenir plusieurs messages, des messages partiels ou toute autre combinaison (voir modifier ). Pour cette raison, le message END est parfois ignoré et la boucle de read n'est jamais terminée.

Le code suppose qu'une seule read recevra exactement ce qu'un seul appel d' write avait envoyé.

Ceci est très inexact, rarement vrai et peut ne fonctionner que parfois, lorsque le client et le serveur sont sur la même machine.

Une seule read peut lire simultanément 2 appels d' write , ou la moitié d'un appel d' write , puis 1,5 autre appel d' write plus tard ...

TCP / IP (contrairement à UDP ) est un protocole de streaming et n'a aucune connaissance des limites des messages.


MODIFIER :

Pour clarifier (comme demandé dans le commentaire), imaginez qu'un appel à read recueille les données suivantes "1234\0EN" (la prochaine read recueillera "D\0" ) ... que fait le programme?

Une autre situation possible est que toutes les writes sont lues en une seule fois. c'est-à-dire que buf contient la chaîne "3\04\05\06\0END\0" .

Que se passe-t-il dans la boucle à ce stade?

Dans cet exemple de scénario, l'instruction if ( strncmp(buf, "END", sizeof(buf)) est toujours false (et non sécurisée), ce qui entraîne une situation où le serveur ne rompt jamais de la boucle while(read) .

Depuis le while en boucle continue, le serveur va tenter une autre read quand il n'y a pas de données disponibles, ce qui dans le blocage du serveur jusqu'à ce que le client envoie des données supplémentaires.


2 commentaires

Merci. Cependant, cela ne m'aide toujours pas à comprendre pourquoi le read() bloque dans ce cas, ou pour le dire à votre façon, ce read() lit 0 write() .


@theman ... hmmm, je modifierai ma réponse avec plus de détails, mais considérons une situation où read obtient "1234\0END\0" . Quand le while en rupture de boucle?



3
votes

Le terminateur nul doit être immédiatement après les données qui ont été lues, pas seulement à la fin du tampon. Sinon, lorsque vous essayez de traiter le tampon comme une chaîne, le contenu précédent fera partie de la chaîne.

while ((num_rd = read(cfd, buf, sizeof(buf)-1)) > 0) {

Et pour éviter que cela n'écrive en dehors du tampon, vous devez soustraire 1 de la taille du tampon lors de l'appel de read() :

buf[num_read] = '\0';

Le serveur se bloque alors car il ne reconnaît pas le message END qui le fait sortir de la boucle de lecture, et il n'y a plus rien à lire. Il bloquera jusqu'à ce qu'il y ait des données à lire ou EOF. Il obtiendra EOF lorsque le client fermera la connexion, mais le client ne le fera pas tant que le serveur n'aura pas envoyé le résultat.

Cependant, je pense que toute votre conception est peut-être vouée à l'échec. TCP est un protocole de flux, il n'a pas de limites de message. Chaque appel à write() dans le client n'entraîne pas nécessairement un seul read() dans le serveur, les écritures consécutives peuvent (et seront généralement) concaténées. Vous devrez repenser votre protocole pour gérer cela.


7 commentaires

Cette réponse résume bien les suspects habituels :)


Je suis conscient que consecutive writes can (and usually will) be concatenated . Cependant, ma principale préoccupation ici est de savoir pourquoi read() bloquerait. AFAIK, read() bloquera jusqu'à ce qu'il y ait au moins 1 octet de données à lire à partir du socket. Dans mon cas, le client a envoyé plus de données que le serveur n'en a lu, donc je pense qu'il y a des données disponibles à lire à partir du socket. Alors pourquoi read() bloque-t-il toujours?


J'ai mis à jour la réponse pour expliquer pourquoi elle bloque. S'il ne reconnaît pas le message END , il appelle à nouveau read() , il attend donc que le client envoie plus de données, mais le client a fini d'envoyer et attend une réponse.


tcp a une chance.


vous avez donc tous les deux commis une erreur. Vérifiez-le


Vérifiez quoi?


j'ai un code de travail à ce sujet avec des sockets de flux.



1
votes

le plus simple est d'utiliser un poll() .

upd:
Je pense que la vraie cause des choses écrites dans une autre réponse. vous voulez pipe où vous avez un ruisseau.
Mais
tu peux essayer ça

#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>    //socket
#include <netdb.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <netinet/tcp.h>
#include <string.h>
#include <poll.h>


int main(int argc , char *argv[])
{
    struct sockaddr_in addr = { AF_INET , htons( 8888 ) /* btc port */ , htonl(INADDR_LOOPBACK) };;
    int down_flag = 0;
    int result = 0;
    int ret = 0;
    bool c = !false;
    int bc = 1024;    

    int sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sfd < 0) {
        perror("Create server socket error: %s\n");
        return 0;
    }
    setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));
    setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (char *)&c, sizeof(c));
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *)&c, sizeof(c));
    setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (char *)&c, sizeof(c));
    setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (int *)&bc, sizeof(bc));
    setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (int *)&bc, sizeof(bc));
    /* Bind socket to loopback address */
    if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1) {
        perror("Bind server socket failed");
        goto _exit;
    }
    setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));

    if (listen(sfd, 128) == -1) {
        perror("Listen failed");
        goto _exit;
    }

    ssize_t num_rd = 0;
    enum { buf_size = 1025 };
    char buf[buf_size] = {0};

    struct pollfd pfd[1] = {{0}};
    int nready;

    for (;;)
    {
        printf("Waiting to accept a connection...\n");
        int cfd = accept(sfd, NULL, NULL);
        printf("Accepted socket fd = %d\n", cfd);
        result = 0;
        pfd[0].fd = cfd;
//      pfd[0].fd = sfd;
        pfd[0].events = POLLIN;
        while (!false) {
            memset(buf, 0, sizeof(buf));
            nready = poll(pfd, 1, 15 * 1000);
            num_rd = read(cfd, buf, 99);
            if (num_rd <= 0) break;
            buf[sizeof(buf) - 1] = '\0';
            printf("Read data: %s\n", buf);

            /* Handle commands */
            if (!strncmp(buf, "DOWN", strlen(buf))) {
                down_flag = 1;
                break;
            }
            if (!strncmp(buf, "END", strlen(buf))) {
                break;
            }
            int temp = 0;
            int f = sscanf(buf, "%d", &temp);
            if (f != 1)
            {
                printf("and then \n" );
                return (0);
            }
            result = result + temp;
        }
        if (-1 == num_rd) {
            perror("Read error");
        }

        memset(buf, 0, sizeof(buf));
        sprintf(buf, "%d", result);
        pfd[0].events = POLLOUT;
        nready = poll(pfd, 1, 15 * 1000);
        ret = write(cfd, buf, 99);
        if (-1 == ret) {
            perror("Write error\n");
            goto _exit;
        }
        close(cfd);
        /* Quit on DOWN command */
        if (down_flag) {
            break;
        }
    }
_exit:
    close(sfd);
    return 0;
}

finalement

#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>    //socket
#include <netdb.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <netinet/tcp.h>
#include <string.h>
#include <poll.h>

int main(int argc , char *argv[])
{
    struct sockaddr_in addr = { AF_INET , htons( 8888 ) /* btc port */ , htonl(INADDR_LOOPBACK) };;
    int down_flag = 0;
    int result = 0;
    int ret = 0;
    bool c = !false;
    enum { buf_size = 1025 };
    char buf[buf_size] = {0};
    int bc = 1024;    

    int data_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    setsockopt(data_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&c, sizeof(c));
    setsockopt(data_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));
    setsockopt(data_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&c, sizeof(c));
    setsockopt(data_socket, SOL_SOCKET, SO_REUSEPORT, (char *)&c, sizeof(c));
    setsockopt(data_socket, SOL_SOCKET, SO_SNDBUF, (int *)&bc, sizeof(bc));
    setsockopt(data_socket, SOL_SOCKET, SO_RCVBUF, (int *)&bc, sizeof(bc));
    ret = connect(data_socket, (const struct sockaddr *) &addr, sizeof(addr));
    if (-1 == ret) {
        perror("Connect error");
        exit(EXIT_FAILURE);
    }
    setsockopt(data_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));    /* Send arguments */

    struct pollfd pfd[1];
    int nready;
    pfd[0].fd = data_socket;
    pfd[0].events = POLLOUT;

    for (int k = 1; k < argc; k++)
    {
        nready = poll(pfd, 1, 15 * 1000);
        // if((pfd[0].revents & (POLLOUT|POLLHUP))) printf("tray \n" );
        memset(buf, 0, sizeof(buf));
        strcpy(buf, argv[k]);
        ret = write(data_socket, buf , 99);
        if (-1 == ret) {
            perror("Write error");
            break;
        }
    }

    memset(buf, 0, sizeof(buf));
    strcpy(buf, "END");
    nready = poll(pfd, 1, 15 * 1000);
    ret = write(data_socket, buf, 99);
    printf("write %s to socket, ret = %d\n", buf, ret);
    if (-1 == ret) {
        perror("Write to socket error");
        exit(EXIT_FAILURE);
    }
    /* Read the result */
    memset(buf, 0, sizeof(buf));
    pfd[0].events = POLLIN;
    nready = poll(pfd, 1, 15 * 1000);
    ret = read(data_socket, buf, 99);
    if (-1 == ret) {
        perror("Read from client socket error");
        exit(EXIT_FAILURE);
    }
    buf[sizeof(buf) - 1] = '\0';
    printf("Result = %10s\n", buf);
    close(data_socket);
    exit(EXIT_SUCCESS);
}

Et

SOCKET q = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bool c = 1;
setsockopt(q, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));

également une taille pour tous les trucs là-bas. Après une écriture, read_buffer devrait être plein pour que les autres attendent que la lecture soit terminée

courir:
./serv
for i in {1..100}; do ./client 3 4 5 6; done

aussi sur github:
https://github.com/alexeyneu/BlockZero/tree/master/onemore

Éditer:
avec 1kb / commande semble tenir contre certains gros transferts. C'est minimum pour les tampons SO_XXX


8 commentaires

Comment cela résout-il le problème sous-jacent ...? Pouvez-vous identifier le problème sous-jacent? Sinon, pourquoi répondre?


@Myst a l'air de ne pas savoir ce que fait poll() . pomper une grande partie de read vides avec un while() est certainement une mauvaise chose. ce n'est même pas ta question


Je suis l'auteur du framework facil.io , qui est un framework C pour les applications web et réseau. Je sais ce que fait le poll . Cependant, le poll ne résoudra pas le problème, car le problème avec le code en question n'est pas lié à l'interrogation ou aux E / S non bloquantes. Le problème avec le code est que la conception teste le premier message dans un tampon et ignore la possibilité que le même tampon puisse contenir plusieurs messages.


PS, je n'ai pas voté contre la réponse parce que je pense que vous essayez seulement d'aider, ce qui est bien. Cependant, je pense que cela ne répond pas à la question. Il s'agit plus d'un "voici une autre approche qui pourrait vous aider". En outre, la boucle lors de la read est une approche courante (et valide) lors de la gestion du blocage des E / S.


Ouais. nous en voyons un maintenant


@Myst haha pourquoi alors vous ne connaissez pas ce stackoverflow.com/questions/13479760/… . si utilisé avec socket opt, j'ai mentionné que cela devrait faire l'affaire


Peut-être que mes commentaires n'étaient pas clairs. Je sais comment fonctionne le poll , ainsi que epoll et setsockopt , comme vous pouvez le voir d'après mes exemples de code. Mais dans votre solution, vous déplacez le problème d' write en poll - vous ne résolvez pas le problème. Le problème est dans un endroit différent.


@Myst c'est peut-être. Je préfère poll() à l'écriture non bloquante qui bloque et tout cela.