Male pytanie odnosnie winsock & recv

0

Witam. Napisalem maly program analizujacy wiadomosci na irc, nie jest to spamer. Pomaga mi wyszuac ludzi o odpowiednich kryteriach. Wszystko dziala prawie dobrze... Od czasu do czasu wystepuje blad, tak jakby 2 wiadomosci lacza sie w 1 i sa odebrane na raz co powoduje bug'a. Tu moje pytanie czy moge w ten sposob uzyc funkcji recv?

recv(sServ, buffer, 512, 0);

Nie wiem czy moge ustawic na sztywno dlugosc "buffer" - wyczytalem w RFC irc'a ze maxymalna dlugosc pakietu to 512, normalnie jest to okolo 100 czyli spora czesc jest nie uzywana. Czy to moze powodowac problem? Mysle tez o napisaniu funkcji ktora bedzie odbierala po znaku zanim nie napotka '\r', ale to ostatecznosc. Mam nadzieje ze nie namotalem, pozdro ;)

0

http://msdn.microsoft.com/en-us/library/ms740121%28v=vs.85%29.aspx
czyli Twoje recv, odczyta maksymalnie 512 bajtów, a ile dokładnie wczyta możesz sprawdzić tak:
int n =recv(sServ, buffer, 512, 0);

jeśli koniecznie musisz mieć 512 to możesz zrobić tak: (pisane z ręki więc mogą być błędy)

int br = 0; //ilosc bajtow odebranych
while (br!=512)
{
  int n = recv(sServ, buffer, 512-br, 0);
  br+=n;
}

ale bardziej polecam zrobić pętle odbierającą zawsze np te 512 bajtów, parsowanie odebranych danych i odbieranie danych dalej (w jakimś wątku najlepiej).

zastanów się co rozumiesz przez słowo pakiet, bo recv służy do odbierania danych, a pakiety z tych danych musisz sobie sam uformować.

0

W programowaniu sieciowym jestem laik, wiem wiem ze pakiet powstaje po parsowaniu. Tak myslalem ze sie nie zrozumiemy ale sprobuje jeszcze raz ;p. Nie chodzi mi o to zeby koniecznie odebrac na raz te 512 bajtow, nawet tak nie chce. Chyba zle przedstawilem problem, probuje sie dowiedziec czy jezeli do recv() dam char buffer[512] to istnieje mozliwosc ze znajda sie w niej 2 male pakiety? Czy zawsze jest tak ze recv odbiera tylko jeden pakiet?

0

Dalej się nie rozumiemy. Co rozumiesz poprzez pakiet?
Generalnie obojętnie co odpowiesz na to pytanie, odpowiedź na Twoje brzmi: TAK, może odebrać więcej

0

Przez pakiet rozumiem ciag znakow zakonczony \r\n. Enyłej, otrzymalem odpowiedz i wiem co jest do poprawki. Jednak bede musial odbierac po znaku... Dzieki za pomoc :)

0

nie musisz odbierać po znaku. Możesz zostawić tak jak jest, tylko np zrobić dwa bufory:

  1. na odbieranie (np. ten co juz masz 512bajtow)
  2. na parsowanie (ok. 3x większy od tego wyżej, zakładając że najdłuższa długość linii to 512bajtów)
    w drugim buforze zapisz sobie takie dane:
    początek
    koniec
    i wyjdź z założenia że początek nie zawsze musi być w tym buforze przed końcem, ponieważ do tego bufora powinieneś zapisywać w kółko: (pisane z reki)
char buff[1536];
char buff_odb[512];
char* buff_end = buff+1536;
char* pocz = buff;
char* koniec = buff;
while (!koniecprogramu)
{
int n = recv(sServ, buff_odb, 512, 0);
if (koniec+n>buff_end)
  {
    int dokonca = (int)(buff_end-koniec);
    memcpy(koniec,buff_odb,dokonca);
    memcpy(buff,buff_odb+dokonca,n-dokonca);
    koniec = buff+n-dokonca;
  }
else
  {
    memcpy(koniec,buff_odb,n);
    koniec+=n;
  }
// parsowanie:
char* p= pocz;
while (p!=koniec)
  {
    // tutaj sobie sprawdzasz czy wystapil po kolei ciag \r\n i znak za miejscem tego wystapienia uystawiasz pocz = te_miejsce
    if (p==buff_end)
      p = buff;
  }
}
0

