Niedawno implementowałem na własne potrzeby połączenia sieciowe TCP wykorzystując QTcpServer
i QTcpSocket
.
Nie wchodząc w szczegóły, idea była taka, że program wysyła co jakiś czas pojedynczy komunikat danych (może mieć wielkość od kilku bajtów do 10 megabajtów) do innej instancji poprzez sieć.
Na podstawie www.google.com i własnych prób udało mi się zrealizować odbiór poprzez sygnał readyRead()
w obiekcie klasy QTcpSocket
, podłączyłem taki slot:
void PicNetwork::TcpMsgRecvEvent()
{
QByteArray buffer = TcpSocket_->readAll();
MsgRecvEvent(buffer, TcpSocket_->peerAddress(), TcpSocket_->peerPort());
}
void PicNetwork::MsgRecvEvent(QByteArray &Msg, QHostAddress MsgAddr, quint16 MsgPort)
{
// Tu jest reakcja związana z odbiorem komunikatu, jako parametry jest komunikat oraz adres z portem drugiej strony komunikacji
}
Problem jest taki, że jak komunikat jest dłuższy niż kilka kilobajtów, to po drugiej stronie przychodzi więcej komunikatów, czyli jest więcej wywołań slotu TcpMsgRecvEvent
, w każdym inna część komunikatu. Zdarzało się także, że dwa komunikaty wysyłane jeden za drugim zostały odebrane jako jeden komunikat. Ja poradziłem sobie z tym problemem w ten sposób, że w każdym komunikacie na początku są dopisane 4 bajty, które przenoszą długość całego komunikatu w bajtach, druga strona ma globalny bufor i globalnie zapamiętaną długość, działa według mniej więcej takiego algorytmu:
int DlugoscCalkowita = 0;
QByteArray Bufor;
void SlotOdbierajacyKomunikat(QByteArray Komunikat)
{
if (DlugoscCalkowita == 0)
{
DlugoscCalkowita = LiczbaZPierwszychCzterechBajtow(Komunikat);
}
Bufor.append(Komunikat);
if (Bufor.length() >= DlugoscCalkowita)
{
WykonanieAkcjiZwiazanejZKomunikatem(Bufor.mid(0, DlugoscCalkowita));
Bufor.remove(0, DlugoscCalkowita);
if (Bufor.length() >= DlugoscCalkowita)
{
DlugoscCalkowita = LiczbaZPierwszychCzterechBajtow(Bufor);
}
else
{
DlugoscCalkowita = 0;
}
}
}
Wyżej opisany sposób działa bardzo dobrze. Kiedyś miałem podobny problem w .NET i rozwiązałem go w taki sam sposób. Jak widać, w samym komunikacie mam informacje o rozmiarze, który pozwala wyczuć, kiedy komunikat został odebrany w całości. Innym sposobem jest przyjęcie pewnego ciągu bajtów oznaczającego koniec komunikatu i sprawdzenie, czy ten ciąg wystąpił.
W jaki sposób można wyczuć przyjęcie całego komunikatu, ale bez wprowadzania elementów kontrolnych do samego komunikatu, takimi jak wielkość komunikatu, element oznaczający koniec komunikatu lub przyjęcie stałej wielkości komunikatów?
Chodzi o to, żeby wykorzystać mechanizmy zapewniane przez sieć, zamiast traktować komunikaty przychodzące jako nieskończony strumień uzupełniany porcjami i analizować treść komunikatu w poszukiwaniu początku lub końca bądź poprzez odczytywanie z bufora porcji o wielkości ustalonej na podstawie samej treści komunikatu.
Gdybym nie wprowadził wielkości komunikatu, to w kodzie wyżej napisanym bym nie wiedział, kiedy odebrano część komunikatu (i która część), kiedy cały, a kiedy dwa jednocześnie.