Powrót do strony wyboru lekcji

Programowanie sieciowe



Na dzień dzisiejszy, sieć Internet opiera się głównie o protokół TCP/IP. Aby dwie aplikacje mogły się komunikować, muszą być podłączone do tego samego gniazda. Gniazdo sieciowe to pojęcie oznaczające dwukierunkowy punkt końcowy urządzenia. Każde gniazdo posiada 3 właściwości:


Przykład

Tworzymy 2 aplikacje konsolowe których działanie przedstawione jest na rysunku poniżej - Rys.1.

Nie można wyświetlić obrazu
Rys.1.Sposób działania naszej aplikacji sieciowej.

Aplikacja kliencka łączy się za pomocą gniazda sieciowego z aplikacją serwera i wysyła do niej komunikat. Aplikacja serwera odbiera komunikat i wysyła do klienta komunikat zwrotny.

Na początku zostanie opisana aplikacja serwera. W odróżnieniu od poprzednich aplikacji prezentowanych w kursie, na początku programu prócz standardowych bibliotek: user32.lib i kernel32.lib, dołączamy również bibliotekę: ws2_32.lib, która posiada specjalne funkcje zarządzające i kontrolne dla gniazd sieciowych:


.586 ;typ instrukcji procesora

.MODEL FLAT,STDCALL

includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\ws2_32.lib

Następnie definiujemy standardowe wyjście na konsolę i szereg funkcji API:


STD_OUTPUT_HANDLE   EQU -11



EXTERN recv@16:NEAR

EXTERN send@16:NEAR

EXTERN accept@12:NEAR

EXTERN listen@8:NEAR

EXTERN bind@12:NEAR

EXTERN closesocket@4:NEAR

EXTERN socket@12:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN WSAStartup@8:NEAR

EXTERN GetStdHandle@4:NEAR

EXTERN ExitProcess@4:NEAR

EXTERN WriteConsoleA@20:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN lstrlenA@4:NEAR

EXTERN shutdown@8:NEAR

Funkcja recv@16 odbiera dane z aplikacji, która je wysyła. Analogicznie funkcja send@16 służy do wysyłania danych. Obie przyjmują po 4 parametry wywołania: deskryptor gniazda, adres bufora, długość bufora oraz znacznik, któremu najczęściej nadawana jest wartość zerowa. Funkcja accept@12 stosowana jest do przyjmowania od klientów żądań ustanowienia połączenia. Związana jest nierozłącznie z funkcją connect@12, która zgłasza żądanie połączenia przez klienta. Funkcję tą zdefiniujemy w aplikacji klienckiej. Funkcja bind@12 powoduje podłączenie gniazda do medium komunikacyjnego. Funkcja socket@12 tworzy nowe gniazdo sieciowe. Przyjmuje ona 3 parametry: pierwszy określa rodzaj protokołu, drugi to tryb interakcji - połączeniowy lub bezpołączeniowy(stała 1 lub 2), trzeci parametr określa protokół warstwy transportowej. Funkcja closesocket@4, analogicznie powoduje zamknięcie utworzonego wcześniej gniazda. Zanim jednak skorzystamy z biblioteki gniazd, należy ją najpierw zainicjować funkcją - WSAStartup@8. Przyjmuje ona 2 parametry. Pierwszy parametr to podwójne słowo, którego bardziej znaczący bajt mniej znaczącego słowa oznacza poboczny numer wersji biblioteki, a mniej znaczący bajt tego słowa- główny numer wersji. Drugim parametrem jest adres struktury, w której zapisywane są informacje o gniazdach. Jej zawartość, jak i także definicje pozostałych struktur wykorzystywanych w programie przedstawiono poniżej:


WSDATA STRUC
mVersion        WORD      ?
mHighVersion    WORD      ?
szDescription   BYTE       257  dup  (?)
szSystemStatus  BYTE       129  dup  (?)
iMaxSockets     WORD      ?
iMaxUdpDg       WORD      ?
lbVendorlnfo    DWORD     ?
WSDATA ENDS


S_UN_B STRUC
S_b1    BYTE      0
S_b2    BYTE      0
S_b3    BYTE      0
S_b4    BYTE      0
S_UN_B ENDS


S_UN_W STRUC
S_w1    WORD      0
S_w2    WORD      0
S_UN_W ENDS


