数次操作后读取returns0

Read returns 0 after a few operations

我在客户端和服务器之间有一个 TCP 多线程通信。我不知道为什么第三次使用读取函数时它只给出 0.

  1. 客户代码
while(1)
    {
        /* citirea raspunsului dat de server
              (apel blocant pina cind serverul raspunde) */
        if (read(sd, msg, 1024) < 0) {
            perror("[client]Eroare la read() de la server.\n");
            return errno;
        }

        /* afisam mesajul primit */
        printf("[client]Mesajul primit este: %s\n", msg);
        if(strcmp(msg, "Conexiune incheiata") == 0)
            break;
        memset(mesaj,0,256);

        cin>>mesaj;

        /* trimiterea mesajului la server */
        if (write(sd, mesaj, 1024) <= 0) {
            perror("[client]Eroare la write() spre server.\n");
            return errno;
        }

    }
    /* inchidem conexiunea, am terminat */
    close (sd);
  1. 服务器主要代码和我发现问题的函数

#include "ConectareServer.h"
using namespace std;
/* portul folosit */
#define PORT 2825

/* codul de eroare returnat de anumite apeluri */
extern int errno;

typedef struct thData{
    int idThread; //id-ul thread-ului tinut in evidenta de acest program
    int cl; //descriptorul intors de accept
}thData;

static void *treat(void *); /* functia executata de fiecare thread ce realizeaza comunicarea cu clientii */
void raspunde(void *);