Dzis mam 6h lekcji na komputerze to przeanalizuje Twoj kod. Dzieki za zainteresowanie :)

0
arasso12 napisał(a)

Przez pakiet rozumiem ciag znakow zakonczony \r\n. Enyłej, otrzymalem odpowiedz i wiem co jest do poprawki. Jednak bede musial odbierac po znaku... Dzieki za pomoc :)

Jesli chodzi o Winsock to jestem zielony. Jeśli zachowuje się on inaczej niż sockety POSIXowe, to będę pewnie plótł bzdury i można zignorować ten wpis

Funkcji recv() wisi i powiewa co jest w danych które otrzymuje. Dla niej '\r\n' nie jest żadną informacją o strukturze danych. Sam musisz się zatroszczyć o odpowiedni podział na pakiety tego, co recv() odbierze. Tak, w jednym wywołaniu recv() możesz otrzymać więcej niż jedną linię (dane zakończone '\r\n'). Tak, w jednym wywołaniu recv() możesz otrzymać tylko część linii (brak '\r\n'). Tak, w jednym wywołaniu recv() możesz otrzymać conajmniej jedną pełną linię i część innej. Poczytaj o protokołach strumieniowych, TCP itp.

0

Racja, mialem mylne wyobrazenie pakietu...
@krwq dzieki za kod, ale moje rozwiazanie jest chyba prostsze. Jezeli zawiedzie to przejde do Twojej propozycji.

Wiec zakodzilem cos takiego:

 void serwer::odbierz(char buffer[512])
{
    //recv(sClient, buffer, DL_WIAD, 0);

    char c;
    int t=0;
    do
    {
        recv(sClient, &c, 1, 0);
        buffer[t]=c;
        t++;
    }
    while(c!='\n');

    buffer[t]=char(0);

  
}

Teraz faza testow, jak nie wysypie sie w godzinach szczytu bedzie super ;D. Dzieki za pomoc :)

0

to powinno działać, aczkolwiek możesz w pewnym momencie zacząć czuć, że to muli...
powinieneś jeszcze dodać jako ten ostatni parametr MSG_WAITALL,
sprawdzć czy funkcja zwróciła 1, jeśli nie to znaczy że wystąpił błąd lub połączenie zostało zamknięte

0

Dzieki za rady :)

@Kumashiro
Wiem ze separatorem jest '\r\n' ... zrobilem tak bo jak bede odbieral znak po znaku az do napotkania '\r' pierwszy "pakiet" bedzie ok a nastepny bedzie sie skladal z '\r'... To samo zrobilem tylko az do napotkania '\n' - na poczatku kazdego kolejnego pakietu po pierwszym byl znak nowej linii.

0
arasso12 napisał(a)

Wiem ze separatorem jest '\r\n' ... zrobilem tak bo jak bede odbieral znak po znaku az do napotkania '\r' pierwszy "pakiet" bedzie ok a nastepny bedzie sie skladal z '\r'... To samo zrobilem tylko az do napotkania '\n' - na poczatku kazdego kolejnego pakietu po pierwszym byl znak nowej linii.

Teoretycznie nie powinien w danych pojawić się sam znak '\n', ale co jeśli się pojawi? Twój kod uzna, że na nim kończy się pakiet, pomimo że nie jest to prawda. Jeśli już to odpowiednio flaguj odebranie '\r' i jeśli kolejnym znakiem jest '\n' to splituj na pakiet. Rozsądniej jednak byłoby odbierać większą ilość danych i później jechać oknem po buforze w poszukiwaniu separatora. Szybsze i większy fun przy programowaniu. Dodatkowo po znalezieniu separatora możesz kopnąć blok pamięci, dokleić bajt zerowy (strncpy() się o to zatroszczy jeśli rozmiar bufora docelowego będzie większy niż długość kopiowanych danych) i zrobić z tego c-stringa. Potem możesz użyć strtok() lub strchr() do splitowania komponentów pakietu, jeśli nie chcesz ręcznie babrać się w jeżdżenie po tablicy.