ADDRESS_UNION UNION
s_u_b     S_UN_B      <>
s_u_w     S_UN_W      <>
s_addr    DWORD      0
ADDRESS_UNION ENDS


in_addr STRUC
s_un      ADDRESS_UNION       <>
in_addr ENDS


sockaddr_in STRUC
sin_family    WORD      0
sin_port      WORD      0
sin_addr      in_addr       <>
sin_zero      BYTE      8  dup  (0)
sockaddr_in ENDS

Z punktu funkcjonalności programu najważniejszą ze struktur jest sockaddr_in, która jest złożeniem wyżej zdefiniowanych struktur i uni. Dane wykorzystywane w programie serwera wyglądają następująco:


.DATA?
HANDL          DD   ? ;uchwyt aplikacji
GNIAZDO1       DD   ?;deskryptor pierwszego gniazda
GNIAZDO2       DD   ?;deskryptor drugiego gniazda
WIELKOSC       DD   ?;rozmiar struktury sockaddr_in
LENS           DD   ?;długość napisu wyświetlanego za pomocą funkcji WriteConsole




.DATA
BUF    DB    100  DUP (0)
POTWIERDZENIE  DB    'Klient połączył się',10,13,0
WYSYLANY       DB    'Wysyłam komunikat do klienta',10,13,0
BLAD           DB    'Błąd serwera',10,13,0
KOMUNIKAT      DB    'Komunikat serwera',10,13,0
sin1           sockaddr_in <?>
sin2           sockaddr_in <?>
WSD            WSDATA <?>

Bufor BUF wykorzystujemy do przesyłania i odbierania danych przez serwer od klienta. Następnie zdefiniowane są 4 zmienne tekstowe, informujące o odpowiedniej operacji wykonywanej w programie serwera, sin1 i sin2 to struktury typu sockaddr_in dla gniazda nr.1 i gniazda nr.2. WSD to struktura typu WSDATA .

Sekcję kodu programu rozpoczynamy standardowym wywołaniem funkcji GetStdHandle@4, pobieramy tym samym uchwyt konsoli do zmiennej HANDL:


.CODE

START:

PUSH  STD_OUTPUT_HANDLE
CALL  GetStdHandle@4
MOV   HANDL,EAX

Następnie inicjujemy bibliotekę gniazd poprzez wywołanie funkcji WSAStartup@8 :


PUSH OFFSET WSD
MOV  EAX, 0
MOV  AX, 0202H
PUSH EAX
CALL WSAStartup@8

CMP  EAX, 0
JZ   NO_ER1

CALL BLEDY
JMP KONIEC

NO_ER1:

W przypadku wystąpienia błędu funkcja zwraca odpowiedni kod. Jeżeli błąd rzeczywiście wystąpi program wykonuje skok do procedury informującej o błędzie - BLEDY a następnie kończy program. Po wywołaniu funkcji WSAStartup@8 , wywołujemy funkcję socket@12 inicjującą nowe gniazdo sieciowe:


PUSH 0 ;specyfikacja rodziny adresów
PUSH 1 ;połączeniowy
PUSH 2 ;wersja 4 protokołu IP
CALL socket@12
MOV GNIAZDO1,EAX

CMP  EAX,NOT 0
JNZ   NO_ER2

CALL BLEDY
JMP KONIEC

NO_ER2:

Jeżeli gniazdo zostanie utworzone prawidłowo, zostanie zwrócony deskryptor gniazda. W przeciwnym wypadku zwracana jest wartość -1 i program wykonuje skok do sekcji obsługi błędu.

Uzupełniamy odpowiednie pola struktury sin1:


MOV  sin1.sin_family,2 ;wersja protokołu ip - 4

MOV  sin1.sin_addr.s_un.s_addr,0

MOV  sin1.sin_port,2000 ;numer portu na którym serwer nasłuchuje żądania klienta

Następnie wywołujemy funkcję bind@12:


PUSH sizeof(sockaddr_in)
PUSH OFFSET SIN1
PUSH GNIAZDO1
CALL bind@12

CMP  EAX, 0
JZ   NO_ER3

CALL BLEDY
JMP KONIEC

NO_ER3:

Wywołujemy funkcję listen@8, aby nasłuchiwać żądań nadchodzących od klienta, drugi parametr funkcji informuje o maksymalnej długości kolejki nadchodzących żądań:


PUSH 2
PUSH GNIAZDO1
CALL listen@8

CMP  EAX, 0
JZ   NO_ER4

