C++ WinSock odbieranie pakietow recv

0

Witam otoz mam pytanie.
Ostatnio uzywajac gniazd napotkalem na problem do tej pory nie zwracalem zbytnio uwagi jak zwraca dane recv() ale gdy przyszla potrzeba troche bardziej sensownego uzywania gniazd napotykam problemy.
Z tego co widze funkcja recv() pobierane dane ktore maja swoja wielkosc i np niech wielkosc odbieranego pakietu bedzie 124 a ja ustawilem 512 przykladowo no to dalsza tablica jest ustawiana NULL'ami ok. Tylko teraz jest taki problem niech bedzie np 440 wielkosc pakietu tylko ze ten pakiet zostaje wyslany w postaci "dane-dane-dane\n-dane-dane\n"
odbieram to tak ze to jest jeden wielki ciag a nie 2 wyslane oddzielnie pakiety mimo ze sa wyslane oddzielnie.

Mozna by naklepac funkcje ktora tak naprawde bedzie rozdzielac pakiet na poszczegolne dane do napotkania znaku entera ale czy nie da sie zrobic tego jakos inaczej (prosciej), a moze cos takiego juz istnieje ? Nie chcialbym na nowo odkrywac ameryki.

0

http://4programmers.net/Forum/C_i_C++/185405-male_pytanie_odnosnie_winsock_recv

Byłem na Twoim poziomie a zabrałem się za klienta IRC... Pakiet tworzysz sobie sam parsując dane które śle server, musisz napisać sobie taka funkcje parsującą/klasę pakietu ;)

0

Troszkę mało rozumne to dla mnie jest może jakieś linki z którymi powinienem się zapoznać polecisz ?
Albo przykłady ?
Bo z tego co widzę w linku od Ciebie muszę od zrobić tak jak piszę.

0

Miałem to w swojej inżynierce tylko, że w QT. Ogólnie to było moje pierwsze spotkanie z gniazdami i było zdziwko. Jeśli 2 pakiety zostaną po sobie wysłane mogą zostać odczytane jako 1. Opracuj własną strukturę pakietu i funkcje go odczytującą. U mnie pakiety to było:
Rozmiar pakietu | separator (np. ";") | Dane. Wtedy dostając coś takiego:
5;abcde6;abcdef7l;
Wiedziałem, że zostały wysłane co najmniej 3 pakiety bo widzimy tu już zaczątek kolejnego. Moja funkcja działała na zasadzie:
1.Odczytaj kilka pierwszych bajtów pakietu (żeby poznać rozmiar)
2. Odczytaj rozmiar-już przeczytane bajty aby odczytać cały pakiet. Przykładowo jeśli rozmiar miałby maxymalnie 3 bajty to dla ciagu "5;abcde6;abcdef7"

Odczyta 5;a - ok. Pakiet będzie miał 5 bajtów. Odczytałem już 1 bajt z wiadomości (a). Teraz przeczytam 5-1 bajtów. Masz pierwszy pakiet itp
drugi krok to samo
3 krok - odczytałem 7; - ale nie ma żadnych bajtów w buforze więc zaczekam.

Proszę:

void Klient::wiadomoscDoSerwera(QByteArray wiadomosc)
{

    int rozmiar = wiadomosc.size();
    wiadomosc.insert(0,"$");
    wiadomosc.insert(0, QString::number(rozmiar));
    //porcjowanie
    int beg=0;
    int end=4000;
    QString calosc(wiadomosc);
    int wyslane=0;
    while(beg<wiadomosc.size())
    {
        if(beg+end>wiadomosc.size())
            end = wiadomosc.size()-beg;
        wyslane+=socket->write(QByteArray(calosc.toStdString().substr(beg, end).c_str()));
       // socket->flush();
        QCoreApplication::processEvents();
        QCoreApplication::processEvents();
        // while(socket->bytesAvailable()==0);
        // socket->waitForReadyRead();
        //QByteArray smieci = socket->readLine();
        beg+=4000;
    }
    //Debug::msgbox("Wyslane "+QString::number(wyslane));
}

Odbieranie:

