   #Start Contents English version

                  Pisanie programow sieciowych pod Linuksem

   Linux jest systemem typowo sieciowym. Nawet niektore uslugi systemowe
   dzialaja jako serwery sieciowe, umozliwiajac dostep maszynom z
   zewnatrz. A ja bez niepotrzebnego zaglebiania sie w porty, protokoly i
   inne szczegoly dotyczace sieci, pokaze teraz, jak napisac prosty,
   wlasny serwerek i klienta do niego.

   Komunikacja w sieci odbywa sie z wykorzystaniem wielu roznych
   elementow. Podstawowym pojeciem jest gniazdo (ang. socket). Jest to
   logiczne (czyli nie istniejace fizyczne) urzadzenie bedace podstawowa
   bramka, przez ktora przeplywaja informacje. Gniazdko tworzy sie
   funkcja socket (z biblioteki jezyka C, tak jak wszystkie pozniejsze).
   Przyjmuje ona 3 argumenty (patrz: man 2 socket):
    1. domena - okresla typ polaczenia. My wykorzystamy wartosc
       PF_INET=2, oznaczajaca protokoly internetowe (IPv4)
    2. typ gniazda - okresla, czy gniazdo jest datagramowe, strumieniowe,
       surowe (raw) itp. My wykorzystamy gniazdo strumieniowe
       SOCK_STREAM=1 i protokol TCP (Transmission Control Protocol),
       ktory gwarantuje wiarygodne, dwustronne polaczenie.
    3. protokol, jesli nie jest on jednoznacznie wyznaczony. U nas TCP
       jest jednoznacznie wyznaczony przez typ gniazda (strumieniowe),
       wiec ten argument przyjmuje wartosc 0.

   Jesli utworzenie gniazda nie udalo sie, funkcja socket zwroci wartosc
   -1. Jesli sie udalo, zwroci liczbe calkowita - deskryptor otwartego
   gniazda (podobnie, jak w plikach). Po zakonczeniu pracy gniazdo mozna
   zamknac funkcja close.

   Od chwili utworzenia gniazda dalszy kod w serwera i klienta roznia
   sie, wiec omowie je po kolei.
     _________________________________________________________________

Serwer

   (przeskocz opis serwera)

   Jak wiemy, zadaniem serwera jest nasluchiwanie polaczen od klientow.
   Aby to osiagnac, nalezy wykonac nastepujace kroki.
    1. Przypisanie gniazda do adresu.
       W chwili utworzenia, gniazdo nie jest jeszcze przypisane do
       adresu, a przeciez trzeba jakos okreslic, na jakim adresie i
       porcie nasluchuje nasz serwer. Sluzy do tego funkcja bind.
       Przyjmuje ona nastepujace argumenty (patrz: man 2 bind):
         1. gniazdo, ktore utworzylismy funkcja socket
         2. adres struktury sockaddr, ktora zaraz sie zajmiemy
         3. dlugosc tejze struktury
       Choc definicja funkcji bind mowi o strukturze sockaddr, to funkcji
       tej podaje sie odpowiednio rzutowany wskaznik do struktury
       sockaddr_in. Ta struktura wyglada tak:
       (przeskocz strukture sockaddr_in)
          struc sockaddr_in
                .sin_family resw 1      ; rodzina adresow
                .sin_port   resw 1      ; numer portu
                .sin_addr   resd 1      ; adres
                            resb 8      ; dopelnienie do 16 bajtow
          endstruc
       Do pola sin_family wpisujemy AF_INET=2, oznaczajace rodzine
       adresow internetowych.
       Do pola sin_port wpisujemy numer portu, na ktorym bedzie
       nasluchiwal nasz serwer. Ale uwaga - nie bezposrednio! Najpierw
       numer portu musi zostac przetlumaczony na sieciowy porzadek bajtow
       funkcja htons (patrz: man htons). Dopiero wynik funkcji, ktorej
       podajemy numer portu, wpisujemy w to pole. Programy bez uprawnien
       administratora moga korzystac tylko z portow o numerach powyzej
       1023.
       Do pola sin_addr wpisujemy wartosc INADDR_ANY=0, co oznacza, ze
       chcemy nasluchiwac na dowolnym adresie.
       W przypadku bledu, bind zwraca -1.
    2. Wlaczenie nasluchiwania na danym gniezdzie.
       Aby wlaczyc nasluchiwanie na danym gniezdzie, nalezy uzyc funkcji
       listen. Przyjmuje ona dwa argumenty (patrz: man 2 listen):
         1. gniazdo, utworzone funkcja socket z adresem przypisanym
            funkcja bind
         2. maksymalna liczbe klientow oczekujacych w kolejce na obsluge
       W przypadku bledu, listen zwraca -1.
       Jesli funkcja listen sie powiedzie, to mozna z serwerem przejsc w
       tryb demona (o tym w kursie o pisaniu programow rezydentnych).

   Po wlaczeniu nasluchiwania na gniezdzie mozemy zaczac przyjmowac
   polaczenia od klientow. Przyjecie polaczenia odbywa sie funkcja
   accept. Przyjmuje ona trzy argumenty (patrz: man 2 accept):
    1. nasluchujace gniazdo
    2. zero lub adres struktury sockaddr (lub tej samej sockaddr_in,
       ktora podalismy dla bind). Struktura ta otrzyma dane o kliencie
       (na przyklad jego adres)
    3. adres zmiennej zawierajacej dlugosc struktury z parametru numer 2

   Gdy klient juz sie polaczyl, accept zwraca deskryptor nowego gniazda,
   ktore bedzie sluzyc do komunikacji z klientem.
     _________________________________________________________________

