Programowanie sieciowe przy u偶yciu gniazdek w辧phi 3 MPLMRFGCOQC4VOMKHU5DAT5YKUDWUHLWUTINXRI


Programowanie sieciowe przy u偶yciu gniazdek w Delphi 3.

Przegl膮daj膮c grup臋 dyskusyjn膮 pl.comp.lang.delphi co jaki艣 czas spotyka si臋 rozpaczliwe pro艣by w stylu „Jak wysy艂a膰 dane przez sie膰?” ew. „Jak obs艂ugiwa膰 sockety?”. W tym kr贸tkim artykuliku chcia艂bym om贸wi膰 podstawy programowania tzw. gniazdek (sockets) w systemie Windows95 za pomoc膮 Delphi 3, s膮dz臋 jednak, 偶e nawet osoby pisz膮ce w C/C++ znajd膮 tu co艣 dla siebie gdy偶 prawie ca艂a zabawa odbywa si臋 na poziomie Win API.

Pierwsz膮 rzecz膮 z jak膮 powinni艣my si臋 zapozna膰 to podstawy (bardzo podstawowe podstawy ;-) komunikacji za pomoc膮 gniazdek. Ka偶de takie gniazdko to identyfikator (uchwyt - handle) m贸wi膮cy systemowi kt贸re po艂膮czenie chcemy wykorzysta膰, ka偶de po艂膮czenie sk艂ada si臋 z: adresu IP nadawcy, portu nadawcy, adresu IP odbiorcy, portu odbiorcy oraz protoko艂u w kt贸rym si臋 b臋d膮 komunikowa膰 (w przypadku Windows95 b臋dzie to TCP lub UDP), nie wnikaj膮c w budow臋 protoko艂u IP powiem 偶e adresy to po prostu 艣cie偶ki prowadz膮ce do jakiego艣 komputera a porty to „wej艣cia” do systemu - maj膮 one numery od 0 do 65535 i mog膮 by膰 albo zamkni臋te albo otwarte. Kiedy jaki艣 port jest zamkni臋ty to pr贸ba po艂膮czenia z nim ko艅czy si臋 niepowodzeniem, natomiast kiedy jest otwarty to znaczy, 偶e po drugiej stronie czeka jaki艣 program (proces) kt贸ry b臋dzie z nami „rozmawia艂”. Samo po艂膮czenie mo偶e by膰 inicjowane na dwa sposoby - pierwszy to tzw. gniazdko aktywne czyli klient - podajemy mu adres i port komputera docelowego a ono 艂膮czy si臋 z serwerem oczekuj膮cym na tym w艂a艣nie porcie, drugi spos贸b to tzw. gniazdko pasywne lub bierne - podajemy mu port lokalny a ono czeka na po艂膮czenie z zewn膮trz czyli pe艂ni rol臋 serwera, standardowe serwery takich us艂ug jak WWW, Mail, FTP, News, IRC maj膮 z g贸ry przypisane porty tak aby艣my mogli si臋 z nimi 艂膮czy膰 podaj膮c tylko adres (np. dla WWW, kt贸rego protoko艂em jest http port to 80) - no dobrze, kto艣 wnikliwy zapyta teraz ile klient贸w mo偶e si臋 w takim razie komunikowa膰 przez jeden port docelowy - odpowied藕 jest prosta i wynika z tego co ju偶 napisa艂em: jeden port - jeden klient jednak za chwil臋 wyja艣ni臋 jak realizowana jest obs艂uga wielu po艂膮cze艅.

Aby zacz膮膰 programowa膰 gniazdka musimy zna膰 kilka podstawowych komend, zajmiemy si臋 teraz najprostszym rodzajem gniazdek pracuj膮cych w protokole TCP zwanych blokuj膮cymi, r贸偶nica mi臋dzy nimi a gniazdkami nie-blokuj膮cymi polega na tym, 偶e te pierwsze wstrzymuj膮 dzia艂anie procesu a偶 dostan膮 jakie艣 dane lub a偶 druga strona zerwie po艂膮czenie, natomiast gniazdka nie-blokuj膮ce umo偶liwiaj膮 obs艂ug臋 wielu po艂膮cze艅 gdy偶 nie przerywaj膮 dzia艂ania procesu (zwracaj膮 po prostu 0 i 偶ycie toczy si臋 dalej ;-)). System operacyjny widzi gniazdko jak standardowy uchwyt - jest to po prostu liczba typu integer, jednak aby m贸c z niej korzysta膰 musi nam zosta膰 przez system przydzielona - s艂u偶y do tego kilka podstawowych funkcji, kt贸re teraz pokr贸tce om贸wi臋 wraz z parametrami wywo艂ania.