#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
int main ()
{
    struct sockaddr_in server;  // structura folosita de server
    struct sockaddr_in from;
    int nr;     //mesajul primit de trimis la client
    int sd;     //descriptorul de socket
    int pid;
    pthread_t th[100];    //Identificatorii thread-urilor care se vor crea
    int i=0;


    /* crearea unui socket */
    if ((sd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror ("[server]Eroare la socket().\n");
        return errno;
    }
    /* utilizarea optiunii SO_REUSEADDR */
    int on=1;
    setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

    /* pregatirea structurilor de date */
    bzero (&server, sizeof (server));
    bzero (&from, sizeof (from));

    /* umplem structura folosita de server */
    /* stabilirea familiei de socket-uri */
    server.sin_family = AF_INET;
    /* acceptam orice adresa */
    server.sin_addr.s_addr = htonl (INADDR_ANY);
    /* utilizam un port utilizator */
    server.sin_port = htons (PORT);

    /* atasam socketul */
    if (bind (sd, (struct sockaddr *) &server, sizeof (struct sockaddr)) == -1)
    {
        perror ("[server]Eroare la bind().\n");
        return errno;
    }

    /* punem serverul sa asculte daca vin clienti sa se conecteze */
    if (listen (sd, 2) == -1)
    {
        perror ("[server]Eroare la listen().\n");
        return errno;
    }
    /* servim in mod concurent clientii...folosind thread-uri */
    while (1)
    {
        int client;
        thData * td; //parametru functia executata de thread
        int length = sizeof (from);

        printf ("[server]Asteptam la portul %d...\n",PORT);
        fflush (stdout);

        // client= malloc(sizeof(int));
        /* acceptam un client (stare blocanta pina la realizarea conexiunii) */
        if ( (client = accept (sd, (struct sockaddr *) &from, reinterpret_cast<socklen_t *>(&length))) < 0)
        {
            perror ("[server]Eroare la accept().\n");
            continue;
        }

        /* s-a realizat conexiunea, se astepta mesajul */

        // int idThread; //id-ul threadului
        // int cl; //descriptorul intors de accept

        td=(struct thData*)malloc(sizeof(struct thData));
        td->idThread=i++;
        td->cl=client;
        pthread_create(&th[i], NULL, &treat, td);

    }//while
};
#pragma clang diagnostic pop
static void *treat(void * arg)
{
    struct thData tdL;
    tdL= *((struct thData*)arg);
    printf ("[thread]- %d - Asteptam mesajul...\n", tdL.idThread);
    fflush (stdout);
    pthread_detach(pthread_self());
    raspunde((struct thData*)arg);
    /* am terminat cu acest client, inchidem conexiunea */
    printf ("[Server]Inchidem conexiunea cu threadul %d\n",tdL.idThread);
    close ((intptr_t)arg);
    return(NULL);

}


void raspunde(void *arg)
{
   
    struct thData tdL;
    tdL= *((struct thData*)arg);
    ConectareServer client;
    cout<<tdL.cl;
    client.Conectare(tdL.cl);

}
void ConectareServer::Conectare(int arg) {
    fd=arg;
    cout<<fd;
    memset(mesaj_catre_client, 0, 1024);
    sprintf(mesaj_catre_client,
            "Conectare reusita, pentru a continua, trebuie sa te loghezi. Sintaxa comenzilor este urmatoarea: \n<login>, pentru a realiza logarea la server\n"
            "<quit>, pentru a inchide programul");
    write(arg, mesaj_catre_client, sizeof(mesaj_catre_client));
    VerifComanda(arg);
}
void ConectareServer::VerifComanda(int fd) {
    memset(mesaj_catre_client, 0, 1024);
    memset(mesaj_de_la_client, 0, 1024);
    read(fd, mesaj_de_la_client, sizeof(mesaj_de_la_client));
    printf("[server]Mesajul a fost receptionat...%s\n", mesaj_de_la_client);
    if(strcmp(mesaj_de_la_client, "login")==0){
        Logare client(fd);
        client.Login();
        cout<<"Intra in Logare";
    }
    if (strcmp(mesaj_de_la_client, "quit") == 0) {
        sprintf(mesaj_catre_client, "Conexiune incheiata");
        write(fd, mesaj_catre_client, sizeof(mesaj_catre_client));
    }
    if(strcmp(mesaj_de_la_client, "login") != 0 && strcmp(mesaj_de_la_client, "quit") != 0)
    {
        cout<<"Crash";
    }
}

void Logare::Login() {
    memset(mesaj_catre_client, 0, 256);
    char user[10];
    char parola[8];
    sprintf(mesaj_catre_client,"Introduceti user-ul: ");
    write (fd, mesaj_catre_client, sizeof(mesaj_catre_client));
    read (fd, user, sizeof (user));

    printf ("[server]Mesajul a fost receptionat...%s\n", user);

    memset(mesaj_catre_client, 0, 256);
    sprintf(mesaj_catre_client,"Introduceti parola: ");
    write (fd, mesaj_catre_client, sizeof(mesaj_catre_client));

    read (fd, parola, sizeof (parola));
    printf ("[s]Mesajul a fost receptionat...%s\n", parola);

    if(Verificare_Logare(user,parola) == 1)
    {
        // Logare reusita
        Meniu client(fd, user);
        client.MeniuAplicatie();

    }
}

我得到了登录和用户的正确读数,但是当涉及到 parola 的读取时,它只是 returns 0,然后我可以在客户端中输入输入 window.

显示的代码中存在多个错误。 read() returning 0 是观察到的最少的问题。事实上,这可能只是所示代码中所有其他问题的最终结果,而不是真正的原因。第一个大问题多次出现,最好的例子就是服务器代码:

write (fd, mesaj_catre_client, sizeof(mesaj_catre_client));

此代码正在写入套接字。这完全忽略了 write() 中的 return 代码。当写入套接字时,你绝对不能保证任何东西 任何东西都会被写入套接字。你想在这里写 sizeof(mesaj_catre_client) 个字节。好吧,不幸的是你不能保证所有这些字节都会被写入。 write()只能写mesaj_catre_client和return1的第一个字节。或者写这个结构的一半左右,return实际写的字节数。您的代码必须检查它,但您的代码会忽略它,因此您实际上并不知道这里的套接字中写入了什么。

每次调用write()的return值必须必须检查并相应调整程序逻辑.通常的解决方案是重复 write(),这次是针对剩余的字节。

从套接字读取时出现相同的逻辑错误:

if (read(sd, msg, 1024) < 0) {

而且,再一次,您绝对不能保证,这将 return 服务器的整个消息,但可能只是它的前几个字节。即使服务器设法 write() 整个消息,这里的第一次调用也可能 return 它的一个字节,再次调用它会检索消息的其余部分(或它的一些附加部分) .

反之亦然。如果服务器发送了多条消息,而它们最终都小于 1024 字节,你猜怎么着?您将一口气 read() 全部搞定。显示的代码根本不准备处理这个问题。

正确地从套接字读取和写入比一开始看起来要困难得多。

所示代码的最后一组(潜在)问题:

td=(struct thData*)malloc(sizeof(struct thData));

// ...

static void *treat(void * arg)
{
    struct thData tdL;
    tdL= *((struct thData*)arg);

显示的代码是C++代码,它使用C++ 类。现代 C++ 是类型安全的。没有现代 C++ 代码需要使用 mallocfree,或将 to/from 转换为 void *。这仅在此处是必需的,因为您使用的是 C API。这应该用类型安全的 std::thread 代替,并且不需要 mallocnew 任何东西。

但是显示的代码的根本问题是它没有准备好并且不能正确处理套接字的 read/write 语义,这几乎不能保证每个套接字上读取和写入的内容read()write() 通话。您不能做出任何类型的假设,您必须小心地实现正确的逻辑,以从套接字写入和读取单个字节,并将它们组装成读取和写入的逻辑结构。 read()write() 都不会为你做这些,有必要自己实现所有逻辑。