Pisanie programów sieciowych pod Linuksem

Linux jest systemem typowo sieciowym. Nawet niektóre usługi systemowe działają jako serwery sieciowe, umożliwiając dostęp maszynom z zewnątrz. A ja bez niepotrzebnego zagłębiania się w porty, protokoły i inne szczegóły dotyczące sieci, pokażę teraz, jak napisać prosty, własny serwerek i klienta do niego.

Komunikacja w sieci odbywa się z wykorzystaniem wielu różnych elementów. Podstawowym pojęciem jest gniazdo (ang. socket). Jest to logiczne (czyli nie istniejące fizyczne) urządzenie będące podstawową bramką, przez którą przepływają informacje. Gniazdko tworzy się funkcją socket (z biblioteki języka C, tak jak wszystkie późniejsze). Przyjmuje ona 3 argumenty (patrz: man 2 socket):

  1. domena - określa typ połączenia. My wykorzystamy wartość PF_INET=2, oznaczającą protokoły internetowe (IPv4)
  2. typ gniazda - określa, czy gniazdo jest datagramowe, strumieniowe, surowe (raw) itp. My wykorzystamy gniazdo strumieniowe SOCK_STREAM=1 i protokół TCP (Transmission Control Protocol), który gwarantuje wiarygodne, dwustronne połączenie.
  3. protokół, jeśli nie jest on jednoznacznie wyznaczony. U nas TCP jest jednoznacznie wyznaczony przez typ gniazda (strumieniowe), więc ten argument przyjmuje wartość 0.

Jeśli utworzenie gniazda nie udało się, funkcja socket zwróci wartość -1. Jeśli się udało, zwróci liczbę całkowitą - deskryptor otwartego gniazda (podobnie, jak w plikach). Po zakończeniu pracy gniazdo można zamknąć funkcją close.

Od chwili utworzenia gniazda dalszy kod w serwera i klienta różnią się, więc omówię je po kolei.


Serwer


(przeskocz opis serwera)

Jak wiemy, zadaniem serwera jest nasłuchiwanie połączeń od klientów. Aby to osiągnąć, należy wykonać następujące kroki.

  1. Przypisanie gniazda do adresu.

    W chwili utworzenia, gniazdo nie jest jeszcze przypisane do adresu, a przecież trzeba jakoś określić, na jakim adresie i porcie nasłuchuje nasz serwer. Służy do tego funkcja bind. Przyjmuje ona następujące argumenty (patrz: man 2 bind):

    1. gniazdo, które utworzyliśmy funkcją socket
    2. adres struktury sockaddr, którą zaraz się zajmiemy
    3. długość tejże struktury

    Choć definicja funkcji bind mówi o strukturze sockaddr, to funkcji tej podaje się odpowiednio rzutowany wskaźnik do struktury sockaddr_in. Ta struktura wygląda tak:


    (przeskocz strukturę sockaddr_in)
    	  struc sockaddr_in
    		.sin_family resw 1	; rodzina adresów
    		.sin_port   resw 1	; numer portu
    		.sin_addr   resd 1	; adres
    		            resb 8	; dopełnienie do 16 bajtów
    	  endstruc

    Do pola sin_family wpisujemy AF_INET=2, oznaczające rodzinę adresów internetowych.

    Do pola sin_port wpisujemy numer portu, na którym będzie nasłuchiwał nasz serwer. Ale uwaga - nie bezpośrednio! Najpierw numer portu musi zostać przetłumaczony na sieciowy porządek bajtów funkcją htons (patrz: man htons). Dopiero wynik funkcji, której podajemy numer portu, wpisujemy w to pole. Programy bez uprawnień administratora mogą korzystać tylko z portów o numerach powyżej 1023.

    Do pola sin_addr wpisujemy wartość INADDR_ANY=0, co oznacza, że chcemy nasłuchiwać na dowolnym adresie.

    W przypadku błędu, bind zwraca -1.

  2. Włączenie nasłuchiwania na danym gnieździe.

    Aby włączyć nasłuchiwanie na danym gnieździe, należy użyć funkcji listen. Przyjmuje ona dwa argumenty (patrz: man 2 listen):

    1. gniazdo, utworzone funkcją socket z adresem przypisanym funkcją bind
    2. maksymalną liczbę klientów oczekujących w kolejce na obsługę

    W przypadku błędu, listen zwraca -1.

    Jeśli funkcja listen się powiedzie, to można z serwerem przejść w tryb demona (o tym w kursie o pisaniu programów rezydentnych).