Klient

   (przeskocz opis klienta)

   W porownaniu z serwerem, w kliencie jest mniej pracy. Po utworzeniu
   gniazda do polaczenia sie z serwerem wystarczy jedna funkcja -
   connect. Przyjmuje ona trzy argumenty (patrz: man 2 connect):
    1. gniazdo utworzone funkcja socket
    2. adres struktury sockaddr
    3. dlugosc tejze struktury

   Tutaj takze zamiast struktury sockaddr przekazujemy adres struktury
   sockaddr_in. Jednak trzeba ja troche inaczej wypelnic.

   Pola sin_family i sin_port wypelniamy tak samo, jak dla bind. W koncu
   chcemy sie polaczyc do tego samego portu, na ktorym nasluchuje serwer.

   Pole sin_addr wypelniamy adresem IP serwera. Oczywiscie nie wprost
   jako lancuch znakow, ale odpowiednio przerobionym. Do przerobienia
   lancucha znakow 127.0.0.1 (oznaczajacego zawsze biezacy komputer dla
   niego samego) na wlasciwa postac posluzy nam funkcja inet_aton.
   Przyjmuje ona 2 argumenty (patrz: man inet_aton):
    1. adres lancucha znakow z adresem w zapisie dziesietnym kropkowym
       (ttt.xxx.yyy.zzz)
    2. adres struktury in_addr, ktora otrzyma wynik

   Struktura in_addr jest jedyna skladowa pola sin_addr w naszej
   strukturze sockaddr_in i to adres tego wlasnie pola podajemy funkcji
   inet_aton.

   Po poprawnym wykonaniu polaczenia funkcja connect, mozna przystapic do
   wymiany danych.
     _________________________________________________________________

Wymiana danych

   (przeskocz wymiane danych)

   Po dokonaniu polaczenia obie strony - klient i serwer - maja gotowe
   gniazda, ktorymi moga sie komunikowac. Do wymiany danych sluza dwie
   podstawowe funkcje: send i recv. Obie przyjmuja dokladnie te same
   cztery parametry (patrz: man 2 send, man 2 recv):
    1. gniazdo, ktore jest polaczone z klientem/serwerem
    2. adres bufora odbiorczego/nadawczego
    3. dlugosc tego bufora
    4. specjalne flagi, jesli jest taka potrzeba. U nas bedzie to zero.
     _________________________________________________________________

