Autor: Jiří Hnídek / jiri.hnidek@tul.cz
# su netstat -l -x
Active UNIX domain sockets (only servers)
Proto RefCnt Flags Type State I-Node Path
unix 2 [ ACC ] STREAM LISTENING 28162 /tmp/.X11-unix/X0
int socket(int domain, int type, int protocol);
int bind(int sock_fd, struct sockaddr *addr);
int connect(int sock_fd, struct sockaddr *addr, int addr_len);
int listen(int sock_fd, int queue_len);
int accept(int sock_fd, struct sockaddr *addr, int addr_len);
int read(int sock_fd, char *buf, int buf_len);
int write(int sock_fd, char *buf, int buf_len);
int close(sock_fd);
Systémové volání socket vrací deskriptor souborů, který je potřeba spojit (bind) s adresou a následně je možné ho použít pro komunikaci.
Otázka: kolik deskriptorů souborů může mít proces otevřený (ve výchozím nastavení)?
Defaultně může mít každý proces otevřeno 1024 deskriptorů souborů (viz. příkaz: ulimit -a).
Určuje jmenný prostor, ve kterém se bude provádět komunikace. Přibližně odpovídá protokolu linkové vrstvy. Některé vybrané hodnoty:
Typ protokolu určuje druh komunikace mezi koncovými body.
Specifikuje konrétní protokol, který bude použit pro komunikaci.
Provede TCP teardown
addres.sin_family = AF_INET;
addres.sin_addr.s_addr = inet_addr("127.0.0.1");
addres.sin_port = htons(PORT);
addres.sin6_family = AF_INET6;
inet_pton(AF_INET6, argv[1], &addres.sin6_addr);
addres.sin6_port = htons(PORT);
htons(), htonl(), ntohs(), ntohl()
V produkčním kódu je nutné kontrolovat návratové hodnoty všech systémových volání a patřičným způsobem na ně reagovat. V případě, že návratová hodnota bude ignorována může být výsledný kód nespolehlivý i nebezpečný.
Dokumentaci k návratovým hodnotám hledejte v manuálových stránkách, např.: man 2 accept
Pokud je socket v blokujícím režimu, tak každé systémové volání bude zablokování v případě, kdy to nedovoluje některá ze síťových vrstev.
Příkladem může být systémové volání write() v případě, že dojde k ucpání přenosových cest nebo dojde k zahlcení příjemce.
Takové chování je ve většině případu nevhodné.
Socket se do neblokujícího režimu musí přepnout explicitně:
int flag;
flag = fcntl(listen_sock_fd, F_GETFL, 0);
fcntl(listen_sock_fd, F_SETFL, flag | O_NONBLOCK);
Výhoda tohoto přístupu spočívá v tom, že proces se vám nemůže na systémovém volání zablokovat. Nevýhodou je, že je nutné reagovat na více variant (viz manuálové stránky).
Proces (server i klient) mohou čekat na událost pomocí některého systémového volání (select, pselect, poll, epoll, atd.)
Pokud má server efektivně vyřizovat požadavky klientů, tak je vhodné to dělat paralelně. V některých případech je lepší použít víceprocesový přístup (např.: HTTP). V jiných případech je lepší použít vícevláknový přístup (např.: server sdílené virt. reality).
Např. při neblokujícím režimu socketů je vhodné pravidelně zjišťovat kolik dal je možné odeslat.
Velikost bufferu pro odchozího a příchozí data (jednoho spojení) při použití protokolu TCP/UDP lze zjistit mnoha způsoby.
Informace o bufferu lze zjistit například v adresáři /proc:
cat /proc/sys/net/ipv4/tcp_rmem
Tento soubor obsahuje 3 hodnoty (v bytech): minimální, aktuální a maximální velikost bufferu pro příchozí data.
cat /proc/sys/net/ipv4/tcp_wmem
Informace pro odchozí data jednoho spojení.
V programu můžeme získat aktuální velikost bufferu pomocí:
int rcv_buf_size, send_buf_size;
unsigned int int_size = sizeof(rcv_buf_size);
getsockopt(sock_fd, SOL_SOCKET, SO_RCVBUF,
(void *)&rcv_buf_size, &int_size);
getsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF,
(void *)&send_buf_size, &int_size);
Na Linuxu lze zjistit využité místo v bufferu odchozích dat pomocí:
ioctl(sock_fd, SIOCOUTQ, &buf_size);
throughput = buffer_size / latency;
Pokud nám velikost bufferu nestačí, tak ji můžeme navýšit pomocí:
setsockopt(sock_fd, SOL_SOCKET, SO_RCVBUF,
(void *)&rcv_buf_size, int_size);
setsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF,
(void *)&send_buf_size, int_size);
Pozor: kernel násobí nastavovanou hodnotu dvěmi.
buffer_size = RTT * bandwidth;
I protokol UDP má samozřejmě omezení na bufferu:
cat /proc/sys/net/ipv4/udp_mem
Velikosti bufferu lze nastavovat/zjišťovat analogicky jako u TCP.
Jelikož se jedná o nespojovaný protokol, tak mnohá systémová volání postrácejí smysl (listen, accept).
Na druhou stranu se některé věci komplikují.
Datagramy lze odesílat pomocí send(), sendto(), sendmsg().
Přijímat datagramy lze analogicky pomocí recv(), recvfrom(), recvmsg().
Produkční server budeme chtít tzv. démonizovat:
Server budeme chtít pravidelně spouštět při startu, elegantně zastavovat či restartovat, atd. K tomu je potřeba udělat několik "drobných" změn a vytvořit spouštěcí skripty.