Po włączeniu nasłuchiwania na gnieździe możemy zacząć przyjmować połączenia od klientów. Przyjęcie połączenia odbywa się funkcją accept. Przyjmuje ona trzy argumenty (patrz: man 2 accept):

  1. nasłuchujące gniazdo
  2. zero lub adres struktury sockaddr (lub tej samej sockaddr_in, którą podaliśmy dla bind). Struktura ta otrzyma dane o kliencie (na przykład jego adres)
  3. adres zmiennej zawierającej długość struktury z parametru numer 2

Gdy klient już się połączył, accept zwraca deskryptor nowego gniazda, które będzie służyć do komunikacji z klientem.



Klient


(przeskocz opis klienta)

W porównaniu z serwerem, w kliencie jest mniej pracy. Po utworzeniu gniazda do połączenia się z serwerem wystarczy jedna funkcja - connect. Przyjmuje ona trzy argumenty (patrz: man 2 connect):

  1. gniazdo utworzone funkcją socket
  2. adres struktury sockaddr
  3. długość tejże struktury

Tutaj także zamiast struktury sockaddr przekazujemy adres struktury sockaddr_in. Jednak trzeba ją trochę inaczej wypełnić.

Pola sin_family i sin_port wypełniamy tak samo, jak dla bind. W końcu chcemy się połączyć do tego samego portu, na którym nasłuchuje serwer.

Pole sin_addr wypełniamy adresem IP serwera. Oczywiście nie wprost jako łańcuch znaków, ale odpowiednio przerobionym. Do przerobienia łańcucha znaków 127.0.0.1 (oznaczającego zawsze bieżący komputer dla niego samego) na właściwą postać posłuży nam funkcja inet_aton. Przyjmuje ona 2 argumenty (patrz: man inet_aton):

  1. adres łańcucha znaków z adresem w zapisie dziesiętnym kropkowym (ttt.xxx.yyy.zzz)
  2. adres struktury in_addr, która otrzyma wynik

Struktura in_addr jest jedyną składową pola sin_addr w naszej strukturze sockaddr_in i to adres tego właśnie pola podajemy funkcji inet_aton.

Po poprawnym wykonaniu połączenia funkcją connect, można przystąpić do wymiany danych.



Wymiana danych


(przeskocz wymianę danych)

Po dokonaniu połączenia obie strony - klient i serwer - mają gotowe gniazda, którymi mogą się komunikować. Do wymiany danych służą dwie podstawowe funkcje: send i recv. Obie przyjmują dokładnie te same cztery parametry (patrz: man 2 send, man 2 recv):

  1. gniazdo, które jest połączone z klientem/serwerem
  2. adres bufora odbiorczego/nadawczego
  3. długość tego bufora
  4. specjalne flagi, jeśli jest taka potrzeba. U nas będzie to zero.


Przykład

