wxWidgets, socket w wątku i odbieranie odpowiedzi serwera

0

Witam,

Dokladnie chodzi o "gadanie" z serwerem SMTP. W tym celu używam klasy wxSocketClient, utworzonej w osobnym wątku.

Jednak mam problem z interpretacją odpowiedzi serwera. Chodzi o to, zeby w zaleznosci od tego co serwer odpowie (albo jak rozlaczy), wątek "nie pchał" danych na siłę do zamkniętego połączenia.

Jak zaimplementować natychmiastowe odebranie odpowiedzi poza wątkiem gui (czyl w tym samym wątku, który utworzył socketa).

Próbowałem po każdym Write dać Read. Niestety to nie tak działa jak chcę - wygląda na to, że komunikaty są w jakiś dziwny sposób buforowane (nie mogę dojść w jaki) tak, że to co pobierze Write jest nieaktualne, czasami z kolei nie pobierze nic... czasami spowoduje wywalenie się programu...

Jedyny sposób na prawidłowe odczytanie komunikatu, to przekazanie ich poprzez ewent do wątku GUI (standardowy even socketa) - jednak to akurat mi nic nie daje, bo zanim wątek główny zinterpretuje te komunikaty i zatrzyma wątek socketa, wątek operujący na sockecie "wepchnie" mnóstwo niepotrzebnych danych, a jezeli serwer zerwał połączenie lub przestał akceptować dane, to spowoduje to wywalenie programu.

Jaki jest na to sposób? Mam nadzieje ze wystarczająco jasno wytlumaczylem o co mi chodzi. Pewnie biore sie za to od d... strony, ale innego pomyslu nie mialem

0

jednak to akurat mi nic nie daje, bo zanim wątek główny zinterpretuje te komunikaty i zatrzyma wątek socketa, wątek operujący na sockecie "wepchnie

watek "wpychajacy" dane powinien zajmowac sie sprawdzaniem odpowiedzi od serwera. skoro moze robic WRITE, to rownie dobrze moze robic READ. a jesli masz problem z tym, ze READ jest blokujacy --- to otworz socketa w trybie nieblokujacym

0

Próbowałem po każdym Write dać Read. Niestety to nie tak działa jak chcę - wygląda na to, że komunikaty są w jakiś dziwny sposób buforowane (nie mogę dojść w jaki) tak, że to co pobierze Write jest nieaktualne, czasami z kolei nie pobierze nic... czasami spowoduje wywalenie się programu...

A sprawdzasz chociaż przy odczycie/zapisie danych co zwraca LastCount? Taka to już jest natura socketów, że nie muszą naraz odczytać/zapisać żądaną liczbą bajtów. Nie znam za bardzo protokołu SMTP, ale jeśli jest on podobny do FTP, to powinieneś odbierać na bieżąco odpowiedzi serwera, czyli: komenda -> odpowiedź. Nawet w specyfikacji masz:

<i>Every command must generate at least one reply, although there may be more than one.</i>

Przy odbiorze odpowiedzi zawsze sprawdzaj, czy odebrałeś całą odpowiedź, która w przypadku jednolinijkowych wygląda tak:

xxx<span style="color: orange"><SP></span>treść<span style="color: orange"><CRLF></span>

a wielolinijkowych tak:

xxx-linia 1<span style="color: orange"><CRLF></span>
linia 2<span style="color: orange"><CRLF></span>
...<span style="color: orange"><CRLF></span>
xxx<span style="color: orange"><SP></span>linia N<span style="color: orange"><CRLF></span>
0

Przy odbiorze odpowiedzi zawsze sprawdzaj, czy odebrałeś całą odpowiedź

Ale jak?

Tworząc domyślny socket (wxSocketClient), czyli z pustym konstruktorem i robiąc Read po każdym Write nie osiągnę celu. Jeżeli dam flagę blocking, to i tak nic nie zmieni, bo tu chodzi o blokowanie GUI - jezeli jest to w innym wątku to nie ma znaczenia.

Podam przykład. Łączenie z poczta.o2.pl. Od razu po połączeniu serwer odpowiada mniej więcej tak:

220 poczta.o2.pl ESMTP Wita

W odpowiedzi na komendę: EHLO [127.0.0.1] odpowiedz jest:

EHLO [127.0.0.1]
250-poczta.o2.pl
250-PIPELINING
250-SIZE 150000000
250-VRFY
250-ETRN
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN

I drugie polecenie AUTH LOGIN, odpowiedź

334 VXNlcm5hbWU6

Wydawałoby się, że jeżeli zrobię tak:

sock->Write("EHLO [127.0.0.1]\r\n");
sock->Write("AUTH LOGIN\r\n");
sock->Read(buf,150);