WSASTARTUP

wsadata:twsadata; wsastartup(257,wsadata);- tak wygl膮da poprawne wywo艂anie funkcji wsastartup, kt贸re musi by膰 umieszczone przed jakimkolwiek innym odwo艂aniem do funkcji operuj膮cych na gniazdkach. Jest to odst臋pstwo od konwencji gniazdek BSD, na kt贸rych opiera si臋 biblioteka winsock. Nie ma sensu wg艂臋bia膰 si臋 tu w parametry jakie s膮 przekazywane do funkcji, wa偶ne jest 偶eby wiedzie膰 偶e zwraca 0 (zero) je艣li biblioteka mog艂a zosta膰 poprawnie zainicjowana lub kod b艂臋du je艣li takowy wyst膮pi艂 (warto艣膰 r贸偶na od 0).

SOCKET

ssocket:integer; ssocket:=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); - funkcja socket tworzy nowe gniazdko i zwraca jego numer. Jej pierwszy parametr to typ adres贸w jaki b臋dziemy u偶ywa膰, dla Internetu b臋dzie to AF_INET, drugi parametr to typ gniazdka mo偶liwe warto艣ci to SOCK_DGRAM kt贸ry definiuje bezpo艂膮czeniowy spos贸b wymiany danych, kt贸rym si臋 teraz nie b臋dziemy zajmowa膰 oraz SOCK_STREAM, u偶ywany przy protokole TCP. Ostatni parametr funkcji socket to rodzaj protoko艂u - dla nas b臋dzie to IPPROTO_TCP. Kiedy mamy ju偶 tak utworzone gniazdko mo偶emy skorzysta膰 z funkcji do inicjowania po艂膮czenia. Je艣li wywo艂anie si臋 nie powiod艂o zmienna ssocket przyjmie warto艣膰 INVALID_SOCKET a informacje o kodzie b艂臋du zwr贸ci funkcja WSAGetLastError.

CONNECT

ssocket:integer; addr: tsockaddrin; connect(ssocket,addr,sizeof(addr)); - jest to wywo艂anie funkcji connect, kt贸ra spr贸buje zainicjowa膰 po艂膮czenie pomi臋dzy naszym komputerem (a dok艂adniej - niepo艂膮czonym gniazdkiem okre艣lonym w zamiennej ssocket) a odleg艂ym serwerem (jego adres i port znajduj膮 si臋 w zmiennej addr). W tym miejscu warto powiedzie膰 co to takiego w艂a艣ciwie ta struktura tsockaddrin - najpro艣ciej rzecz ujmuj膮c dzi臋ki niej connect wie z jakim komputerem nas po艂膮czy膰. Jednak nie jest to takie proste jak by si臋 wydawa艂o, przede wszystkim biblioteka interfejs gniazdek wymaga specyficznego formatu adres贸w, a do ich reprezentacji u偶ywa odwr贸conego zapisu bajt贸w tzn. najmniej znacz膮cy jest na pocz膮tku - taki uk艂ad nazywa si臋 „sieciowym porz膮dkiem bajt贸w” (network byte order) i s膮 oczywi艣cie funkcje konwertuj膮ce do tego formatu i na odwr贸t. Prze艣led藕my spos贸b podawania adresu docelowego dla funkcji connect, zak艂adaj膮c 偶e: addr jest zmienn膮 typu tsockaddrin, DESTINATION_HOST to ci膮g znak贸w (string) zawieraj膮cy adres komputera docelowego, DESTINATION_PORT to zmienna typu integer zawieraj膮ca port docelowy. Na pocz膮tku czy艣cimy zmienn膮 addr wype艂niaj膮c j膮 zerami fillchar(addr,sizeof(addr),0), teraz musimy okre艣li膰 rodzin臋 adres贸w (tak jak w funkcji socket) addr.sin_family:=AF_INET; nast臋pnie podajemy adres komputera docelowego - do konwersji zapisu aaa.bbb.ccc.ddd na adres s艂u偶y funkcja inet_addr a jej argumentem (jak wi臋kszo艣ci funkcji z bibliotek Windows95) jest wska藕nik do ci膮gu znak贸w zawieraj膮cych wpisany adres - czyli w naszym przypadku zmiennej DESTINATION_HOST addr.sin_addr.s_addr:=inet_addr(pchar(DESTINATION_HOST)); - za艂o偶yli艣my tutaj 偶e u偶ytkownik wprowadzi艂 poprawny adres IP a nie np. nazw臋 komputera, je艣li jednak sta艂o si臋 inaczej to pole sin_addr.s_addr zmiennej addr jest bezu偶yteczne, trzeba co艣 z tym zrobi膰 a najpro艣ciej b臋dzie spr贸bowa膰 skonwertowa膰 DESTINATION_HOST na adres IP za pomoc膮 funkcji he=gethostbyname(pchar(DESTINATION_HOST)); gdzie zmienna he jest typu phostent. phostent to wska藕nik do rekordu hostent zawieraj膮cego informacje o przekszta艂canym adresie. Je艣li funkcja gethostbyname si臋 powiedzie to pole he^.h_addr_list^^ b臋dzie wska藕nikiem do uzyskanego adresu (w formie odpowiadaj膮cej polu sin_addr.s_addr zmiennej addr) a he^.h_length b臋dzie jego d艂ugo艣ci膮, teraz wystarczy tylko przekopiowa膰 odpowiednie pola robimy to za pomoc膮 procedury move(he^.h_addr_list^^, addr.sin_addr.s_addr, he^.h_length). Je艣li gethostbyname zwr贸ci pusty wska藕nik (nil) to znaczy, 偶e 偶膮dany adres jest albo nieosi膮galny albo jest prawid艂owym adresem IP. Tak wi臋c teraz zosta艂 nam jeszcze tylko do okre艣lenia port docelowego komputera robimy to wpisuj膮c do odpowiedniego pola zmiennej addr przekszta艂cony na network byte order numer portu addr.sin_port:=htons(DESTINATION_PORT); je艣li wszystko posz艂o dobrze connect(ssock,addr,sizeof(addr)) zwr贸ci 0 je艣li natomiast wyst膮pi艂 b艂膮d zostanie zwr贸cona warto艣膰 ujemna a kod b艂臋du przeka偶e znana ju偶 funkcja WSAGetLastError.

