Je fais une conversation simple qui doit envoyer un message texte rédigé par un membre à tous les autres membres. Le format du message que tout le monde doit recevoir est "[IP]: bonjour!". Le serveur doit également avertir tout le monde lorsque quelqu'un se connecte ou se déconnecte: «[IP] s'est connecté» et «[IP] est connecté» respectivement.

Voici un morceau de code qui implémente cette fonction du serveur. Vous pouvez commencer à chercher depuis la ligne qui a le commentaire "LE PROBLÈME EST ICI":

while (true)
{

// select() for reading

static constexpr int bufferSize = 1024;
static char buffer[bufferSize];

// this is for getting IP-address of sender
static sockaddr_in sockAddr;
static socklen_t sockAddrSize;

if (FD_ISSET(masterSocket, &set)) // masterSocket is a socket that establishes connections
{
    sockAddrSize = sizeof(sockAddr);
    int slaveSocket = accept(masketSocket, &sockAddr, &sockAddrSize); // slaveSocket is a client socket

    // setting a slaveSocket non-blocking

    sprintf(buffer, "[%d.%d.%d.%d] has connected\n", 
        (sockAddr.sin_addr.s_addr & 0x000000FF), 
        (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8, 
        (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16, 
        (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

    for (const auto &socket : slaveSockets)
        send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);

    slaveSockets.insert(slaveSocket);
}

for (const auto &socket : slaveSockets)
{
    if (FD_ISSET(socket, &set))
        continue;

    static int recvSize = recv(socket, buffer, bufferSize, MSG_NOSIGNAL);

    if (recvSize == 0 && errno != EAGAIN)
    {
        sockAddrSize = sizeof(sockAddr);
        getsockname(socket, (sockaddr *) &sockAddr, &sockAddrSize);
        sprintf(buffer, "[%d.%d.%d.%d] has disconnected\n", 
                    (sockAddr.sin_addr.s_addr & 0x000000FF), 
                    (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8, 
                    (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16, 
                    (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

        shutdown(socket, SHUT_RDWR);
        close(socket);
        slaveSockets.erase(socket);

        for (const auto &socket : slaveSockets)
            send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);
    }
    else if (recvSize > 0) // THE PROBLEM IS HERE
    {
        static char reply[bufferSize];
        sockAddrSize = sizeof(&sockAddr);
        getsocklen(socket, (sockaddr *) &sockAddr, &sockAddrSize);
        sprintf(reply, "[%d.%d.%d.%d]: %s\n",
            (sockAddr.sin_addr.s_addr & 0x000000FF),
            (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
            (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
            (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24,
            buffer);

        int senderSocket = socket;
        for (const auto &socket : slaveSockets)
        {
            if (socket == senderSocket)
                continue;

            send(socket, reply, strlen(reply), MSG_NOSIGNAL); // even tried the "strlen(reply) + 1"
        }
    }
}

}

Le problème est que les récepteurs ont chaque message émis de manière incorrecte: il est sorti complètement mais a également la fin des anciennes valeurs de tampon à la fin. Par exemple:

Le client A s'est connecté.

Le client B s'est connecté. Le client A a reçu "[127.0.0.1] s'est connecté".

Le client A a envoyé "bonjour". Le client B a reçu "[127.0.0.1]: bonjour \ n0.1] s'est connecté \ n".

Le client B a envoyé "quoi de neuf?". Le client A a reçu "[127.0.0.1]: quoi de neuf? \ Nil est connecté \ n".

Le client A s'est déconnecté. Le client B a reçu "[127.0.0.1] s'est déconnecté".

Comme vous pouvez le voir, les informations de connexion / déconnexion sont toujours générées correctement, mais le chat ne fonctionne pas correctement: il contient des parties d'informations de connexion / déconnexion à la fin.

Je crois sincèrement que j'utilise correctement les tampons et ne peux pas comprendre ce que je fais mal.

1
ghostinecatnewyear 3 nov. 2019 à 16:42

2 réponses

Meilleure réponse

Le buffer n'est pas une chaîne C terminée par un caractère nul après le retour de recv. C'est logique - et si vous transférez des données binaires? Ensuite, vous voudrez recv exactement (longueur du message) octets et n'ajoutez aucun zéro octet.

Notez que l'envoi d'un octet nul de fin dans send n'est pas la bonne chose à faire - votre récepteur dépend de l'expéditeur pour ajouter ce zéro octet, mais si l'expéditeur est malveillant, il ne peut pas ajouter zéro octet et provoquer toutes sortes de bogues et vulnérabilités - y compris les attaques DoS et l'exécution de code à distance.

Vous pouvez toujours compter sur l'expéditeur en ajoutant zéro octet, mais vous devez ensuite passer bufferSize-1 comme longueur de tampon à recv, et après un appel à recv définir le reply[bufferSize-1]=0. Mais peut-être que ce n'est toujours pas la meilleure chose à faire: l'une des nombreuses autres options consiste à passer une "longueur de message" en tant qu'entier non codé de 32 bits, vérifiez la longueur maximale (par exemple, aucun message ne dépasse 1 024 caractères et si c'est le cas, ne recevez rien et fermez simplement le socket), et recv exactement les octets de "longueur de message" passés dans le tampon. Vous devrez toujours ajouter l'octet nul final si vous avez l'intention d'utiliser le tampon comme chaîne de style C.

Modifier: IMPORTANT! Si vous utilisez TCP (SOCK_STREAM), utilisez toujours la longueur du message: le message peut (et un jour sera) lu par recv en fragments. Vous devez absolument les concaténer par vous-même dans un message entier.

2
smitsyn 3 nov. 2019 à 14:13

Juste pour ajouter à la réponse de smitsyn, vous devez utiliser la recvSize (c'est plus que 0 dans la branche avec le problème - puisque vous avez reçu quelque chose d'un des clients) pour définir le '\ 0' à l'intérieur du tampon.

Vos variables statiques ont été initialisées à 0 (par défaut) partout donc votre tampon contenait un '\ 0' immédiatement après le message le plus long que vous avez reçu (puisque le reste a été écrasé) et vous avez eu de la chance que sprintf l'a trouvé et n'est pas allé le chercher ailleurs dans la mémoire de votre programme (même hors limites).

Quelque chose dans ce sens devrait le faire fonctionner (pour un cas simple):

else if (recvSize > 0) // THE PROBLEM IS HERE
    {
     ...

     buffer[recvSize] = '\0'; // of course make sure it fits! could use a std::min(recvSize, bufferSize - 1)
     sprintf(reply, "[%d.%d.%d.%d]: %s\n",
            (sockAddr.sin_addr.s_addr & 0x000000FF),
            (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
            (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
            (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24,
            buffer); // now this print should work as expected 

Modifier: parce que je ne peux pas poster de commentaires :(

1
ben10 3 nov. 2019 à 14:23