To bufor powinien zawierac odpowiedz na auth login. A niestety tak nie jest. Bufor zawiera zawsze poprzednie dane i za kazdym razem inne. Z kolei dając Read(50) i potem znów Read(50) operacja odczytu nigdy sie nie zakończy.

Cos takiego:

while(sock->IsData())
{
  //read
}

Tez nic nie pomaga, chociaz chyba wyglada najbardziej sensownie (buforowany odczyt danych), ale tutaj też dane zawsze odbierane z opoznieniem albo część jest gdzies tracona. Jak więc odebrać tylko ostanią odpowiedź (na ostatnie polecenie) ?

O ile pamiętam na winsocku nie bylo takich problemów. Zawsze send i recv i mam ostatnią odpowiedź.
A sockety w wxWidgets chyba jakos inaczej dzialaja, albo ja czegos nie rozumiem? Kombinowanie z WaitForRead tez nic nie daje. Więcej pomysłow nie mam :|

Z dokumentacji wynika, ze dane są jakos buforowane i na przyklad Read niby usuwa dane z kolejki, z kolej Peek nie usuwa. Ale ja nie zauwazylem zadnej roznicy - zawsze Read i Peek działają u mnie dokładnie tak samo i zawsze mam poprzednie dane zamiast aktualnych.

0

To bufor powinien zawierac odpowiedz na auth login.

Jakim cudem? Jeżeli wysłałeś dwie komendy i nie odebrałeś żadnej odpowiedzi to dlaczego w buforze miałaby się znajdować tylko jedna, ostatnia odpowiedź serwera?

Jak więc odebrać tylko ostanią odpowiedź (na ostatnie polecenie) ?

Już pisałem, wysyłasz polecenie i odbierasz całą odpowiedź. Dopóki nie odbierzesz kompletnej odpowiedzi z serwera, nie wysyłasz żadnych innych poleceń - proste. Oczywiście musisz uwzględnić to, że serwer może wysłać więcej niż jedną odpowiedź, więc lepiej po każdym poleceniu odczytać wszystko co serwer wysłał (coby nie było nieoczekiwanych obsuw komenda - odpowiedź). Inna sprawa to to, że serwer po coś te odpowiedzi wysyła, więc warto by było sprawdzać te numerki na początku każdej odpowiedzi ;)

O ile pamiętam na winsocku nie bylo takich problemów. Zawsze send i recv i mam ostatnią odpowiedź.

Bzdura...

Kombinowanie z WaitForRead tez nic nie daje. Więcej pomysłow nie mam :|

Napisz własną klasę do obsługi socket'ów :P W sumie nie tak dużo roboty.

0

Już pisałem, wysyłasz polecenie i odbierasz całą odpowiedź. Dopóki nie odbierzesz kompletnej odpowiedzi z serwera, nie wysyłasz żadnych innych poleceń - proste.

No oczywiście, proste. Jest tylko jedno pytanie: jak odebrać całą odpowiedź z serwera?

Narzuca mi sie to rozwiazanie:

while(sock->IsData())
{
  //read
}

Jak rozumiem jezeli socket jest blokujący, to dopóki są jakieś dane do odebrania, wątek nie ruszy dalej tylko odczyta wszystko.

Dobrze myślę? Bo albo cos robie nie tak, albo to nie tak działa.

0

No oczywiście, proste.

Heh bo to jest proste. Choć być może nie na tych socketach ;) Z ciekawości sprawdziłem u siebie jak to jest z tymi socketami i faktycznie niezły kosmos... :/ U mnie na przykład pomimo, że WaitForRead zwraca true, Read/Peek czyta mi 0 bajtów, co skutkuje Error'em socketa. Socket pracuje w trybie wxSOCKET_NONE, który według dokumentacji powinien zachowywać się jak low level'owe socket'y w trybie blokującym.

0

Hmm szczerze to juz bym dawno wrocil do winsocka. Problem w tym, ze to ma dzialac na Windows i na Linuksie, a nie chcialbym pisac oddzielnego kodu dla tych 2 platform...

Moze w takim razie sa jakies inne wieloplatformowe biblioteki socketów, ktore moge wykorzystac w projekcie wxWidgets?

0

Pewnie są, ale z tego co się orientuję to sockety windowsowe niewiele różnią się od tych unixowych - mówię o podstawowym zestawie funkcji (Berkeley Sockets). W windowsie trzeba wywołać odpowiednio WSAStartup/WSACleanup przed i pod koniec używania socketów. Zamiast close musisz użyć closesocket. No i sockety windowsowe nie działają z deskryptorami plików, ale to chyba nie taki duży problem.

