// bezpośrednie połączenia między klientami (dcc) w ekg aby umożliwić przesyłanie plików i rozmowy głosowe należy wykonać następujące komendy: set dcc 1 set dcc_ip reconnect jeśli jesteśmy za maskaradą lub chcemy przesyłać pliki do ludzi z tej samej sieci lokalnej, podajemy adres komputera widoczny od strony sieci. w przeciwnym wypadku podajemy adres zewnętrzny. jeśli mamy tylko jeden interfejs sieciowy, problemu nie ma, bo adres jest tylko jeden. po ponownym połączeniu możemy korzystać z dobrodziejstw bezpośrednich połączeń. jeśli ktoś spróbuje coś do nas wysłać, pojawi się: ->- ktoś/123 przesyła plik zdjecie.jpg o rozmiarze 129117 ->- Wpisz dcc get #1 by go odebrać, lub dcc close #1 by anulować wystarczy zastosować się do podanych wskazówek. jeśli chcemy wysłać komuś plik, wpisujemy: dcc send szczegóły dotyczące rozmów głosowych znajdują się w pliku ,,voip.txt''. należy pamiętać, że do przesyłania plików obie strony muszą mieć dopisane siebie do list kontaktów. jeśli ustawimy niewłaściwy adres, oryginalne klienty Gadu-Gadu będą odrzucać połączenia. dzieje się tak, gdy adres z którego przychodzi połączenie jest inny niż ten, który został przesłany przez serwer. ma to wpływ głównie na sytuacje typu: ,-----------. | Gadu-Gadu | workstacja `-----+-----' | | | | 10.0.0.5 `------+---+------+--------' | (sieć lokalna) | 10.0.0.1 ,----+----. | ekg | router z NAT `----+----' | 1.2.3.4 | (internet) jeśli ekg na routerze będzie miało ustawiony adres 1.2.3.4, taki adres zobaczy też serwer. ten sam adres dostanie klient Gadu-Gadu w sieci lokalnej. tyle że kiedy ekg będzie chciało wysłać plik do Gadu-Gadu, połączy się z adresu 10.0.0.1, bo taki ma przypisany do sieci lokalnej. w takim wypadku Gadu-Gadu odrzuci połączenie ze względu na niezgodność adresów. dlatego można albo ustawić adres sieci lokalnej, albo użyć do wysyłania: dcc send --reverse które wyśle do Gadu-Gadu żądanie połączenia za pośrednictwem serwera Gadu-Gadu i windowsowy klient sam się z nami połączy. ekg nie jest aż tak restrykcyjne, ale może się to zmienić. najprawdopodobniej parametry komendy dcc również ulegną zmianom w przyszłości. póki co, kod jest w fazie rozwoju. // api kodu dcc to jest początkowy opis początkowych stadiów kodu. wszystko może się zmienić i najprawdopodobniej spora część ulegnie zmianie. gadu-gadu, w przeciwieństwie do irc, umożliwia połączenia w obie strony, bez względu na to, który klient nadaje, a który odbiera. do tego, jeśli obie strony wychodzą z tego samego adresu IP, serwer informuje ich o ich adresach wewnętrznych z tego samego LANu. mamy kilka możliwych sytuacji: a) mam publiczny lub niepubliczny adres IP i chcę wysłać plik do kogoś z publicznym adresem -- łączę się z jego klientem, przedstawiam się, mówię czego chcę i jeśli to zaakceptuje, zaczynam wysyłać plik. bardzo to przypomina zwykłe połączenia dcc klientów irc. b) mam publiczny adres IP i wysyłam plik do kogoś za maskaradą -- wysyłam do niego odpowiedni pakiet ctcp (client-to-client protocol). jest to pakiet klasy GG_CLASS_CTCP (0x10) o treści składającej się z jednego znaku o kodzie 0x02. druga strona, odebrawszy taki pakiet łączy się z nami, mówi, że proszono ją o połączenie i czeka na dalsze instrukcje. wtedy wysyłamy informację, że owszem, chcemy wysłać plik, mówimy jaki i jeśli druga strona to zaakceptuje, nadajemy. c) mam niepubliczny adres IP, tak samo jak i druga strona -- tutaj nawiązanie połączenia jest możliwe tylko i wyłącznie, gdy oba klienty znajdują się w tej samej sieci (tj. oba łączą się z serwerem GG z tego samego adresu zewnętrznego) i wygląda to wtedy identycznie jak w punkcie a). to, czy możemy się z kimś połączyć widać po porcie, jaki dostajemy w pakietach gg_notify_reply. jeśli jest mniejszy niż 10, połączenie nie jest możliwe, a wtedy wysyłamy pakiet ctcp za pomocą funkcji gg_dcc_request(). każde połączenie związanie z dcc opisywane jest przez strukturę gg_dcc. najważniejsze jest GG_SESSION_DCC_SOCKET, które odpowiada za przychodzące połączenia. tworzymy je przez: struct gg_dcc *socket = gg_dcc_socket_create(uin, port); if (!socket) wykrzacz_się("nie mogę otworzyć socketu"); dodaj_do_listy_przeglądanych_deskryptorów(socket); port może wynosić 0, a wtedy libgadu samo weźmie pierwszy lepszy z brzegu. w razie powodzenia zwraca zaalokowaną strukturę gg_dcc, której najbardziej interesującym polem jest gg_dcc->port zawierające numer przyznanego portu. jeśli funkcja zwróci NULL, patrzymy na errno. EINVAL to niewłaściwie parametry, ENOMEM brak pamięci, a reszta możliwych błędów to te związane z socketami, typu EADDRINUSE gdy nie może wolnego portu znaleźć. teraz wypadałoby ustawić zmienną ,,gg_dcc_port'' i połączyć się z serwerem GG, żeby ogłosić swoje namiary. ogłaszany adres IP będzie brany z połączenia z serwerem. gg_dcc_port = socket->port; połącz_się_z_serwerem(); w każdym razie, gdy pojawi się coś na deskryptorze, wywołujemy: struct gg_event *event = gg_dcc_watch_fd(socket); if (!event) { usuń_z_listy_przeglądanych_deskryptorów(socket); gg_dcc_free(socket); wykrzacz_się("poważny błąd"): } błąd jest zwracany tylko w naprawdę krytycznych sytuacjach, gdy brakuje pamięci, lub nie powiodła się operacja na socketach, która nie miała się nie powieść (i przy okazji dalsza zabawa jest kompletnie bezcelowa). jeśli błędu nie będzie, dostajemy informacje o zdarzeniu. w przypadku GG_SESSION_DCC_SOCKET mogą to być: 1) GG_EVENT_NONE -- nic ciekawego się nie wydarzyło. 2) GG_EVENT_DCC_ERROR -- wystąpił błąd, którego kod znajduje się w event->event.dcc_error. w przypadku tego typu sesji możliwy jest tylko GG_ERROR_DCC_HANDSHAKE, który mówi, że nie udało się nawiązać połączenia z klientem. 3) GG_EVENT_DCC_NEW -- nowe połączenie od klienta. w polu event->event.dcc_new jest struktura gg_dcc typu GG_SESSION_DCC, którą dodajemy do listy przeglądanych deskryptorów. w każdym z tych wypadków należy po sprawdzeniu zdarzenia wywołać funkcję: gg_event_free(socket->event); by zwolnić pamięć po zdarzeniu. gdy nadejdzie połączenie i dopiszemy je do listy przeglądanych deskryptorów, musimy zwracać uwagę na następujące zdarzenia: 1) GG_EVENT_NONE -- nic się nie zdarzyło. 2) GG_EVENT_DCC_CLIENT_ACCEPT -- klient się przedstawił i czeka na autoryzację połączenia. sprawdzamy gg_dcc->uin czy jest naszym numerem i czy gg_dcc->peer_uin jest na naszej liście kontaktów i czy chcemy z nim nawiązywać połączenie. jeśli nie, to po prostu usuwamy połączenie: if (!akceptujemy_połączenie(klient->uin, klient->peer_uin)) { usuń_z_listy_przeglądanych_deskryptorów(client); gg_dcc_free(klient); } 3) GG_EVENT_DCC_CALLBACK -- poprosiliśmy klienta, żeby się z nami połączył za pomocą gg_dcc_request() i on teraz pyta się, czego chcemy. zaraz po tym zdarzeniu należy wywołać funkcję: gg_dcc_set_type(klient, rodzaj_połączenia); gdzie rodzaj to GG_SESSION_DCC_SEND albo GG_SESSION_DCC_VOICE. jeśli wysyłamy plik, można od razu wywołać gg_dcc_fill_file_info(), ale nie jest to wymagane. kiedy przyjdzie pora, libgadu sama nas o to poprosi. 4) GG_EVENT_DCC_NEED_FILE_ACK -- klient chce wysłać nam plik. w strukturze gg_dcc->file_info znajdują się wszystkie informacje na temat pliku, jak jego nazwa, rozmiar, atrybuty, data i czas utworzenia itp. jeśli nie chcemy pliku, zamykamy połączenie w podobny sposób jak przy braku autoryzacji. libgadu jeszcze nie potrafi odpowiadać negatywnie na prośby połączeń dcc. jeśli chcemy plik, otwieramy plik do zapisu i numer jego deskryptora zapisujemy do gg_dcc->file_fd. dalej libgadu zajmie się transferem. 5) GG_EVENT_DCC_NEED_FILE_INFO -- wcześniej poprosiliśmy drugą stronę by się z nami połączyła, bo jest za maskaradą, a my chcemy wysłać plik. w tym wypadku możemy albo sami wypełnić strukturę gg_dcc->file_info, którą biblioteka wyśle drugiej stronie, albo skorzystać z funkcji gg_dcc_fill_file_info(). if (gg_dcc_fill_file_info(klient, nazwa_pliku)) { wykrzacz_się("nie mogę otworzyć pliku"); usuń_z_listy_przeglądanych_deskryptorów(klient); gg_dcc_free(klient); } 6) GG_EVENT_DCC_DONE -- zakończono transfer, można już nie patrzeć na deskryptor i zwolnić pamięć po połączeniu. 7) GG_EVENT_DCC_ERROR -- błąd. możliwy kod błędu to GG_ERROR_DCC_HANDSHAKE gdy nie powiodło się ustanowienie połączenia z klientem, GG_ERROR_DCC_NET kiedy nie udało się wysłać lub odczytać czegoś z socketa, GG_ERROR_DCC_FILE gdy nie można było odczytać albo zapisać do pliku, GG_ERROR_DCC_EOF gdy plik lub połączenie zbyt wcześnie się skończy, GG_ERROR_DCC_REFUSED gdy użytkownik po drugiej stronie odmówił połączenia. tutaj również należy pamiętać o wywoływaniu gg_event_free(). jeśli chcemy sami wysłać plik, sprawdzamy najpierw, czy druga strona może przyjąć połączenie, patrząc na jej port. jeśli powyżej 10, możemy śmiało wywołać funkcję: struct gg_dcc *klient = gg_dcc_send_file(adres_ip, port, nasz_uin, jego_uin); if (!klient) wykrzacz_się("nie można ustanowić połączenia"); zaraz potem możemy wywołać funkcję gg_dcc_fill_file_info() by uzupełnić informację o pliku... gg_dcc_fill_file_info(klient, nazwa_pliku); ...ale jeśli tego nie zrobimy teraz, biblioteka poprosi nas o to w odpowiedniej za pomocą zdarzenia GG_EVENT_DCC_NEED_FILE_INFO. jeśli port jest podejrzanie niski, znaczy że połączenie jest niemożliwe i wtedy wywołujemy funkcję: gg_dcc_request(sesja_gg, jego_uin); gdzie sesja_gg to nasza sesja GG (jakoś musimy wysłać wiadomość), a jego_uin to numer drugiej strony. spowoduje ona, że druga strona spróbuje się z nami połączyć, jeśli ma taką możliwość. gdy otrzymamy wiadomość klasy GG_CLASS_CTCP o treści 0x02 znaczy, że ktoś chce nam coś przesłać i mamy się z nim połączyć. wywołujemy wtedy: struct gg_dcc *klient = gg_dcc_get_file(adres_ip, port, nasz_uin, jego_uin); if (!klient) wykrzacz_się("nie można ustanowić połączenia"); dalej tak samo, jak przy zwykłym odbieraniu pliku. $Id: dcc.txt,v 1.9 2002/09/11 14:50:55 wojtekka Exp $