Kiedy ju偶 mamy po艂膮czone gniazdko wypada艂o by co艣 przez nie wys艂a膰 albo odebra膰 jakie艣 dane - s艂u偶膮 do tego funkcje send oraz recv.

SEND

ssocket:integer; bufor:array[0..1023]of char; bufor_len:integer; flags:integer; send(ssocket,bufor,bufor_len,flags); - funkcja send wysy艂a porcj臋 danych o d艂ugo艣ci bufor_len zawartych w zmiennej bufor, najwygodniej jest je艣li zmienna ta jest tablic膮 znak贸w o rozmiarze np. 1024 bajty. parametr flags m贸wi o sposobie wysy艂ania danych, nas interesuje teraz tylko warto艣膰 0 (flags:=0). Funkcja zwr贸ci warto艣膰 dodatni膮 je艣li wysy艂anie danych powiod艂o si臋 (jest to ilo艣膰 wys艂anych bajt贸w), warto艣c zerow膮 kiedy po艂膮czenie zosta艂o zerwane i warto艣膰 ujemn膮 kiedy nie przysz艂y 偶adne dane (ta swego rodzaju innowacja firmy z Redmont skutecznie utrudnia przenoszenie program贸w).

RECV

ssocket:integer; bufor:array[0..1023]of char; bufor_len:integer; flags:integer; recv(ssocket,bufor,bufor_len,flags); - funkcja recv odbiera porcj臋 danych i umieszcza je w zmiennej bufor (o d艂ugo艣ci bufor_len), parametry podobnie jak zwracane warto艣ci s膮 analogiczne do funkcji send.

Dobrym zwyczajem jest czyszczenie bufora po ka偶dym u偶yciu - robimy to za pomoc膮 procedury fillchar(bufor,sizeof(bufor),0);

Umiemy ju偶 tworzy膰 gniazdko, 艂膮czy膰 si臋, wysy艂a膰 i odbiera膰 dane. Czas by mogli si臋 z nami 艂膮czy膰 ludzie z zewn膮trz. Oto g艂贸wne funkcje u偶ywane po stronie serwera.

BIND