Przyklad

   Po przebrnieciu przez tl trudna teorie mozemy wreszcie przystapic do
   pisania programow. Wiem, ze sucha teoria nie umozliwi natychmiastowego
   napisania programow serwera i klienta (jest wiele pulapek, na ktore
   trzeba zwrocic uwage), dlatego prezentuje tutaj przykladowe programy
   serwera i klienta (skladnia NASMa).

   Serwer:
   (przeskocz program serwera)
; Program serwera
;
; autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
; nasm -O999 -f elf -o serwer.o serwer.asm
; gcc -o serwer serwer.o

section .text
global  main            ; bedziemy korzystac z biblioteki C, wiec
                        ; funkcja glowna musi sie nazywac "main"

; definicje kilku przydatnych stalych
%define PF_INET         2
%define AF_INET         PF_INET
%define SOCK_STREAM     1
%define INADDR_ANY      0

%define NPORTU          4242
%define MAXKLIENT       5       ; maksymalna liczba klientow

; zewnetrzne funkcje z biblioteki C, z ktorych bedziemy korzystac
extern  daemon
extern  socket
extern  listen
extern  accept
extern  bind
extern  htons
extern  recv
extern  send
extern  close

main:
        push    dword 0
        push    dword SOCK_STREAM
        push    dword AF_INET
        call    socket                  ; tworzymy gniazdo:
                                        ;socket(AF_INET,SOCK_STREAM,0);
        add     esp, 12                 ; usuwamy argumenty ze stosu

        cmp     eax, 0                  ; EAX < 0 oznacza blad
        jl      .sock_blad

        mov     [gniazdo], eax          ; zachowujemy deskryptor gniazda

        push    word NPORTU
        call    htons                   ; przerabiamy numer portu na
                                        ; wlasciwy format
                                        ; htons(NPORTU);
        add     esp, 2

                        ; wpisujemy przerobiony numer portu:
        mov     [adres+sockaddr_in.sin_port], ax
                        ; rodzina adresow internetowych:
        mov     word [adres+sockaddr_in.sin_family], AF_INET
                        ; akceptujemy kazdy adres
        mov     dword [adres+sockaddr_in.sin_addr], INADDR_ANY

        push    dword sockaddr_in_size
        push    dword adres
        push    dword [gniazdo]
        call    bind                    ; przypisujemy gniazdo do adresu:
                                ; bind(gniazdo,&adres,sizeof(adres));
        add     esp, 12

        cmp     eax, 0
        jl      .bind_blad

        push    dword MAXKLIENT
        push    dword [gniazdo]
        call    listen                  ; wlaczamy nasluchiwanie:
                                        ; listen(gniazdo,MAXKLIENT);
        add     esp, 8

        cmp     eax, 0
        jl      .list_blad

        push    dword 1
        push    dword 1
        call    daemon                  ; przechodzimy w tryb demona
        add     esp, 8                  ; usuniecie argumentow ze stosu

        mov     dword [rozmiar], sockaddr_in_size

.czekaj:
        push    dword rozmiar           ; [rozmiar] zawiera rozmiar
                                        ; struktury sockaddr_in
        push    dword adres
        push    dword [gniazdo]
        call    accept                  ; czekamy na polaczenie
                                ; accept(gniazdo,&adres,&rozmiar)
        add     esp, 12
        cmp     eax, 0
        jl      .czekaj

        mov     [gniazdo_kli], eax      ; gdy accept sie udalo,
                                        ; zwraca nowe gniazdo klienta

.rozmowa:
        push    dword 0
        push    dword buf_d
        push    dword bufor
        push    dword [gniazdo_kli]
        call    recv                    ; odbieramy dane;
                        ; recv(gniazdo_kli,&bufor,sizeof(bufor),0);
        add     esp, 16

        cmp     eax, 0                  ; jesli blad, to czekamy ponownie
        jl      .rozmowa

        cmp     byte [bufor], "Q"       ; ustalamy, ze Q konczy transmisje
        je      .koniec

        mov     ecx, buf_d
        mov     edi, bufor
        xor     eax, eax
        cld
        rep     stosb                   ; czyscimy bufor

        push    dword 0
        push    dword 2
        push    dword ok
        push    dword [gniazdo_kli]
        call    send                    ; wysylamy dane
                                        ; (na cokolwiek odpowiadamy "OK")
                                        ; send(gniazdo_kli,&ok,2,0);
        add     esp, 16

        jmp     .rozmowa                ; i czekamy od nowa