Po przebrnięciu przez tł trudną teorię możemy wreszcie przystąpić do pisania programów. Wiem, że sucha teoria nie umożliwi natychmiastowego napisania programów serwera i klienta (jest wiele pułapek, na które trzeba zwrócić uwagę), dlatego prezentuję tutaj przykładowe programy serwera i klienta (składnia 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		; będziemy korzystać z biblioteki C, więc
			; funkcja główna musi się nazywać "main"

; definicje kilku przydatnych stałych
%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 klientów

; zewnętrzne funkcje z biblioteki C, z których będziemy korzystać
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 błąd
	jl	.sock_blad

	mov	[gniazdo], eax		; zachowujemy deskryptor gniazda

	push	word NPORTU
	call	htons			; przerabiamy numer portu na
					; właściwy format
					; htons(NPORTU);
	add	esp, 2

			; wpisujemy przerobiony numer portu:
	mov	[adres+sockaddr_in.sin_port], ax
			; rodzina adresów internetowych:
	mov	word [adres+sockaddr_in.sin_family], AF_INET
			; akceptujemy każdy 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			; włączamy nasłuchiwanie:
					; 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 argumentów 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 połączenie
				; accept(gniazdo,&adres,&rozmiar)
	add	esp, 12
	cmp	eax, 0
	jl	.czekaj

	mov	[gniazdo_kli], eax	; gdy accept się udało,
					; 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			; jeśli błąd, to czekamy ponownie
	jl	.rozmowa

	cmp	byte [bufor], "Q"	; ustalamy, że Q kończy transmisję
	je	.koniec

	mov	ecx, buf_d
	mov	edi, bufor
	xor	eax, eax
	cld
	rep	stosb			; czyścimy bufor

	push	dword 0
	push	dword 2
	push	dword ok
	push	dword [gniazdo_kli]
	call	send			; wysyłamy 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			; wysyłamy Q, które jest w buforze
	add	esp, 16

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

; jeśli chcemy, aby serwer nasłuchiwał kolejnych połączeń, piszemy tu:
;;; 	jmp	.czekaj
; serwera nie da się wtedy inaczej zamknąć niż przez zabicie procesu

	push	dword [gniazdo]
	call	close			; zamykamy gniazdo główne serwera
	add	esp, 4

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


; obsługa błędów:

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

	mov	eax, 1
	mov	ebx, 1
	int	80h			; wyjście z programu z
					; odpowiednim kodem błędu

.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		; długość bufora

						; komunikaty błędów:
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 wysyłamy

struc sockaddr_in

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

			resb	8		; dopełnienie do 16 bajtów
endstruc

adres		istruc	sockaddr_in		; adres jako zmienna, która
						; jest strukturą
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		; będziemy korzystać z biblioteki C, więc
			; funkcja główna musi się nazywać "main"

; definicje kilku przydatnych stałych
%define PF_INET		2
%define AF_INET		PF_INET
%define SOCK_STREAM	1
%define INADDR_ANY	0

%define	NPORTU		4242

; zewnętrzne funkcje z biblioteki C, z których będziemy korzystać
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 błąd
	jle	.sock_blad

	mov	[gniazdo], eax		; zachowujemy deskryptor gniazda

					; rodzina adresów 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
					; właściwy format
	add	esp, 8
	test	eax, eax		; EAX = 0 oznacza, że adres
					; był nieprawidłowy
	jz	.inet_blad

	push	word NPORTU
	call	htons			; przerabiamy numer portu
					; na właściwy 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			; łączymy się 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 wejścia

	push	dword 0
	push	dword buf_d
	push	dword bufor
	push	dword [gniazdo]
	call	send			; wysyłamy to, co wczytaliśmy:
				; 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			; czyścimy 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 odebraliśmy

	cmp	byte [bufor], "Q"		; "Q" kończy transmisję
	jne	.rozmowa

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

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


; sekcja obsługi błędów


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

	mov	eax, 1
	mov	ebx, 1
	int	80h			; wyjście z programu z
					; odpowiednim kodem błędu

.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		; długość bufora
		db	10			; przejście do nowej linii
odebrano_dl	equ	$ - odebrano

						; komunikaty błędów
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 którym
						; będziemy się łączyć

struc sockaddr_in

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

			resb	8		; dopełnienie do 16 bajtów
endstruc

adres		istruc	sockaddr_in		; adres jako zmienna,
						; która jest strukturą

Jako że programy te korzystają z biblioteki języka C, ich kompilacja musi wyglądać trochę inaczej niż zwykle:

	nasm -f elf -o plik.o plik.asm
	gcc -o plik plik.o

Po kompilacji najpierw oczywiście uruchamiamy serwer poleceniem ./serwer (program serwera sam przejdzie w tło). Możecie sprawdzić, co się stanie, jeśli dwa razy spróbujecie uruchomić serwer lub uruchomicie klienta bez uruchomionego serwera.

Oczywiście, serwer może też być klientem innego serwera (na przykład po odebraniu danych przerabiać je i przekazywać dalej).



Funkcje sieciowe przerwania int 80h


(przeskocz int 80h)

Korzystanie z sieci jest oczywiście możliwe także bez pośrednictwa biblioteki języka C. W końcu każda tak istotna funkcja przecież musi być zaprogramowana jako część jądra.

Interfejs sieciowy jądra to jedna funkcja - sys_socketcall (numer 102). Przyjmuje ona dwa argumenty. Pierwszy (w EBX) to funkcja, którą chcemy uruchomić. Każda wspomniana wcześniej funkcja z biblioteki C ma swój numer. Są to: dla socket - 1, dla bind - 2, connect - 3, listen - 4, accept - 5, send - 9, recv - 10. Funkcja close jest tą samą, której używa się do zamykania plików (a więc EBX=[gniazdo], EAX=6, int 80h).

Drugim argumentem (w ECX) jest adres reszty argumentów, które podalibyśmy funkcji z biblioteki C. Można je bez przeszkód w tej samej kolejności, co wcześniej, umieścić na stosie, po czym wykonać instrukcję mov ecx, esp. Z resztą, tak to właśnie robi biblioteka C (plik sysdeps/unix/sysv/linux/i386/socket.S w źródłach glibc, tam jednak jest "ecx+4", gdyż należy przeskoczyć jeszcze adres powrotny z funkcji). Można te dane umieścić oczywiście w swojej sekcji danych i podać ich adres, ale dane te muszą być jedna po drugiej dokładnie w takiej kolejności, w jakiej znajdowałyby na stosie (czyli od lewej do prawej na wzrastających adresach). Po prostu po kolei, według deklaracji C, od lewej do prawej.

Do omówienia zostają jeszcze funkcje pomocnicze - htons i inet_aton.

Funkcja htons jest dość prosta w budowie (plik sysdeps/i386/htons.S w źródłach glibc), jej treść mieści się w takim oto makrze (zakładając, że argument jest w EAX):

	%macro htons 0
		and	eax, 0FFFFh
		ror	ax, 8
	%endm

Czyli po prostu zeruje górną połowę EAX i zamienia zawartość rejestrów AH i AL między sobą.

Funkcja inet_aton (plik resolv/inet_addr.c w źródłach glibc) jest trochę trudniejsza. Wolę znacznie wszystko skrócić i powiedzieć, że adres należy załadować do rejestru EAX binarnie, czyli na przykład z 127.0.0.1 dostajemy EAX=7F000001h, a z 192.168.0.2 - EAX=C0A80002h. Potem trzeba odwrócić kolejność bajtów. Najlepiej od początku skorzystać z następującego 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

	; użycie:
		adr2bin	127, 0, 0, 1	   ; dla adresu 127.0.0.1
		adr2bin	192, 168, 45, 243  ; dla adresu 192.168.45.243

którego wynik (EAX) zapisujemy do pierwszych czterech bajtów pola sin_addr struktury sockaddr_in (co normalnie funkcja inet_aton robiła automatycznie).

To całe odwracanie bierze się z tego, że porządek bajtów w protokole TCP jest typu big-endian, a procesory zgodne z Intelem są typu little-endian.

O tym, jak pisać demony korzystając wyłącznie z przerwania int 80h, napisałem w kursie o pisaniu programów rezydentnych.



Funkcje sieciowe w systemie 64-bitowym


(przeskocz system 64-bitowy)

Obsługa sieci różni się nieco na systemach 64-bitowych w porównaniu z systemami 32-bitowymi. Nie tylko zmienia się numer funkcji, ale teraz poszczególne operacje sieciowe mają swoje własne funkcje systemowe. Są to: socket - 41, connect - 42, accept - 43, sendto - 44, recvfrom - 45, bind - 49, listen - 50. Reszta parametrów 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 wywołanie systemu następuje instrukcją syscall, a nie poprzez przerwanie 80h.

Przykładowe wywołania funkcji wyglądają więc następująco:

	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 są takie same, jak dla systemów 32-bitowych (bo przecież kolejność bajtów przesyłanych w sieci się nie zmienia).



Warto jeszcze wspomnieć o dwóch sprawach. Pierwsza to programy strace i ltrace. Pozwalają one na śledzenie, których funkcji systemowych i kiedy dany program używa. Jeśli coś Wam nie działa, wyłączcie tryb demona w serwerze, po czym uruchomcie strace ./serwer i patrzcie, na których wywołaniach funkcji są jakieś problemy. Podobnie możecie oczywiście zrobić z klientem, na przykład na drugim terminalu. Po szczegóły odsyłam do stron manuala.

Drugą sprawa jest dla tych z Was, którzy poważnie myślą o pisaniu aplikacji sieciowych. Jest to zbiór norm RFC (Request For Comment). Opisują one wszystkie publicznie używane protokoły, na przykład HTTP, SMTP czy POP3: rfc-editor.org.


Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)