0

Tak sobie czytam dokumentacje winsocka i nie za bardzo wiem jak podejsc do tematu. Chodzi o to, jak zrealizowac cos takiego:

while (SaDaneDoOdczytania)
{
recv(bufor...)
}
//---idz dalej

Z tego co czytam na msdn http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx to nie ma mozliwosci na zrobienie czegos takiego.

Musze koniecznie zrobic tak, zeby po kazdym send bylo odczytane wszystko co jest do odczytania, zinterpretowane i potem nastepne send itd... zadna ze zwracanych wartosci nie sygnalizuje, ze jeszcze sa albo juz nie ma wiecej danych do odczytania, wiec nie ma mozliwosci napisac takiej petli. W zalaczonym przykladzie jest odbieranie az do zamkniecia socketa, co mi niewiele daje.

Jak wiec to zrobic?

0

Z tego co czytam na msdn to nie ma mozliwosci na zrobienie czegos takiego

Oczywiście że jest taka możliwość - funkcja select ;)

Coś w tym stylu:

int is_data(SOCKET s, size_t sec_timeout = 0)
{
	timeval tv;
	fd_set readfds;

	tv.tv_sec = sec_timeout;
	tv.tv_usec = 0;

	FD_ZERO(&readfds);
	FD_SET(m_socket,&readfds);
	
	int res = select((int)s + 1,&readfds,NULL,NULL,&tv);
	if(res <= 0) return res; //<--- błąd socketa (<0) lub timeout (0)
	return FD_ISSET(s,&readfds);
} 

Dobrze byłoby, żebyś przy odbieraniu odpowiedzi dał jej trochę czasu na dotarcie do klienta, czyli nie:

send(s,...);
if(is_data(s) > 0)
{
	recv(s,...);
}

tylko np.:

send(s,...);
if(is_data(s,10) > 0)
{
	recv(s,...);
}
0

po to sa w RFC ustalone kody odpowiedzi.. na poczatku kazdej linii

EHLO odpowiada kodami z serii 250
AUTH LOGIN odpowiedzial Ci kodem 334

nie musisz po kazdej komendzie parsowac odpowiedzi.. o ile dobrze pamietam, kazda komenda wyslana ma scisle okreslone KODY odpowiedzi. jeden per linia. kazdy znaczy cos innego..

wysylasz ciag komend, potem czekasz na odpowiedz o konkrentym kodzie/ach: czyli czytasz w kolko blokujaco kolejne linijki az sie trafi 334..

dlatego wlasnie wywala sie odczytywanie do osobnego watku -- bo ono jest/powinno byc blokujace.. niech watek boczny kazda linie przeczytana wrzuca PRZYROSTOWO na biezaco do jakiegos bufora, a watek glowny po wyslaniu AUTHLOGIN co pare sekund sprawdza czy w buforze pojawila sie odpowiedz 334.. jak przez N sekund spodziewane 334 nie przyszlo, mozna wtedy w buforze obejrzec i przeanalizowac co dokladnie nie wyszlo, zresetowac sockety i ew. sprobowac jeszcze raz

0

Ok, jak rozumiem ilosc odczytywancyh znaków z socketa (za jednym zamachem) nie ma za bardzo znaczenia, bo jest to po prostu buforowany odczyt, ale jakiej wielkosci bufor najlepiej użyć do recv?
To jeszcze bardziej przyda mi sie pozniej, gdy bede implementowac POP3, gdzie bede pobierac wieksze ilosci danych z serwera - docelowo ma to byc projekt typu "dysk email".

No i czy pod linuksem metoda is_data bedzie wygladac analogicznie? Bo założenie od początku jest takie, że ma to działać również pod linuksem - dlatego wybrałem wxWidgets i sockety od niego.

0

ale jakiej wielkosci bufor najlepiej użyć do recv?

vector<char>, taki będzie dobry ;-P

No i czy pod linuksem metoda is_data bedzie wygladac analogicznie?

Powinna wyglądać tak samo.

0

vector<char>, taki będzie dobry ;-P

Hmm ale że mam podat vector<char> do recv rzutując na char*? A to zadziała? :> recv przyjmuje char* :>

0

Ech, do vectora doklejasz to co odebrałeś z recv:

char buff[512];
int c;

if((c = recv(s,buff,512,0)) > 0)
{
    vec_of_chars.insert(vec_of_chars.end(),buff,buff + c);
}
0

no bez zartow :) jesli uzywac vectora, to z glowa..
vector<char> buff;