CALL BLEDY
JMP KONIEC

NO_ER4:

Funkcja accept@12, odbiera pierwsze żądanie połączenia i zwraca deskryptor gniazda, który będzie stosowany do wymiany danych z klientem:


MOV WIELKOSC,sizeof(sockaddr_in)

PUSH OFFSET WIELKOSC
PUSH OFFSET SIN2
PUSH GNIAZDO1
CALL accept@12
MOV GNIAZDO2,EAX

Następnie wyświetlamy komunikat o udanym połączeniu klienta:


PUSH OFFSET POTWIERDZENIE
PUSH OFFSET POTWIERDZENIE
CALL CharToOemA@8

PUSH OFFSET POTWIERDZENIE
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET POTWIERDZENIE
PUSH HANDL
CALL WriteConsoleA@20

Odbieramy komunikat od klienta i go wyświetlamy:


PUSH 0
PUSH 100
PUSH OFFSET BUF
PUSH GNIAZDO2
CALL recv@16

PUSH OFFSET BUF
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BUF
PUSH HANDL
CALL WriteConsoleA@20

Następnie wyświetlamy informację o wysłaniu komunikatu od serwera do klienta:


PUSH OFFSET WYSYLANY
PUSH OFFSET WYSYLANY
CALL CharToOemA@8

PUSH OFFSET WYSYLANY
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET WYSYLANY
PUSH HANDL
CALL WriteConsoleA@20

Funkcją send@16 wysyłamy komunikat do klienta:


PUSH 0
PUSH OFFSET KOMUNIKAT
CALL lstrlenA@4

PUSH EAX
PUSH OFFSET KOMUNIKAT
PUSH GNIAZDO2
CALL send@16

Po wykonanych przez serwer operacjach, wymuszamy zamknięcie połączenia i gniazda, przez które trwała komunikacja między serwerem a klientem:


PUSH 0
PUSH GNIAZDO2
CALL shutdown@8

PUSH GNIAZDO2
CALL closesocket@4

NIEZAMYKAJ:

JMP NIEZAMYKAJ

PUSH GNIAZDO1
CALL closesocket@4

KONIEC:

PUSH  0
CALL  ExitProcess@4

Procedura obsługi błędu wygląda następująco:


BLEDY   PROC


PUSH OFFSET BLAD
CALL lstrlenA@4

PUSH EAX

PUSH OFFSET BLAD
PUSH OFFSET BLAD
CALL CharToOemA@8

POP EAX

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BLAD
PUSH HANDL
CALL WriteConsoleA@20

RET
BLEDY   ENDP

END Start

Całość programu serwera przedstawiona jest na poniższym listingu:


.586 ;typ instrukcji procesora

.MODEL FLAT,STDCALL

includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\ws2_32.lib


STD_OUTPUT_HANDLE   EQU -11



EXTERN recv@16:NEAR

EXTERN send@16:NEAR

EXTERN accept@12:NEAR

EXTERN listen@8:NEAR

EXTERN bind@12:NEAR

EXTERN closesocket@4:NEAR

EXTERN socket@12:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN WSAStartup@8:NEAR

EXTERN GetStdHandle@4:NEAR

EXTERN ExitProcess@4:NEAR

EXTERN WriteConsoleA@20:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN lstrlenA@4:NEAR

EXTERN shutdown@8:NEAR


WSDATA STRUC
mVersion        WORD      ?
mHighVersion    WORD      ?
szDescription   BYTE       257  dup  (?)
szSystemStatus  BYTE       129  dup  (?)
iMaxSockets     WORD      ?
iMaxUdpDg       WORD      ?
lbVendorlnfo    DWORD     ?
WSDATA ENDS


S_UN_B STRUC
S_b1    BYTE      0
S_b2    BYTE      0
S_b3    BYTE      0
S_b4    BYTE      0
S_UN_B ENDS


S_UN_W STRUC
S_w1    WORD      0
S_w2    WORD      0
S_UN_W ENDS


ADDRESS_UNION UNION
s_u_b     S_UN_B      <>
s_u_w     S_UN_W      <>
s_addr    DWORD      0
ADDRESS_UNION ENDS


in_addr STRUC
s_un      ADDRESS_UNION       <>
in_addr ENDS


sockaddr_in STRUC
sin_family    WORD      0
sin_port      WORD      0
sin_addr      in_addr       <>
sin_zero      BYTE      8  dup  (0)
sockaddr_in ENDS