.koniec:
        push    dword 0
        push    dword buf_d
        push    dword bufor
        push    dword [gniazdo_kli]
        call    send                    ; wysylamy Q, ktore jest w buforze
        add     esp, 16

        push    dword [gniazdo_kli]
        call    close                   ; zamykamy gniazdo klienta
        add     esp, 4

; jesli chcemy, aby serwer nasluchiwal kolejnych polaczen, piszemy tu:
;;;     jmp     .czekaj
; serwera nie da sie wtedy inaczej zamknac niz przez zabicie procesu

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo glowne serwera
        add     esp, 4

        mov     eax, 1
        xor     ebx, ebx
        int     80h                     ; wychodzimy z programu


; obsluga bledow:

.sock_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_socket
        mov     edx, blad_socket_d
        int     80h                     ; wyswietlenie napisu

        mov     eax, 1
        mov     ebx, 1
        int     80h                     ; wyjscie z programu z
                                        ; odpowiednim kodem bledu

.bind_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_bind
        mov     edx, blad_bind_d
        int     80h

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo

        mov     eax, 1
        mov     ebx, 2
        int     80h

.list_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_listen
        mov     edx, blad_listen_d
        int     80h

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo

        mov     eax, 1
        mov     ebx, 3
        int     80h


section .data

                                        ; deskryptory gniazd:
gniazdo         dd      0
gniazdo_kli     dd      0

bufor           times   20      db      0       ; bufor odbiorczo-nadawczy
buf_d           equ     $ - bufor               ; dlugosc bufora

                                                ; komunikaty bledow:
blad_socket     db      "Problem z socket!", 10
blad_socket_d   equ     $ - blad_socket

blad_bind       db      "Problem z bind!", 10
blad_bind_d     equ     $ - blad_bind

blad_listen     db      "Problem z listen!", 10
blad_listen_d   equ     $ - blad_listen

ok              db      "OK"            ; to, co wysylamy

struc sockaddr_in

        .sin_family     resw    1               ; rodzina adresow
        .sin_port       resw    1               ; numer portu
        .sin_addr       resd    1               ; adres

                        resb    8               ; dopelnienie do 16 bajtow
endstruc

adres           istruc  sockaddr_in             ; adres jako zmienna, ktora
                                                ; jest struktura
rozmiar         dd      sockaddr_in_size        ; rozmiar struktury

   Klient:
   (przeskocz program klienta)
; Program klienta
;
; autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
; nasm -O999 -f elf -o klient.o klient.asm
; gcc -o klient klient.o

section .text
global  main            ; bedziemy korzystac z biblioteki C, wiec
                        ; funkcja glowna musi sie nazywac "main"

; definicje kilku przydatnych stalych
%define PF_INET         2
%define AF_INET         PF_INET
%define SOCK_STREAM     1
%define INADDR_ANY      0

%define NPORTU          4242

; zewnetrzne funkcje z biblioteki C, z ktorych bedziemy korzystac
extern  socket
extern  connect
extern  htons
extern  recv
extern  send
extern  close
extern  inet_aton

main:
        push    dword 0
        push    dword SOCK_STREAM
        push    dword AF_INET
        call    socket                  ; tworzymy gniazdo:
                                        ; socket(AF_INET,SOCK_STREAM,0);
        add     esp, 12                 ; usuwamy argumenty ze stosu

        cmp     eax, 0                  ; EAX < 0 oznacza blad
        jle     .sock_blad

        mov     [gniazdo], eax          ; zachowujemy deskryptor gniazda

                                        ; rodzina adresow internetowych:
        mov     word [adres+sockaddr_in.sin_family], AF_INET

        push    dword (adres + sockaddr_in.sin_addr)
        push    dword localhost
        call    inet_aton               ; przerabiamy adres 127.0.0.1 na
                                        ; wlasciwy format
        add     esp, 8
        test    eax, eax                ; EAX = 0 oznacza, ze adres
                                        ; byl nieprawidlowy
        jz      .inet_blad

        push    word NPORTU
        call    htons                   ; przerabiamy numer portu
                                        ; na wlasciwy format
        add     esp, 2
                                ; wpisujemy przerobiony numer portu:
        mov     word [adres+sockaddr_in.sin_port], ax

        push    dword sockaddr_in_size
        push    dword adres
        push    dword [gniazdo]
        call    connect                 ; laczymy sie z serwerem:
                                ; connect(gniazdo,&adres,sizeof(adres));
        add     esp, 12

        cmp     eax, 0
        jne     .conn_blad

