Hey. Mam pytanie co do przesyłanych danych za pomocą QTcpSocket'a. Mianowicie czy jest jakaś możliwość w QT podziału strumienia danych zależnie od typu wiadomości? Np mam klasę Wiadomość, klasę jeszcze jakąś inną (serializuję i przesyłam), komunikaty PingPong, i chciałbym móc jakoś rozróżnić, który rodzaj danych przesyłam, żeby np funkcja odpowiedzialna za Pingowanie nie otrzymała danych przeznaczonych dla klasy Wiadomość. A może trzeba zbudować jakąś uniwersalną klasę?
A skąd Qt ma wiedzieć jaki to jest typ wiadomości? Z socketa czyta bufor danych i to Ty musisz go parsować.
No właśnie, czyli format wiadomości zawsze musi być ten sam (jakas jedna klasa)? Mógłbyś rozwinąć myśl, jak to najlepiej zrobić?
Musisz z przeczytanego bufora umieć wyciągnąć typ przekazanej wiadomości oraz jej wielkość (a także sprawdzić czy cała wiadomość jest już w buforze).
Z pamięci mogę przytoczyć trzy rozwiązania, zapewne można to rozwiązać także w inny sposób:
- każda wiadomość zaczyna się i kończy w szczególny sposób (np. tag w xml, {})
- każda wiadomość ma stałą wielkość
- każda wiadomość zaczyna się nagłówkiem informującym o jej wielkości
po wczytaniu gdzieś wewnątrz wiadomości musisz zawrzeć jej typ (id w nagłówku/tag w xml) i na jego podstawie odpalić odpowiednią funkcję obsługującą.
Chyba wybiorę metodę 3.
1 to chyba to samo co 3 tylko bardziej przekombinowane
2 odpada, bo za bardzo może mi obciążyć łącze jezeli caly czas idą ping-pongi między klientami a serwerem (małe porcje danych) + inne wiadomości (większe)
po wczytaniu gdzieś wewnątrz wiadomości musisz zawrzeć jej typ (id w nagłówku/tag w xml) i na jego podstawie odpalić odpowiednią funkcję obsługującą.
Mam wiele klas nasłuchujących (PingPong, Message, Console) więc w każdej chyba będę musiał załączyć jakieś funkcje weryfikujące czy te dane były przeznaczone dla tej klasy?
Nie, najlepiej miej osobną klasę/funkcję routującą wiadomości do klas, które dostaną to co chciały i to obsłużą - inaczej będziesz powtarzał funkcjonalność routera w każdej z nich.
Hmm to byłoby trudne. Mam np. dla każdego usera osobny obiekt który w innym wątku go cały czas pinguje. Musiałbym przelecieć po całej tablicy klientów i wysyłać odpowiednim komunikat. A co jeżeli np. te operacje routingu wiadomości będą trwały tyle że zakłamią mi ping (ilość milisekund) bo np. transport po internetach będzie trwał 10ms a kolejne 10ms routing i wszystkie poboczne operacje = 20ms. Ta klasa routująca musiałaby być bardzo sprytna i mieć dostęp do wszystkich klas które tych danych porzebują
O ile nie robisz czegoś bardzo źle (albo na kalkulatorze z zeszłego tysiąclecia), to wszystko powinno wykonać się w ułamku milisekundy. Ponadto, jak Ty to sobie inaczej wyobrażasz? Wiele klas współdzielących ten sam socket i wyjmujących wszystko jak popadnie?
Nie neguję tej koncepcji, ba, lepszej chyba nie ma, zastanawia mnie tylko jak najefektywniej to zrobić (żeby nie powstał chaos w kodzie). Pewnie zrobię jakąś klasę nadrzędną dla wiadomości i klasy pochodne dla jej typów + klasa routująca, sprwadzająca na podstawie wielkości wiadomości, która to jest klasa pochodna. Tylko muszę się zastanowić jak dobrze przesłać już zdeserializowaną wiadomość do odpowiednich klas (Ping, Wiadomość itd.). Sygnał się w tym sprawdzi?
Jeśli wszystko jest w tym samym wątku to po prostu metoda wirtualna wystarczy (ewentualnie mapa wiadomość-handler, gdzie handler to pewnie std::function<bool(wiadomość)>
), jeśli wątki różne to sygnał&slot lub bezpośrednio QMetaObject::invokeMethod
.
Czytam o invokeMethod ale to jest chyba to samo co
obiekt->akcja();
// == ?
QMetaObject::invokeMethod(obiekt, "akcja");
Jeśli obiekt żyje w innym wątku - nie. Wtedy wewnętrzny mechanizm Qt zagwarantuje bezpieczną komunikację międzywątkową i wywołanie slotu w jego wątku. (to jest używane przez sygnały i sloty wewnętrznie) Możesz oczywiście przesyłać argumenty do slotów, tylko muszą one być znane mechanizmowi Qt (czyli nie obejdzie się bez qRegisterMetaType<>
na początku progamu) oraz opakowane w makro Q_ARG
edit: znowu jeśli obiekt żyje w tym samym wątku, to invokeMethod jest kilka razy wolniejsze (na współczesnych komputerach to są nanosekundy, może kilka mikrosekund, ale zawsze) oraz pozbawia Cię statycznej analizy kompilatora w kontekście użycia tego slotu
Tzn, na pewno będzie trzeba użyć invoke bo obiekty Pingujące są w osobnych wątkach. Nie wiem jeszcze czy opłaca się tworzyć osobną klasę routującą czy załatwić to w metodzie w klasie Servera:
//sygnal wyslany od connect(server, SIGNAL(readReady()), server, SLOT(deliver())))
void Server::deliver() {
//tutaj sprawdzenie typu wiadomości
// cos w stylu:
if(do pingow)
for(ping : pingi)
ping.obierz(wiadomosc) //ping odpowie
else if(do usera) //np jakas wiadomosc tekstowa
//pobranie adresata i dostarczyciela i wyslanie do konkretnego klienta
server.wyslij(client, wiadomosc)
}
I czy do wysyłania danych też trzeba jeden strumień czy to jest pakowane w jakąś kolejkę i mogę z różnych miejsc wysyłać dane?
W dokumentacji nie ma nigdzie informacji, że send jest thread-safe, więc załóż, że nie jest.
Mam jeszcze jeden mały problem z którym się męczę. Chcę przesłać wiadomość Ping
class Ping
{
private:
QString message;
public:
Ping(QString msg);
}
Fajnie sobie obiekt zserializowałem (szkoda że tu nie ma mechanizmu jak w Javie, że nie trzeba każdego pola osobno przepisywać do streamu) i jest problem, bo niby wysyłam 4 bajty (czyli tyle ile zajmuje ten obiekt) a odbiera mi 12. I tu już jest zonk, bo ja sprawdzam co wysłałem za pomocą porównania ilości bajtów z rozmiarem klasy (tyle że nie wiem czy to najlepszy pomysł, bo ciekawe co będzie jeżeli akurat w buforze znajdą się BUFOR -> [Ping(4), CosInnego(24)] = 28bajtów (przykład) i wtedy nie idzie sprawdzić co zostało przesłane )
//odbieranie
QDataStream in(sender);
in.setVersion(QDataStream::Qt_5_0);
qDebug() << sender->bytesAvailable(); //12
qDebug() << sizeof(PingData); //4
if(sender->bytesAvailable() == sizeof(PingData)) {
PingData pingMsg("nvm");
in >> pingMsg;
Message::console(pingMsg.getMessage());
}
//Wysyłanie
QByteArray bytes;
QDataStream out(&bytes, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_0);
PingData pingMsg("Ping");
out << pingMsg;
qDebug() << sizeof(bytes); //4
socket->write(bytes);
człowieku sizeof zwraca ci stałą wielkość, a zawartość QString jest gdzis inndzije na stercie i ma zmienny rozmiar.
Poczytaj ten wątek: http://stackoverflow.com/a/19682690/1387438