void Serwer::obsluzWiadomosc()
{
    // QTcpSocket* socket = server->
    QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
   // socket->open(QIODevice::ReadWrite);
    // Debug::pom();
    static  QByteArray wiadomosc;
    static int rozmiar=0;
    int liczbaDostepnychBajtow = socket->bytesAvailable();
    while(socket->bytesAvailable())
    {
        if(wiadomosc=="")
        {
            wiadomosc =  socket->read(10);

            QList<QByteArray> wiadomosci = wiadomosc.split('$');

            rozmiar = wiadomosci[0].toInt();
            wiadomosc = wiadomosci[1];
        }

        while(wiadomosc.size()!=rozmiar)
        {
            liczbaDostepnychBajtow = socket->bytesAvailable();
            if(liczbaDostepnychBajtow == 0)
                break;
            wiadomosc.append(socket->read(qMin((rozmiar-wiadomosc.size()), 4000)));
            liczbaDostepnychBajtow = socket->bytesAvailable();
        }

        ////PARSOWANIE
        if(wiadomosc.size() == rozmiar)
        {
            QByteArray kopia(wiadomosc);
            wiadomosc = "";
            rozmiar =0;
            if(QString(kopia).toStdString().substr(0, 3) == "dmc")
            {
                qDebug()<<"Serwer. Dostalem wiadomosc dmc: "<<kopia;
                wyslijWszystkim(kopia);
            }else
            {
                emit przyszlaWiadomosc(kopia, socket);
            }
        }

        if(liczbaDostepnychBajtow == 0)
            return;
    }

}
0

Tak jak napisałeś, musisz w wybrany przez Ciebie sposób parsować(dzielić) przychodzące dane na pakiety. Innej opcji niestety nie ma, np pakiet kończy się '\r\n'. Odbierasz
"asdada\r\nasdgdfgdg\r\nasdadasda\r", musisz teraz wyciągać z tego po pakiecie czyli "asdada\r\n", "nasdgdfgdg\r\n", a tutaj masz wyjątek - przyszedł niepełny pakiet, musisz odebrać resztę i dopiero go analizować. Zadanie dla początkującego nie jest proste, wiem po sobie :P. Najprościej będzie chyba zrobić w ten sposób:
-sprawdzasz jaka jest maxymalna długość pakietu który śle server
-alokujesz sobie 3x więcej pamięci i do niej odbierasz, z tym że do recv podajesz max dł pakietu, jeżeli przyjdzie niepełny- masz miejsce żeby dociągnąć resztę ;). Wadą jest mała wydajność bo co odebranie pakietu będziesz kopiował nieużyte dane do nowego buffora a stary usuwał.

Raczej bez wskaźników nie ruszysz tematu, ja w tym kliencie IRC odbierałem po znaku :D. Co do linku to nie mam nic na oku, uczyłem się metodą prób i błędów.

0

Dobra spróbuje sobie parsować dokładnie tak jak tu mówisz sprawdzę tez jeszcze ten przykład w QT.
Gdy zrobię serwer który na końcu ma \n to to \n zawsze będzie występować na końcu pakietu czyli jakoś da rade logicznie to ogryźć.

No dobra tylko co programiści robią gdy tak naprawdę nie ma jak ustalić jakiego znaku chcemy się oczepić by odpowiadał końcowi pakietu ? :o
Zaczynam tęsknic za Delphi :D

0

A widzisz gdzieś u mnie znak końca pakietu? Chodzi o separator? Dokładnie to co jest robione w C++ z backslashem.

0

W większości normalnych protokołów stosuje się sekwencję /r/n albo jak kto woli #13#10 i radzę się tego trzymać gdyż wiele nadbudówek nad WinSock obsługuje automatyczne dzielenie pakietów które są tak dzielone, ale jeżeli tego nie masz to możesz zrobić to tak (pseudokod):
1.Pobierz nowe dane i doklej do bufora (na początku powinien być pusty)
2.Znajdź pozycję pierwszego #13#10 w buforze i wytnij go z bufora, obsłuż i powtórz pkt.2 ; jeżeli nie ma idź dalej
3.Zachowaj gdzieś bufor do następnego pobierania danych

Proste jak drut..

No dobra tylko co programiści robią gdy tak naprawdę nie ma jak ustalić jakiego znaku chcemy się oczepić by odpowiadał końcowi pakietu ? :o