.rozmowa:
        mov     eax, 3
        mov     ebx, 0
        mov     ecx, bufor
        mov     edx, buf_d
        int     80h                     ; wczytujemy dane ze
                                        ; standardowego wejscia

        push    dword 0
        push    dword buf_d
        push    dword bufor
        push    dword [gniazdo]
        call    send                    ; wysylamy to, co wczytalismy:
                                ; send(gniazdo,&bufor,sizeof(bufor),0);
        add     esp, 16

        cmp     eax, 0
        jl      .send_blad

        mov     ecx, buf_d
        mov     edi, bufor
        xor     eax, eax
        cld
        rep     stosb                   ; czyscimy bufor

.odbieraj:
        push    dword 0
        push    dword buf_d
        push    dword bufor
        push    dword [gniazdo]
        call    recv                    ; odbieramy dane od serwera:
                                ; recv(gniazdo,&bufor,sizeof(bufor),0);
        add     esp, 16

        cmp     eax, 0
        jl      .odbieraj

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, odebrano
        mov     edx, odebrano_dl
        int     80h                     ; wypisujemy, co odebralismy

        cmp     byte [bufor], "Q"               ; "Q" konczy transmisje
        jne     .rozmowa

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo
        add     esp, 4

        mov     eax, 1
        xor     ebx, ebx
        int     80h                     ; wychodzimy z programu


; sekcja obslugi bledow


.sock_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_socket
        mov     edx, blad_socket_d
        int     80h                     ; wyswietlenie napisu

        mov     eax, 1
        mov     ebx, 1
        int     80h                     ; wyjscie z programu z
                                        ; odpowiednim kodem bledu

.conn_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_connect
        mov     edx, blad_connect_d
        int     80h

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo

        mov     eax, 1
        mov     ebx, 2
        int     80h

.inet_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_inet
        mov     edx, blad_inet_d
        int     80h

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo

        mov     eax, 1
        mov     ebx, 3
        int     80h

.send_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_send
        mov     edx, blad_send_d
        int     80h

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo

        mov     eax, 1
        mov     ebx, 4
        int     80h

.recv_blad:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, blad_recv
        mov     edx, blad_recv_d
        int     80h

        push    dword [gniazdo]
        call    close                   ; zamykamy gniazdo

        mov     eax, 1
        mov     ebx, 5
        int     80h

section .data

gniazdo         dd      0                       ; deskryptor gniazda

odebrano        db      "Serwer: "
bufor           times   20      db      0       ; bufor nadawczo-odbiorczy
buf_d           equ     $ - bufor               ; dlugosc bufora
                db      10                      ; przejscie do nowej linii
odebrano_dl     equ     $ - odebrano

                                                ; komunikaty bledow
blad_socket     db      "Problem z socket!", 10
blad_socket_d   equ     $ - blad_socket

blad_connect    db      "Problem z connect!", 10
blad_connect_d  equ     $ - blad_connect

blad_inet       db      "Problem z inet_aton!", 10
blad_inet_d     equ     $ - blad_inet

blad_send       db      "Problem z send!", 10
blad_send_d     equ     $ - blad_send

blad_recv       db      "Problem z recv!", 10
blad_recv_d     equ     $ - blad_recv


localhost       db      "127.0.0.1", 0          ; adres, z ktorym
                                                ; bedziemy sie laczyc

struc sockaddr_in

        .sin_family     resw    1               ; rodzina adresow
        .sin_port       resw    1               ; numer portu
        .sin_addr       resd    1               ; adres

                        resb    8               ; dopelnienie do 16 bajtow