0

Cenna uwaga, nie pomyslalem ze znak '\n' moze wystapic w danych niekoniecznie jako separator. Podumam jeszcze, teraz podaliscie mi patenty wiec nie ma problemu ;)

0

Najprostszy sposób "Tour de Tablica" oknem:

#include <stdio.h>
#include <stdint.h>
#include <string.h>


int  main(void)
{
        const char      *packet = "Foo bar baz\r\nFrob";
        char            *pos, buf[64];


        pos = (char*)packet;
        while ( *(uint16_t*)pos != 2573 )
                pos++;

        memcpy(buf, packet, pos - packet);
        buf[pos - packet] = 0;
        printf("%s\n", buf);
        return 0;
}

Brakuje tylko pilnowania zakresów i obsługi ''\r\n" rozpindrzonego między recv(), ale to już Pryszcz(tm).

0

Wyzsza szkola jazdy, nie czaje tego kodu ;/. Moze zaraz cos doczytam na necie.

Jakos nie moge ogarnac, jezeli bede odbieral sposobem Kumashiro(ktorego nadal nie rozumiem) pakiety beda poucinane. np:
odebralem "Foo bar baz\r\nFrob"
jako pakiet wychodzi "Foo bar baz\r\n" a dalsza czesc jest ignorowana
wywoluje recv i dochodzi do mnie reszta pakietu "Frob*" tyle tylko ze pierwszej czesci "Frob" tego pakietu juz nie ma.

Albo zle to rozumiem, albo moj program jest do gruntownej przebudowy ;/

0

Eee, ten kod tylko strasznie wygląda (dużo gwiazdek), ale w rzeczywistości jest banalnie prosty. Obkomentuję go trochę i dodam przykładową detekcję endianess (Uwaga! Pod Windows ta detekcja może nie działać... nie wiem jak tam się takie rzeczy robi).

#include <endian.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

/* Tu sobie wykrywamy na czym biegamy i odpowiednio
    definiujemy makro CRLF_AS_16BIT_INT */
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define CRLF_AS_16BIT_INT    2573
#else
#define CRLF_AS_16BIT_INT    3338
#endif