To wybierają jakiś (często #13#10) a potem go stosują a jak się pojawi taki sam w danych to go konwertują.

Zaczynam tęsknic za Delphi

Widać w Delphi nie przerabiałeś rzeczy na takim poziomie bo to wszystko też tak jest...

0

Czyli w moim wyobrażeniu wygląda to tak:

Odbieram dane
Sprawdzam sobie ile występuje "\r\n" (załóżmy że 2 i 3ci nie pełny)
Wysyłam dane do parsowania funkcja widzi "aha mam 2 pełne pakiety i trzeci niepełny więc obcinam zmienną do pierwszego entera i wiem że to jest jeden cały pakiet więc wysyłam go do innej funkcji która sobie sprawdzi co przy takim pakiecie zrobić a gdy funkcja która dostała jeden pakiet skończy swe działanie ja obetnę resztę do drugiego \r\n i znowu jej wyśle do obsłużenia teraz gdy już mam drugi pakiet obsłużony połowę trzeciego wrzucę do innej zmiennej bo jest on niepełny a gdy przyjdzie kolejny pakiet wiem ze na jego początek muszę wkleić wcześniejszy obcięty bufor"

I tak to ma działać ?
Zależy mi bardzo na tym aby moje programy nie odkrywały na nowo ameryki przez co nie robiły się "mulaste" ale wygląda na to że to właśnie tak ma działać jak wyżej napisałem i nic lepszego nie wymyśli. Ok dzięki wszystkim za odpowiedzi!

// PS w Delphi są komponenty więc nie było trzeba tak dogłębnie się skupiać jak działa dana funkcja.

0

Metoda z terminatorami \r\n jest dobra przy przesyłaniu tekstu. Jeśli chcesz przesyłać dane binarne bez potrzeby kodowania ich w specjalny sposób (tak aby nie zawierały bajtów \r\n) możesz użyć metody z wysłaniem rozmiaru pakietu w nagłówku pakietu.

(uwaga, kod niesprawdzony)

static const unsigned PACKET_SIZE_BYTES = 2;

struct Packet {
    char buffer[1400]; // bajty pakietu, pierwsze PACKET_SIZE_BYTES to rozmiar pakietu
    unsigned pos; // ilość bajtów w buforze

    Packet() { this->Reset(); }
    bool HasSize() const { return this->pos >= PACKET_SIZE_BYTES; }
    unsigned Size() const { return this->buffer[0] + this->buffer[1] << 8 }
    void Reset() { this->pos = 0; }
};

Packet *RecvPacket(SOCKET s, Packet *p)
{
    unsigned bytes_to_recv = // ilość bajtów do odebrania (łącznie z już odebranymi)
        p->HasSize() ?       // rozmiar odebrany ?
        PACKET_SIZE_BYTES :  // odbieramy najpierw rozmiar pakietu (2 bajty)
        p->Size();           // jeśli rozmiar jest znany odbieramy dalsze dane pakietu

    while (p->pos < bytes_to_recv) {
        /* odbieramy porcję danych */
        res = recv(s, p->buffer + p->pos, bytes_to_recv - p->pos, 0);

        /* sprawdzamy błędy, w przypadku EWOULDBLOCK przerywamy (nie chcemy
         * blokować), odbieranie zostanie dokończone później */
        if (res == -1) {
            if (WSAGetLastError() != EWOULDBLOCK) CloseConnection(); // coś poszło nie tak, zamykamy połączenie
            return NULL;
        }

        /* sprawdzamy, czy klient zamknął połączenie */
        if (res == 0) {
            CloseConnection(); // klient zamknął połączenie, więc my też
            return NULL;
        }
        
        /* odebraliśmy 'res' bajtów */
        p->pos += res;

        /* jeśli właśnie odebraliśmy rozmiar to już wiemy ile bajtów zawiera całość */
        if (p->pos == PACKET_SIZE_BYTES) {
            bytes_to_recv = p->Size();

            if (bytes_to_recv > sizeof(p->buffer)) { // błędny rozmiar, za dużo
                CloseConnection();
                return NULL;
            }
        }
    }

    return p; // pakiet gotowy
}

Packet p;

void ReceivePackets(Socket *s)
{
    while (RecvPacket(&p) != NULL) {
        HandlePacket(p);
        p.Reset();
    }
}