buff.resize(buff.size()+512);  //'doklejenie' 512-bloku na koncu bufora
if(int c = recv(s, &buff.front(), 512, 0))
    if(c >= 0)buff.resize(buff.size - 512 + c);  //'obciecie' vectora do faktycznej ilosci odebranych
    else  /*error*/;

vector jest dynamiczna, ciaglą tablicą elementow. nie ma potrzeby tymczasowego bufora 'na boku'

a skoro nie chcesz czytac bajt/znak-po-bajcie/znaku, to mam nadzieje ze 's' jest w nonblocking, inaczej czytanie na pewno Ci sie bloknie.. no, chyba ze to HTTP i ze serwer zrywa polaczenie po odpowiedzi :P

0
quetzalcoatl napisał(a)

jesli uzywac vectora, to z glowa..

No właśnie... :>

buff.resize(buff.size()+512);  //'doklejenie' 512-bloku na koncu bufora
if(int c = recv(s, &buff.front(), 512, 0)) //<--- i dopisanie na początek :P
0

jesli chodzi Ci o if(int c= to nie jest to blad :) //edit - chociaz przyznam, 0.75roku temu sam bym za to komus łeb zgolił. a tu masz, taki trick w standardzie.. to samo z while i switch jest poprawne. zmienna pamietajaca dokladny wynik testu na czas bloku if/while/switch

0

Ja bym tu nie używał vectora. Wystarczą 2 zmienne i jedna funkjca - wskaźnik na bufor, jego wielkość i realloc.
vector tylko zbędnie buforuje bufor, a nic tu nie upraszcza.
No chyba, że są jeszcze inne względy, to wtedy vector.

@quetzalcoatl: było źle, bo powinno być:

if(int c = recv(s, &buff.front() - 512, 512, 0))

początek jest już po doklejce.

0

Nie, chodzi mi o ten front i doklejanie.

0

Arrrghhh!
nawet nie bede zmieniac.. niech zostanie jako przestroga dla mnie na klepanie bez sprawdzania..
niemniej, wiadomo o co chodzi - dwukrotne kopiowanie czytanego bloku vs jedno

adf88 -- zbednie buforuje..? vector do doslownie = wskaznik+realloc, plus auto-delete przy wyjsciu za scope. tablica char[512] byla zbedna

0
quetzalcoatl napisał(a)

adf88 -- zbednie buforuje..? vector do doslownie = wskaznik+realloc, plus auto-delete przy wyjsciu za scope. tablica char[512] byla zbedna
Tak ? To do czego jest metoda reserve ? :>
Zresztą jak się teraz bardziej przyjrzałem, to vector bardziej pasuje.

0

..od tego samego co {reallocowanie na wyrost po np. 512B, nieruszanie wielkosc, a ruszenie maxwielkosc+=512} :)
przeciez reserve nie tworzy jeszcze jednego bloku na boku, tylko powieksza aktualny na zapas bez ruszania size

0

Nie no na to sam bym wpadl. Chodzi mi o to, ile na raz pobierac z socketa - w waszych przykładach jest 512, z kolei jak wysylam do serwera, to po 1 kilo (1024) i wyglada ok - wiec ile tu najlepiej dac?

A co do doklejania, rownie dobrze mocna chyba doklejac do std::string za pomoca operatora += ? Chociaz tu narzut bedzie pewnie sporo wiekszy.

Albo w ogole nie tworzyc oddzielnego bufora tylko parsowac kody błędów na bieżąco i ewentualnie wysyłać odpowiedzi do wątku gui np. w celu wyswietlenia? Tylko jezeli eventy będą wysyłane zbyt często to też spowoduje zablokowanie aplikacji.

0
othello napisał(a)

Nie no na to sam bym wpadl. Chodzi mi o to, ile na raz pobierac z socketa - w waszych przykładach jest 512, z kolei jak wysylam do serwera, to po 1 kilo (1024) i wyglada ok - wiec ile tu najlepiej dac?

zeby miec pewnosc ze czytasz na biezaco i tylko swoje i nie utkniesz niepotrzebnie czekajac na nieswojkawalekdanych -- pozostaje czytanie po jednym znaku i "powolne" skladanie bufora [defacto, takie bardzo powolne to nie bedzie].. tak jak proponowalem na poczatku.. o Ciebie terminatorem rekordu jest \r\n nie \0. i po kazdej skonczonej linii jesli nie jest to linia interesujaca, czytasz dalej..

A co do doklejania, rownie dobrze mocna chyba doklejac do std::string za pomoca operatora += ? Chociaz tu narzut bedzie pewnie sporo wiekszy.

troche wiekszy - o okolo jedno kopiowanie wiecej. przyczym, uwaga, string'a nie mozna podac do recv bezposrednio tak jak vectora bo ... string nie gwarantuje ciaglosci wewnetrznej tablicy