.DATA?
HANDL          DD   ? ;uchwyt aplikacji
GNIAZDO1       DD   ?;deskryptor pierwszego gniazda
GNIAZDO2       DD   ?;deskryptor drugiego gniazda
WIELKOSC       DD   ?;rozmiar struktury sockaddr_in
LENS           DD   ?;długość napisu wyświetlanego za pomocą funkcji WriteConsole




.DATA
BUF    DB    100  DUP (0)
POTWIERDZENIE  DB    'Klient połączył się',10,13,0
WYSYLANY       DB    'Wysyłam komunikat do klienta',10,13,0
BLAD           DB    'Błąd serwera',10,13,0
KOMUNIKAT      DB    'Komunikat serwera',10,13,0
sin1           sockaddr_in <?>
sin2           sockaddr_in <?>
WSD            WSDATA <?>


.CODE

START:

PUSH  STD_OUTPUT_HANDLE
CALL  GetStdHandle@4
MOV   HANDL,EAX


PUSH OFFSET WSD
MOV  EAX, 0
MOV  AX, 0202H
PUSH EAX
CALL WSAStartup@8

CMP  EAX, 0
JZ   NO_ER1

CALL BLEDY
JMP KONIEC

NO_ER1:

PUSH 0 ;specyfikacja rodziny adresów
PUSH 1 ;połączeniowy
PUSH 2 ;wersja 4 protokołu IP
CALL socket@12
MOV GNIAZDO1,EAX

CMP  EAX,NOT 0
JNZ   NO_ER2

CALL BLEDY
JMP KONIEC

NO_ER2:

MOV  sin1.sin_family,2 ;wersja protokołu ip - 4

MOV  sin1.sin_addr.s_un.s_addr,0

MOV  sin1.sin_port,2000 ;numer portu na którym serwer nasłuchuje żądania klienta


PUSH sizeof(sockaddr_in)
PUSH OFFSET SIN1
PUSH GNIAZDO1
CALL bind@12

CMP  EAX, 0
JZ   NO_ER3

CALL BLEDY
JMP KONIEC

NO_ER3:

PUSH 2
PUSH GNIAZDO1
CALL listen@8

CMP  EAX, 0
JZ   NO_ER4

CALL BLEDY
JMP KONIEC

NO_ER4:

MOV WIELKOSC,sizeof(sockaddr_in)

PUSH OFFSET WIELKOSC
PUSH OFFSET SIN2
PUSH GNIAZDO1
CALL accept@12
MOV GNIAZDO2,EAX

PUSH OFFSET POTWIERDZENIE
PUSH OFFSET POTWIERDZENIE
CALL CharToOemA@8

PUSH OFFSET POTWIERDZENIE
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET POTWIERDZENIE
PUSH HANDL
CALL WriteConsoleA@20

PUSH 0
PUSH 100
PUSH OFFSET BUF
PUSH GNIAZDO2
CALL recv@16

PUSH OFFSET BUF
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BUF
PUSH HANDL
CALL WriteConsoleA@20


PUSH OFFSET WYSYLANY
PUSH OFFSET WYSYLANY
CALL CharToOemA@8

PUSH OFFSET WYSYLANY
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET WYSYLANY
PUSH HANDL
CALL WriteConsoleA@20


PUSH 0
PUSH OFFSET KOMUNIKAT
CALL lstrlenA@4

PUSH EAX
PUSH OFFSET KOMUNIKAT
PUSH GNIAZDO2
CALL send@16


PUSH 0
PUSH GNIAZDO2
CALL shutdown@8

PUSH GNIAZDO2
CALL closesocket@4

NIEZAMYKAJ:

JMP NIEZAMYKAJ

PUSH GNIAZDO1
CALL closesocket@4

KONIEC:

PUSH  0
CALL  ExitProcess@4

BLEDY   PROC


PUSH OFFSET BLAD
CALL lstrlenA@4

PUSH EAX

PUSH OFFSET BLAD
PUSH OFFSET BLAD
CALL CharToOemA@8

POP EAX

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BLAD
PUSH HANDL
CALL WriteConsoleA@20

RET
BLEDY   ENDP

END Start

Pobierz kod

Następnie tworzymy program klienta. Początek programu wygląda prawie tak samo jak program serwera:


.586 ;typ instrukcji procesora

.MODEL FLAT,STDCALL

includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\ws2_32.lib


STD_OUTPUT_HANDLE   EQU -11



EXTERN recv@16:NEAR

EXTERN send@16:NEAR

EXTERN connect@12:NEAR

EXTERN gethostbyname@4:NEAR

EXTERN bind@12:NEAR

EXTERN closesocket@4:NEAR

EXTERN socket@12:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN WSAStartup@8:NEAR

EXTERN GetStdHandle@4:NEAR

EXTERN ExitProcess@4:NEAR

EXTERN WriteConsoleA@20:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN lstrlenA@4:NEAR

EXTERN wsprintfA:NEAR


WSDATA STRUC
mVersion        WORD      ?
mHighVersion    WORD      ?
szDescription   BYTE       257  dup  (?)
szSystemStatus  BYTE       129  dup  (?)
iMaxSockets     WORD      ?
iMaxUdpDg       WORD      ?
lbVendorlnfo    DWORD     ?
WSDATA ENDS


S_UN_B STRUC
S_b1    BYTE      0
S_b2    BYTE      0
S_b3    BYTE      0
S_b4    BYTE      0
S_UN_B ENDS


S_UN_W STRUC
S_w1    WORD      0
S_w2    WORD      0
S_UN_W ENDS


ADDRESS_UNION UNION
s_u_b     S_UN_B      <>
s_u_w     S_UN_W      <>
s_addr    DWORD      0
ADDRESS_UNION ENDS


in_addr STRUC
s_un      ADDRESS_UNION       <>
in_addr ENDS


sockaddr_in STRUC
sin_family    WORD      0
sin_port      WORD      0
sin_addr      in_addr       <>
sin_zero      BYTE      8  dup  (0)
sockaddr_in ENDS


hostent STRUCT
h_name     DWORD      ?
h_alias    DWORD      ?
h_addr     WORD       ?
h_len      WORD       ?
h_list     DWORD      ?
hostent ENDS

Dodatkowymi funkcjami wykorzystanymi w programie klienta, w stosunku do programu serwera są funkcje: connect@12,gethostbyname@4 i wsprintfA. Funkcja connect@12 wysyła żądania połączenia przez klienta. Funkcja gethostbyname@4 na podstawie sieciowej nazwy komputera wypełnia strukturę hostent zdefiniowaną powyżej, wsprintfA swoim działaniem przypomina funkcję printf z języka C. Pozwala na łączenie zmiennej tekstowej z odpowiednimi argumentami i zapisanie ich w odpowiednim formacie w buforze, który następnie można wyświetlić np. funkcją WriteConsoleA@20. Struktura hostent składa się z następujących pól, idąc od góry struktury: adres nazwy komputera, wskaźnik na tablicę nazw dodatkowych (aliasów), typ adresu, długość adresu komputera i tablica adresów komputera rozdzielonych znakami zerowymi. Sekcja danych programu klienta wygląda następująco:


.DATA?
HANDL          DD   ? ;uchwyt aplikacji
IPA            DD   ? ;adres IP serwera z którym łączy się klient
GNIAZDO1       DD   ?;deskryptor drugiego gniazda
WIELKOSC       DD   ?;rozmiar struktury sockaddr_in
LENS           DD   ?;długość napisu wyświetlanego za pomocą funkcji WriteConsole




.DATA
NAZWA          DB   "Jack",0
IP             DB   "Adres IP serwera: %hu.%hu.%hu.%hu",10,13,0
SIN2           sockaddr_in <?>
HP             hostent <?>
BUF            DB    100  DUP (0)
BUF1           DB    100  DUP (0)
WSD            WSDATA <?>
URUCHOMIENIE    DB    "Klient uruchominy",10,13,0
KOMUNIKAT       DB    "Komunikat klienta",10,13,0
ODBIERANIE      DB    "Odbieram komunikat od serwera:",10,13,0
BLAD            DB    "Błąd klienta",10,13,0

W sekcji kodu w odróżnieniu do programu serwera korzystamy z funkcji gethostbyname@4, dzięki której za pomocą nazwy sieciowej komputera - Jack, pobieramy adres IP hosta, na którym uruchomiony jest program serwera:


.CODE

START:

PUSH  STD_OUTPUT_HANDLE
CALL  GetStdHandle@4
MOV   HANDL,EAX


PUSH OFFSET WSD
MOV  EAX, 0
MOV  AX, 0202H
PUSH EAX
CALL WSAStartup@8

