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:
Tworzymy 2 aplikacje konsolowe których działanie przedstawione jest na rysunku poniżej - Rys.1.
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
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
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.
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