ssocket:integer; addr: tsockaddrin; bind(ssocket,addr,sizeof(addr)); funkcja bind mo偶e by膰 stosowana w dw贸ch przypadkach. Po pierwsze kiedy chcemy okre艣li膰 jaki interfejs sieciowy (o ile mamy wi臋cej ni偶 jeden interfejs - czyli wi臋cej ni偶 jeden lokalny numer IP) oraz port lokalny b臋dzie wykorzystywa艂o gniazdko do komunikacji - wtedy u偶ywamy tej funkcji przed funkcj膮 connect, jednak mo偶na si臋 oby膰 bez niej i zda膰 na domy艣lne ustawienia - wtedy zostanie nam przydzielony domy艣lny adres lokalny i port z przedzia艂u od 1024 do 5000 (domy艣lnie przyjmuje si臋 偶e porty od 0 do 1024 s膮 zarezerwowane dla standardowych us艂ug takich jak WWW czy FTP, jest to konwencja zaczerpni臋ta z UNIXA). Inaczej sprawa wygl膮da kiedy piszemy aplikacj臋 pracuj膮c膮 jako serwer - wtedy koniecznie musimy okre艣li膰 na jakim porcie b臋dzie prowadzony nas艂uch nowych po艂膮cze艅 (jak ju偶 pisa艂em np. dla http jest to port 80), aby to zrobi膰 nale偶y przed wywo艂aniem funkcji poprawnie wype艂ni膰 zmienn膮 addr. Na pocz膮tek okre艣limy rodzin臋 adres贸w - tak jak w funkcji socket i connect b臋dzie j膮 charakteryzowa艂a sta艂a AF_INET, wpisujemy zatem addr.sin_family:=AF_INET. Nast臋pnie okre艣lamy adres lokalny, poniewa偶 w tym przypadku b臋dziemy u偶ywali adresu domy艣lnego wpisujemy addr.sin_addr.s_addr:=htonl(INADDR_ANY). Funkcja htonl przekszta艂ca warto艣膰 sta艂ej INADDR_ANY na format u偶ywany w sieci w postaci typu long (w przeciwie艅stwie do htons, kt贸ra u偶ywa postaci typu short). Ostatnim krokiem jest okre艣lenie jakiego portu lokalnego b臋dziemy u偶ywali - addr.sin_port:=htons(SOURCE_PORT); gdzie zmienna SOURCE_PORT zawiera liczb臋 typu integer, kt贸rej warto艣膰 jest numerem portu. Je艣li funkcja si臋 powiedzie zwraca warto艣膰 0, w przeciwnym razie WSAGetLastError zwraca kod b艂臋du.

LISTEN

ssocket:integer; listen(ssocket,5); funkcja listen s艂u偶y do wprowadzenia gniazdka ssocket w tryb nas艂uchiwania, w tym trybie gniazdko przyjmuje (na okre艣lonym w funkcji bind porcie) wszystkie po艂膮czenia, ilo艣膰 po艂膮cze艅, kt贸re w jednej chwili mog膮 zosta膰 obs艂u偶one charakteryzuje drugi parametr wywo艂ania funkcji przyjmuj膮cy warto艣ci od 1 do 5. Je艣li funkcja si臋 powiedzie zwraca warto艣膰 0, w przeciwnym razie WSAGetLastError zwraca kod b艂臋du.

ACCEPT

ssocket,new_socket:integer; new_socket:=accept(ssocket,nil,nil); funkcja accept pobiera parametry po艂膮czenia z kolejki po艂膮cze艅 gniazdka ssocket. Przedtem gniazdko ssocket musia艂o zosta膰 ustawione w tryb nas艂uchiwania funkcj膮 listen. Je艣li w kolejce s膮 jakie艣 oczekuj膮ce po艂膮czenia funkcja accept zwr贸ci nowo utworzone gniazdko o takich samych parametrach jak ssocket lecz ze zmienionym numerem portu (pierwszym wolnym). W ten spos贸b zmienna new_socket b臋dzie w rzeczywisto艣ci nowym gniazdkiem, dzi臋ki temu ssocket ci膮gle b臋dzie prowadzi膰 nas艂uch na danym porcie. Je艣li w kolejce nie ma 偶adnego po艂膮czenia a ssocket nie zosta艂o przestawione w tryb nieblokuj膮cy dzia艂anie programu zostanie wstrzymane a偶 pojawi si臋 jakie艣 po艂膮czenie. Aby m贸c komunikowa膰 si臋 z przy艂膮czonym do nas klientem (w takim rodzaju po艂膮czenia nasz komputer jest serwerem) u偶ywamy znanych ju偶 funkcji send i recv. Jak 艂atwo zauwa偶y膰 accept ma trzy parametry wywo艂ania: pierwszy to nas艂uchuj膮ce gniazdko, drugi i trzeci s艂u偶膮 do identyfikacji przychodz膮cego po艂膮czenia i s膮 opcjonalne. Je艣li jednak chcemy mie膰 mo偶liwo艣膰 ustalenia to偶samo艣ci naszego klienta to musimy jako drugi parametr poda膰 wska藕nik do zmiennej typu tsockaddrin a jako trzeci wska藕nik do zmiennej zawieraj膮cej d艂ugo艣膰 zmiennej podanej w drugim parametrze.