CMP  EAX, 0
JZ   NO_ER1

CALL BLEDY
JMP KONIEC

NO_ER1:

PUSH OFFSET NAZWA
CALL gethostbyname@4

CMP  EAX, 0
JNZ   NO_ER2

CALL BLEDY
JMP KONIEC

NO_ER2:

Następnie wyodrębniamy adres IP ze struktury hostent, do której wskaźnik zwróciła nam funkcja gethostbyname@4:


MOV EBX ,[EAX + 12]
MOV EDX ,[EBX]
MOV EDX ,[EDX]
MOV IPA , EDX
SHR EDX , 24
AND EDX , 000000FFh

PUSH EDX

MOV EDX ,IPA
SHR EDX , 16
AND EDX , 000000FFh

PUSH EDX

MOV EDX ,IPA
SHR EDX , 8
AND EDX , 000000FFh

PUSH EDX

MOV EDX ,IPA
AND EDX , 000000FFh

PUSH EDX

PUSH OFFSET IP
PUSH OFFSET BUF1
CALL wsprintfA

Za pomocą funkcji wsprintfA, łączymy odpowiednie człony adresu IP ze zmienną tekstową IP . Poszczególne człony adresu IP to 4 kolejne bajty pod adresem pola umieszczonego w 12 bajcie dalej, biorąc pod uwagę wskaźnik na początek struktury hostent. Gotowy adres IP wyświetlamy funkcją WriteConsoleA@20:


MOV EBX, OFFSET BUF1

PUSH EBX
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH  EBX
PUSH HANDL
CALL WriteConsoleA@20

Następnie inicjujemy strukturę sin2, podając jednak, w porównaniu do analogicznej struktury definiowanej w programie serwera - wskaźnik na adres IP w polu sin_addr.s_un.s_addr:


MOV EDX,IPA


MOV  sin2.sin_family,2 ;wersja protokołu ip - 4

MOV  sin2.sin_addr.s_un.s_addr, EDX

MOV  sin2.sin_port,2000 ;numer portu na którym serwer nasłuchuje żądania klienta

Tworzymy gniazdo i łączymy się z serwerem funkcją connect@12:


PUSH 0 ;specyfikacja rodziny adresów
PUSH 1 ;połączeniowy
PUSH 2 ;wersja 4 protokołu IP
CALL socket@12
MOV GNIAZDO1,EAX

PUSH sizeof(sockaddr_in)
PUSH OFFSET sin2
PUSH GNIAZDO1
CALL connect@12

CMP  EAX, 0
JZ   NO_ER4

CALL BLEDY
JMP KONIEC

NO_ER4:

Informujemy o uruchomieniu klienta:


PUSH OFFSET URUCHOMIENIE
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET URUCHOMIENIE
PUSH HANDL
CALL WriteConsoleA@20

Wysyłamy komunikat do serwera:


PUSH 0
PUSH OFFSET KOMUNIKAT
CALL lstrlenA@4

PUSH EAX
PUSH OFFSET KOMUNIKAT
PUSH GNIAZDO1
CALL send@16

Odbieramy komunikat zwrotny serwera:


PUSH OFFSET ODBIERANIE
PUSH OFFSET ODBIERANIE
CALL CharToOemA@8

PUSH OFFSET ODBIERANIE
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET ODBIERANIE
PUSH HANDL
CALL WriteConsoleA@20

PUSH 0
PUSH 100
PUSH OFFSET BUF
PUSH GNIAZDO1
CALL recv@16

PUSH OFFSET BUF
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BUF
PUSH HANDL
CALL WriteConsoleA@20

Na końcu programu zamykamy gniazdo i definiujemy analogiczną funkcję obsługi błędu, jak w programie serwera:


PUSH GNIAZDO1
CALL closesocket@4

KONIEC:

NIEZAMYKAJ:

JMP NIEZAMYKAJ

PUSH  0
CALL  ExitProcess@4

BLEDY   PROC


PUSH OFFSET BLAD
CALL lstrlenA@4

PUSH EAX

PUSH OFFSET BLAD
PUSH OFFSET BLAD
CALL CharToOemA@8

POP EAX

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BLAD
PUSH HANDL
CALL WriteConsoleA@20

RET
BLEDY   ENDP

END Start

Pełny listing kodu programu klienta przedstawiony jest poniżej:


.586 ;typ instrukcji procesora