endstruc

adres           istruc  sockaddr_in             ; adres jako zmienna,
                                                ; ktora jest struktura

   Jako ze programy te korzystaja z biblioteki jezyka C, ich kompilacja
   musi wygladac troche inaczej niz zwykle:
        nasm -f elf -o plik.o plik.asm
        gcc -o plik plik.o

   Po kompilacji najpierw oczywiscie uruchamiamy serwer poleceniem
   ./serwer (program serwera sam przejdzie w tlo). Mozecie sprawdzic, co
   sie stanie, jesli dwa razy sprobujecie uruchomic serwer lub
   uruchomicie klienta bez uruchomionego serwera.

   Oczywiscie, serwer moze tez byc klientem innego serwera (na przyklad
   po odebraniu danych przerabiac je i przekazywac dalej).
     _________________________________________________________________

Funkcje sieciowe przerwania int 80h

   (przeskocz int 80h)

   Korzystanie z sieci jest oczywiscie mozliwe takze bez posrednictwa
   biblioteki jezyka C. W koncu kazda tak istotna funkcja przeciez musi
   byc zaprogramowana jako czesc jadra.

   Interfejs sieciowy jadra to jedna funkcja - sys_socketcall (numer
   102). Przyjmuje ona dwa argumenty. Pierwszy (w EBX) to funkcja, ktora
   chcemy uruchomic. Kazda wspomniana wczesniej funkcja z biblioteki C ma
   swoj numer. Sa to: dla socket - 1, dla bind - 2, connect - 3, listen -
   4, accept - 5, send - 9, recv - 10. Funkcja close jest ta sama, ktorej
   uzywa sie do zamykania plikow (a wiec EBX=[gniazdo], EAX=6, int 80h).

   Drugim argumentem (w ECX) jest adres reszty argumentow, ktore
   podalibysmy funkcji z biblioteki C. Mozna je bez przeszkod w tej samej
   kolejnosci, co wczesniej, umiescic na stosie, po czym wykonac
   instrukcje mov ecx, esp. Z reszta, tak to wlasnie robi biblioteka C
   (plik sysdeps/unix/sysv/linux/i386/socket.S w zrodlach glibc, tam
   jednak jest "ecx+4", gdyz nalezy przeskoczyc jeszcze adres powrotny z
   funkcji). Mozna te dane umiescic oczywiscie w swojej sekcji danych i
   podac ich adres, ale dane te musza byc jedna po drugiej dokladnie w
   takiej kolejnosci, w jakiej znajdowalyby na stosie (czyli od lewej do
   prawej na wzrastajacych adresach). Po prostu po kolei, wedlug
   deklaracji C, od lewej do prawej.

   Do omowienia zostaja jeszcze funkcje pomocnicze - htons i inet_aton.

   Funkcja htons jest dosc prosta w budowie (plik sysdeps/i386/htons.S w
   zrodlach glibc), jej tresc miesci sie w takim oto makrze (zakladajac,
   ze argument jest w EAX):
        %macro htons 0
                and     eax, 0FFFFh
                ror     ax, 8
        %endm

   Czyli po prostu zeruje gorna polowe EAX i zamienia zawartosc rejestrow
   AH i AL miedzy soba.

   Funkcja inet_aton (plik resolv/inet_addr.c w zrodlach glibc) jest
   troche trudniejsza. Wole znacznie wszystko skrocic i powiedziec, ze
   adres nalezy zaladowac do rejestru EAX binarnie, czyli na przyklad z
   127.0.0.1 dostajemy EAX=7F000001h, a z 192.168.0.2 - EAX=C0A80002h.
   Potem trzeba odwrocic kolejnosc bajtow. Najlepiej od poczatku
   skorzystac z nastepujacego makra:
        %macro adr2bin 4

                mov     al, %4
                shl     eax, 8
                mov     al, %3
                shl     eax, 8
                mov     al, %2
                shl     eax, 8
                mov     al, %1
        %endm

        ; uzycie:
                adr2bin 127, 0, 0, 1       ; dla adresu 127.0.0.1
                adr2bin 192, 168, 45, 243  ; dla adresu 192.168.45.243

   ktorego wynik (EAX) zapisujemy do pierwszych czterech bajtow pola
   sin_addr struktury sockaddr_in (co normalnie funkcja inet_aton robila
   automatycznie).

   To cale odwracanie bierze sie z tego, ze porzadek bajtow w protokole
   TCP jest typu big-endian, a procesory zgodne z Intelem sa typu
   little-endian.

   O tym, jak pisac demony korzystajac wylacznie z przerwania int 80h,
   napisalem w kursie o pisaniu programow rezydentnych.
     _________________________________________________________________