Przy pisaniu program贸w wykorzystuj膮cych gniazdka blokuj膮ce nale偶y bra膰 pod uwag臋 fakt, 偶e takie funkcje jak: connect, accept, send i recv zatrzymaj膮 dzia艂anie programu na czas nieokre艣lony (dop贸ki nie dostan膮 danych lub nie uzyskaj膮 po艂膮czenia), je艣li chcemy aby funkcja zwraca艂a jak膮艣 warto艣膰 zamiast zatrzymywa膰 program musimy j膮 ustawi膰 w tryb nie blokuj膮cy, mo偶na to zrobi膰 na par臋 sposob贸w - najprostszym z nich jest funkcja ioctlsocket(ssocket,FIONBIO,1) gdzie ssocket to nasze gniazdko, FIONBIO to warto艣膰 odpowiedzialna za ten tryb, a ostatni parametr (tutaj 1) musi by膰 dodatni膮 liczb膮 typu integer. Po takiej operacji np. dla funkcji accept b臋dziemy musieli u偶ywa膰 p臋tli (najlepiej dodatkowo u偶y膰 sleep(100) wewn膮trz p臋tli) i sprawdza膰 czy zwr贸cona warto艣膰 jest wi臋ksza od 0 (je艣li tak to znaczy, 偶e jest to nowo utworzone gniazdko), natomiast send i recv zwr贸c膮 0 je艣li po艂膮czenie zostanie zerwane a -1 gdy nie otrzymaj膮 偶adnych danych (to wspomniana ju偶 innowacja firmy Ma艂emi臋kkie).

Na zako艅czenie dwie funkcje u偶ywane do zako艅czenia pracy z gniazdkami.

SHUTDOWN

ssocket:integer; how:integer; shutdown(ssocket,how); - funkcja zamyka cz臋艣膰 lub ca艂o艣膰 po艂膮czenia. W zale偶no艣ci od warto艣ci parametru how dzieje si臋 to na r贸偶ne sposoby: 0 - wy艂膮czana jest mo偶liwo艣膰 czytania z gniazdka, 1 - wy艂膮czana jest mo偶liwo艣膰 pisania do gniazdka, 2 - wy艂膮czana jest mo偶liwo艣膰 czytania i pisania. Po u偶yciu tej funkcji nie s膮 zwalniane zasoby zwi膮zane z gniazdkiem chocia偶 obecna implementacja biblioteki winsock.dll nie pozwala na u偶ycie funkcji connect na tak zamkni臋tym gniazdku.

CLOSESOCKET

ssocket:integer; closesocket(ssocket); - ostatecznie zwalnia pami臋膰 i zasoby skojarzone z gniazdkiem, u偶ywa si臋 jej po wywo艂aniu funkcji shutdown.

Przyk艂ad programu oraz prostego algorytmu serwera znajduje si臋 w listingu 1, a klienta w listingu 2.



Wyszukiwarka

Podobne podstrony:
2009 10 Programowanie przy u偶yciu gniazd sieciowych [Programowanie]
Jak programowa膰 PATS przy u偶yciu FORScan
Tworzenie szkic贸w miejsca wypadku przy u偶yciu programu PLAN
Jak scrapowa膰 przy u偶yciu programu Corel Photo
Biblioteki Qt Zaawansowane programowanie przy uzyciu C
Biblioteki Qt Zaawansowane programowanie przy uzyciu C 2
Aktualizacja kart przy u偶yciu tunera na Linuxie i programu The Last Drakkar
Programowanie aplikacji dla Sklepu Windows w C Projektowanie innowacyjnych aplikacji sklepu Windows
Biblioteki Qt Zaawansowane programowanie przy uzyciu C
J Bieli艅ski, K Iwi艅ska, A Rosi艅ska Kordasiewicz ANALIZA DANYCH JAKO艢CIOWYCH PRZY U呕YCIU PROGRAM脫W K
Biblioteki Qt Zaawansowane programowanie przy uzyciu C bibqtc
BCT1630, Programowanie przy u偶yciu LPT COM
膯w 1 Pomiar strumienia obj臋to艣ci i masy p艂ynu przy u偶yciu rurek spi臋trzaj膮cych
Wykonywanie rob贸t ziemnych przy u偶yciu koparek
Cele nauczania wyra偶one przy u偶yciu czasownik贸w operacyjnych, edukacja specjalna
1 Wyznaczanie warto艣ci przyspieszenia ziemskiego g przy u偶yciu wahad艂a matematycznego instr przys
Pomiary wykonali艣my przy u偶yciu suwmiarki oraz mikrometru
Projekt badania operacyjne- programowanie sieciowe, Badania operacyjne
konspekt cw 4 programowanie sieciowe

wi臋cej podobnych podstron