.MODEL FLAT,STDCALL

includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\ws2_32.lib


STD_OUTPUT_HANDLE   EQU -11



EXTERN recv@16:NEAR

EXTERN send@16:NEAR

EXTERN connect@12:NEAR

EXTERN gethostbyname@4:NEAR

EXTERN bind@12:NEAR

EXTERN closesocket@4:NEAR

EXTERN socket@12:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN WSAStartup@8:NEAR

EXTERN GetStdHandle@4:NEAR

EXTERN ExitProcess@4:NEAR

EXTERN WriteConsoleA@20:NEAR

EXTERN CharToOemA@8:NEAR

EXTERN lstrlenA@4:NEAR

EXTERN wsprintfA:NEAR


WSDATA STRUC
mVersion        WORD      ?
mHighVersion    WORD      ?
szDescription   BYTE       257  dup  (?)
szSystemStatus  BYTE       129  dup  (?)
iMaxSockets     WORD      ?
iMaxUdpDg       WORD      ?
lbVendorlnfo    DWORD     ?
WSDATA ENDS


S_UN_B STRUC
S_b1    BYTE      0
S_b2    BYTE      0
S_b3    BYTE      0
S_b4    BYTE      0
S_UN_B ENDS


S_UN_W STRUC
S_w1    WORD      0
S_w2    WORD      0
S_UN_W ENDS


ADDRESS_UNION UNION
s_u_b     S_UN_B      <>
s_u_w     S_UN_W      <>
s_addr    DWORD      0
ADDRESS_UNION ENDS


in_addr STRUC
s_un      ADDRESS_UNION       <>
in_addr ENDS


sockaddr_in STRUC
sin_family    WORD      0
sin_port      WORD      0
sin_addr      in_addr       <>
sin_zero      BYTE      8  dup  (0)
sockaddr_in ENDS


hostent STRUCT
h_name     DWORD      ?
h_alias    DWORD      ?
h_addr     WORD       ?
h_len      WORD       ?
h_list     DWORD      ?
hostent ENDS


.DATA?
HANDL          DD   ? ;uchwyt aplikacji
IPA            DD   ? ;adres IP serwera z którym łączy się klient
GNIAZDO1       DD   ?;deskryptor drugiego gniazda
WIELKOSC       DD   ?;rozmiar struktury sockaddr_in
LENS           DD   ?;długość napisu wyświetlanego za pomocą funkcji WriteConsole




.DATA
NAZWA          DB   "Jack",0
IP             DB   "Adres IP serwera: %hu.%hu.%hu.%hu",10,13,0
SIN2           sockaddr_in <?>
HP             hostent <?>
BUF            DB    100  DUP (0)
BUF1           DB    100  DUP (0)
WSD            WSDATA <?>
URUCHOMIENIE    DB    "Klient uruchominy",10,13,0
KOMUNIKAT       DB    "Komunikat klienta",10,13,0
ODBIERANIE      DB    "Odbieram komunikat od serwera:",10,13,0
BLAD            DB    "Błąd klienta",10,13,0
.CODE

START:

PUSH  STD_OUTPUT_HANDLE
CALL  GetStdHandle@4
MOV   HANDL,EAX


PUSH OFFSET WSD
MOV  EAX, 0
MOV  AX, 0202H
PUSH EAX
CALL WSAStartup@8

CMP  EAX, 0
JZ   NO_ER1

CALL BLEDY
JMP KONIEC

NO_ER1:

PUSH OFFSET NAZWA
CALL gethostbyname@4

CMP  EAX, 0
JNZ   NO_ER2

CALL BLEDY
JMP KONIEC

NO_ER2:


MOV EBX ,[EAX + 12]
MOV EDX ,[EBX]
MOV EDX ,[EDX]
MOV IPA , EDX
SHR EDX , 24
AND EDX , 000000FFh

PUSH EDX

MOV EDX ,IPA
SHR EDX , 16
AND EDX , 000000FFh

PUSH EDX

MOV EDX ,IPA
SHR EDX , 8
AND EDX , 000000FFh

PUSH EDX

MOV EDX ,IPA
AND EDX , 000000FFh

PUSH EDX

PUSH OFFSET IP
PUSH OFFSET BUF1
CALL wsprintfA


MOV EBX, OFFSET BUF1

PUSH EBX
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH  EBX
PUSH HANDL
CALL WriteConsoleA@20


MOV EDX,IPA