Albo w ogole nie tworzyc oddzielnego bufora tylko parsowac kody błędów na bieżąco i ewentualnie wysyłać odpowiedzi do wątku gui np. w celu wyswietlenia? Tylko jezeli eventy będą wysyłane zbyt często to też spowoduje zablokowanie aplikacji.

wlasnie takie cos proponowalem na poczatku --- z tym ze, rzeczywiscie eventy za czeste ja zwiesza -- wiec mozna je buforowac i niech sobie GUI co 0.250 .. 1.000 sekundy sprawdza czy nic nowego nie zaszlo

0
quetzalcoatl napisał(a)

przeciez reserve nie tworzy jeszcze jednego bloku na boku, tylko powieksza aktualny na zapas bez ruszania size
Czyli buforuje. W efekcie mamy buforowany bufor ;)

quetzalcoatl napisał(a)

Albo w ogole nie tworzyc oddzielnego bufora tylko parsowac kody błędów na bieżąco i ewentualnie wysyłać odpowiedzi do wątku gui np. w celu wyswietlenia? Tylko jezeli eventy będą wysyłane zbyt często to też spowoduje zablokowanie aplikacji.

wlasnie takie cos proponowalem na poczatku --- z tym ze, rzeczywiscie eventy za czeste ja zwiesza -- wiec mozna je buforowac i niech sobie GUI co 0.250 .. 1.000 sekundy sprawdza czy nic nowego nie zaszlo
Nie lepszy będzie osobny wątek ?

0

1' nie buforuje bufora. JEST buforem. jednym. nie podwojnym. jezu.. droczysz sie czy co?:) to jest dokladnie to samo co realloc.. jesli (re)allocowanie na wstepie 512 bajtow aby wczytac 40 bajtow nazywasz buforowaniem bufora 512bajtowego -- ok, niech Ci bedzie :)

2' caly czas mowimy o osobnych dwoch watkach. GUI oraz ten-ktory-robi-recv. othello wysunal spostrzezenie, ze jesli watek recvujacy bedie analizowac na biezaco i pingowac eventami GUI, to gdy dostanie baaardzooo duzo sygnalow z serwera, za'event'uje GUI na smierc i to ulegnie (czasowemu) zablokowaniu - a tego chce sie uniknac..
wiec wysunalem propozycje, aby watek-ten-ktory-robi-recv nie odpalal eventow co rusz, tylko zeby swoje znaleziska pakowal do "bufora" do ktorego GUI tez ma odstep i ktorego to bedzie sobie ogladac w wygodnych momentach - timer, onpaint, whatever.
gdzie tu nowy watek? ot, timer.

0

thello wysunal spostrzezenie, ze jesli watek recvujacy bedie analizowac na biezaco i pingowac eventami GUI, to gdy dostanie baaardzooo duzo sygnalow z serwera, za'event'uje GUI na smierc i to ulegnie (czasowemu) zablokowaniu

Dokladnie to mialem na mysli. Jak jeszcze kiedys zaczynałem się tym bawić, to zrobiłem wątek z pętlą nieskończoną while, progress co 1 i przy każdym zwiększeniu licznika pętli wysyłałem event do wątku gui - i co się działo? Ano był taki sam efekt, jakby wszystko odbywało się w wątku gui, czyli tak, jakbym wcale nie tworzył drugiego wątku.

A z drugiej strony ciekawe, bo jeżeli zrobić wątek poboczny w Winapi, i z tego wątku bezpośrednio zmieniać tekst Labela w oknie dialogowym winapi w analogicznej pętli, to okazuje się że wszystko będzie będzie chodzić nawet bez żadnej synchronizacji, nie wywali się ani nie zablokuje... a tekst etykiety będzie zmieniał się tak szybko jak idzie pętla... jestem ciekawy jak osiągnąć taki efekt chociażby za pomocą eventów i c++

0

Ok pytanie za 100 punktów... dołączam do headera eventu winsock2.h czyli:

#pragma once
#include <wx/wx.h>
#include <wx/thread.h>
#include <wx/url.h>
#include <wx/file.h>
#include "SMTPEvent.h"
#include "_base64.h"
#include <winsock2.h>



//...........

Wynik próby kompilacji: 264 błędy m.in. w winsock2.h

Error	18	error C2011: 'fd_set' : 'struct' type redefinition	C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\winsock2.h	132	test

Error	21	error C2011: 'hostent' : 'struct' type redefinition	C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\winsock2.h	232	test

....

O co biega??? Czemu nie mogę dołączyć winsock2.h ??

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