int  main(void)
{
        char            *pos, packet[17], buf[64];

	/* Żeby nie operować na cstringu (bo z recv() dostaniesz bajty)
	    symulujemy tablicę bajtów ze stringa. Na końcu nie będzie
	    bajtu zerowego. W tablicy wskazywanej przez `packet' będą
	    dane niby odebrane z sieci. Celem jest wyciągnięcie bajtów
	    przed '\r\n' */
        memcpy(packet, "Foo bar baz\r\nFrob", 17);

	/* `pos' wskazuje teraz na pierwszy bajt tablicy `packet'. Ponieważ
	    będziemy zmieniać offset, nie możemy użyć `packet' do iteracji */
        pos = packet;

	/* Clou algorytmu. "(uint16_t*)pos" to ściema dla kompilatora.
	    Wciskamy mu ciemnotę, że dwa kolejne bajty (poczynając od
	    wskazywanego przez `pos') w pamięci to tak naprawdę jest wskaźnik
	    na 16bitowy integer bez znaku. Na architekturze Little Endian
	    (np. Intel) bajty '\r\n' (0x0D i 0x0A) dają 0x0D0A, co jako 16bitowy
	    int bez znaku daje wartość 2573 (decymalnie), której będziemy szukać.
	    *() to zwykła dereferencja naszego ściemnianego wskaźnika na int.
	    Ta "sztuczka" zadziała tylko platformach, na których char ma 8 bitów.
	    W warunku pętli powinien jeszcze być test zapobiegający wyjechaniu
	    poza tablicę. Z pętli musimy wyskoczyć kiedy `pos' osiągnie wartość
	    o 2 mniejszą od wielkości przeszukiwanej tablicy (dwa bajty: '\r\n').
	    Z tym już sobie poradzisz */
        while ( *(uint16_t*)pos != CRLF_AS_16BIT_INT )
		/* Jeśli dwa bajty wskazywane przez `pos' nie mają szukanej wartości
		    przeskakujemy o jeden bajt dalej i powtarzamy zabawę. Zwiększamy
		    wartość !wskaźnika! (arytmetyka na wskaźnikach to fajna rzecz) */
                pos++;

	/* Pominąłem sprawdzenie czy '\r\n' zostało znalezione i zakładamy tutaj,
	    że znaleźliśmy. Kopiujemy zatem do bufora docelowego dane z tablicy
	    `packet', zaczynając od jej początku. Kopiujemy tyle bajtów, ile przelecieliśmy
	    (ponownie arytmetyka na wskaźnikach) */
        memcpy(buf, packet, pos - packet);

	/* Doklejamy bajt zerowy, robiąc ze skopiowanych danych pełnoprawnego
	    c-stringa. Pamiętaj, że `pos - packet - 1' nie może być większe niż rozmiar
	    docelowego bufora, gdyż zaczniesz bazgrać po sąsiednich danych w pamięci.
	    Najlepiej bufor alokować dynamicznie, o wielkości odpowiedniej do rozmiaru
	    kopiowanych danych (pos - packet) + 1 bajt na terminator (bajt zerowy) */
        buf[pos - packet] = 0;

	/* Do celów demonstracyjnych printujemy dane. Będą one pozbawione '\r\n',
	    więc w formacie dodajemy newline */
        printf("%s\n", buf);
        return 0;
}

Nic nadzwyczajnego, jak widać. Trzeba jeszcze obsłużyć podzielony separator, gdyż z jednego recv() możesz dostać "<pakiet>\r", a z drugiego "\n<inny pakiet="pakiet">". Powyższy "Tour de Tablica" sprawdza po dwa bajty, więc taki przypadek nie zostanie wykryty. Ja to robię tak, że w buforze dla recv() zawsze po przeskanowaniu kopiuję ostatni bajt na początek jeśli nie ma już miejsca w buforze (jeśli jest, to używam go do doczytania reszty) i do recv() przekazuję wskaźnik na drugi bajt (i ilość danych do odebrania ustawiam na wielkość bufora - 1 of course). To najbanalniejszy sposób.

0
arasso12 napisał(a)

Jakos nie moge ogarnac, jezeli bede odbieral sposobem Kumashiro(ktorego nadal nie rozumiem) pakiety beda poucinane. np:
odebralem "Foo bar baz\r\nFrob"
jako pakiet wychodzi "Foo bar baz\r\n" a dalsza czesc jest ignorowana
wywoluje recv i dochodzi do mnie reszta pakietu "Frob*" tyle tylko ze pierwszej czesci "Frob" tego pakietu juz nie ma.

Bo musisz prawidłowo to obsługiwać. Niestety, ze strumieniami już tak jest.
Po wyciągnięciu danych powyższym sposobem dostaniesz "Foo bar baz" (bez '\r\n'!). Teraz od Ciebie zależy co z tym zrobisz. Możesz resztę kopnąć na początek bufora dla recv() i przekazać mu wskaźnik przesunięty o długość tych pozostałości (pamiętaj o zmniejszeniu wartości ilości danych do pobrania), możesz pozostałości kopnąć do tablicy pod kolejny pakiet i bufor zaorać podając go do recv() (ale patrz moja uwaga co do dzielonego separatora), albo zrobić coś jeszcze innego.

0

Dzieki za wyczerpujaca odpowiedz, teraz juz musze dac sobie rade :). Pozdro

1 użytkowników online, w tym zalogowanych: 0, gości: 1