MOV  sin2.sin_family,2 ;wersja protokołu ip - 4

MOV  sin2.sin_addr.s_un.s_addr, EDX

MOV  sin2.sin_port,2000 ;numer portu na którym serwer nasłuchuje żądania klienta
PUSH 0 ;specyfikacja rodziny adresów
PUSH 1 ;połączeniowy
PUSH 2 ;wersja 4 protokołu IP
CALL socket@12
MOV GNIAZDO1,EAX

PUSH sizeof(sockaddr_in)
PUSH OFFSET sin2
PUSH GNIAZDO1
CALL connect@12

CMP  EAX, 0
JZ   NO_ER4

CALL BLEDY
JMP KONIEC

NO_ER4:
PUSH OFFSET URUCHOMIENIE
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET URUCHOMIENIE
PUSH HANDL
CALL WriteConsoleA@20


PUSH 0
PUSH OFFSET KOMUNIKAT
CALL lstrlenA@4

PUSH EAX
PUSH OFFSET KOMUNIKAT
PUSH GNIAZDO1
CALL send@16


PUSH OFFSET ODBIERANIE
PUSH OFFSET ODBIERANIE
CALL CharToOemA@8

PUSH OFFSET ODBIERANIE
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET ODBIERANIE
PUSH HANDL
CALL WriteConsoleA@20


PUSH 0
PUSH 100
PUSH OFFSET BUF
PUSH GNIAZDO1
CALL recv@16

PUSH OFFSET BUF
CALL lstrlenA@4

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BUF
PUSH HANDL
CALL WriteConsoleA@20


PUSH GNIAZDO1
CALL closesocket@4

KONIEC:

NIEZAMYKAJ:

JMP NIEZAMYKAJ

PUSH  0
CALL  ExitProcess@4

BLEDY   PROC


PUSH OFFSET BLAD
CALL lstrlenA@4

PUSH EAX

PUSH OFFSET BLAD
PUSH OFFSET BLAD
CALL CharToOemA@8

POP EAX

PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH OFFSET BLAD
PUSH HANDL
CALL WriteConsoleA@20

RET
BLEDY   ENDP

END Start

Pobierz kod


Przykładowe wywołanie programu klienta komunikującego się z programem serwera w sieci lokalnej przedstawiono na rysunku poniżej. - Rys.2. Należy pamiętać, aby podać poprawną nazwę sieciową komputera, na którym uruchamiany jest serwer w kodzie klienta. W naszym przypadku jest to nazwa: " Jack ". Inaczej klient nie będzie w stanie zlokalizować programu serwera uruchomionego na innym komputerze.


Nie można wyświetlić obrazu
Rys.2.Uruchomione aplikacje sieciowe: po prawej stronie aplikacja klienta, po lewej aplikacja serwera.


Ćwiczenie 9

Należy zaprogramować 2 komunikatory do komunikacji pomiędzy dwoma komputerami w oparciu o architekturę P2P. Pierwszy wysyła wiadomości tekstowe korzystając z portu 2000 a odbiera na porcie 2001. Natomiast drugi, wysyła na porcie 2001 a odbiera na porcie 2000. Użytkownik ma mieć możliwość wprowadzenia nazwy komputera, na którym uruchomiony jest drugi komunikator. Dopiero po podaniu nazwy i wciśnięciu przycisku - "Połącz" nawiązywane jest połączenie i możliwe jest wysyłanie, bądź odbieranie wiadomości z drugiego komunikatora. Odbieranie wiadomości jak i wysyłanie w każdym z komunikatorów ma być realizowane przez osobne wątki. Wysyłanie wiadomości następuje po wciśnięciu przycisku - "Wyślij wiadomość". Po kliknięciu przycisku, wiadomość jest dodawana do pola rozmowy zarówno komunikatora na którym piszemy, jak i komunikatora do którego wysłaliśmy wiadomość. W przypadku braku podanego komputera z którym chcemy się połączyć w polu "Nazwa komputera" komunikator powinien wygenerować stosowny komunikat. Pole rozmowy jest czyszczone po wciśnięciu przycisku- "Czyść okno komunikatora". Przykładowe użycie komunikatorów na dwóch komputerach przedstawiono na rysunku poniżej- Rys.3

Pobierz szablon

Nie można wyświetlić obrazu
Rys.3. Interfejsy komunikatorów.

<Poprzednia lekcjaKolejna lekcja>