Funkcje sieciowe w systemie 64-bitowym

   (przeskocz system 64-bitowy)

   Obsluga sieci rozni sie nieco na systemach 64-bitowych w porownaniu z
   systemami 32-bitowymi. Nie tylko zmienia sie numer funkcji, ale teraz
   poszczegolne operacje sieciowe maja swoje wlasne funkcje systemowe. Sa
   to: socket - 41, connect - 42, accept - 43, sendto - 44, recvfrom -
   45, bind - 49, listen - 50. Reszta parametrow jest przekazywana nie na
   stosie, a w kolejnych rejestrach, zgodnie z interfejsem systemu
   64-bitowego (kolejno w rejestrach: RDI, RSI, RDX, R10, R8, R9). Samo
   wywolanie systemu nastepuje instrukcja syscall, a nie poprzez
   przerwanie 80h.

   Przykladowe wywolania funkcji wygladaja wiec nastepujaco:
        mov     rax, 41                 ; socket
        mov     rdi, AF_INET
        mov     rsi, SOCK_STREAM
        mov     rdx, IPPROTO_TCP
        syscall

        mov     rax, 42                 ; connect
        mov     rdi, [socket]
        mov     rsi, sock_struc
        mov     rdx, sockaddr_in_size
        syscall

        mov     rax, 44                 ; sendto
        mov     rdi, [socket]
        mov     rsi, buf
        mov     rdx, buf_ile
        mov     r10, 0
        syscall

        mov     rax, 49                 ; bind
        mov     rdi, [socket]
        mov     rsi, sock_struc
        mov     rdx, sockaddr_in_size
        syscall

        mov     rax, 50                 ; listen
        mov     rdi, [socket]
        mov     rsi, MAXKLIENT
        syscall

        mov     rax, 43                 ; accept
        mov     rdi, [socket]
        mov     rsi, sock_struc
        mov     rdx, sockaddr_in_size
        syscall

        mov     rax, 45                 ; recvfrom
        mov     rdi, [socket_client]
        mov     rsi, buf
        mov     rdx, buf_ile
        mov     r10, 0
        syscall

        ...
struc sockaddr_in
        .sin_family:    resw 1
        .sin_port:      resw 1
        .sin_addr:      resd 1
                        resb 8
endstruc

sock_struc istruc sockaddr_in

   Funkcje htons i inet_aton sa takie same, jak dla systemow 32-bitowych
   (bo przeciez kolejnosc bajtow przesylanych w sieci sie nie zmienia).
     _________________________________________________________________

   Warto jeszcze wspomniec o dwoch sprawach. Pierwsza to programy strace
   i ltrace. Pozwalaja one na sledzenie, ktorych funkcji systemowych i
   kiedy dany program uzywa. Jesli cos Wam nie dziala, wylaczcie tryb
   demona w serwerze, po czym uruchomcie strace ./serwer i patrzcie, na
   ktorych wywolaniach funkcji sa jakies problemy. Podobnie mozecie
   oczywiscie zrobic z klientem, na przyklad na drugim terminalu. Po
   szczegoly odsylam do stron manuala.

   Druga sprawa jest dla tych z Was, ktorzy powaznie mysla o pisaniu
   aplikacji sieciowych. Jest to zbior norm RFC (Request For Comment).
   Opisuja one wszystkie publicznie uzywane protokoly, na przyklad HTTP,
   SMTP czy POP3: rfc-editor.org.

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
