V této kapitole si popíšeme základy komunikace v počítačových sítích, kde mohou být zapojeny i jiné systémy, než jen systémy typu unix. Dozvíme se také, jak využívat tyto služby v unixu, pomocí služby, která se nazývá sokety.
Nejdříve trocha teorie:
Aby mohly různé systémy spolu komunikovat po sítích, vypracoval mezinárodní normalizační úřad ISO takzvaný referenční model OSI (Reference Model for Open System Interconection). Jeho základem je rozdělení všech činností při výměně dat do sedmi částí -- vrstev, počínaje fyzickou definicí spoje a konče aplikačními programy -- viz. obr. 19. Každá vrstva definuje vlastnosti svých dvou rozhraní, tj. služby, poskytované vyšší vrstvě a služby, poskytované nižší vrstvě. Vrstvy jsou definovány i tvarem přenášených dat.
Obrázek 19: Vrstvová architektura modelu OSI
Jednotlivé vrstvy si ve stručnosti popíšeme:
V unixu se pro přenos v sítích používají často protokoly třídy TCP/IP. V názvu slyšíme sice jen dva protokoly, ale patří k nim ještě další. Jejich vzájemné vazby a analogie k jednotlivým OSI vrstvám lze vysledovat z obr. 20.
Obrázek 20: Vrstvy OSI v protokolech TCP/IP
Internetová adresa je (zatím) 32 bitová a obsahuje jak číslo sítě (netid), tak i číslo počítače v síti (hostid). Tyto adresy se dělí do několika tříd, podle velikosti sítě, viz obr. 21. Někdy se využívá v části hostid i tzv. podsítě.
Obrázek 21: Třídy IP adres
Internetové adresy se zapisují v desítkové soustavě, po bajtech a jsou oddělené tečkou, např. 147.32.80.1 -- což je jeden počítač na ČVUT FEL.
Berkeleyovské sokety (sockets) jsou jednou z nadstaveb (API -- Application Program Interface) nad komunikačními protokoly. Další je např. System V Transport Layer Interface (TLI).
Sokety slouží jako nadstavba nad internetovskými protokoly, dále nad tzv. Unix domain protokoly, které slouží pro komunikaci mezi procesy na jednom počítači, a nad protokoly XNS -- Xerox Network Systems.
My se zde zaměříme hlavně na protokoly TCP/IP a Unix domain.
Většina síťových systémových volání používá ukazatel na tuto strukturu, popsanou v <sys/socket.h>:
struct sockaddr {
u_short sa_family; /* rodina adres; jedna z~AF_xxx */
char sa_data[14]; /* až 14 bytů; závisí na protokolu */
}
Pro internetovskou rodinu jsou tyto definovány struktury v <netinet/in.h>:
struct in_addr {
u_long s_addr; /* 32 bitová adresa */
};
struct sockaddr_in {
short sin_family; /* AF_INET */
u_short sin_port; /* 16 bitové číslo portu */
struct in_addr sin_addr; /* 32 bitová adresa */
char sin_zero[8]; /* nepoužito */
}
Pro Unix doman je definována struktura v <sys/un.h>:
struct sockaddr_un {
short sun_family; /* AF_UNIX */
char sun_path[108]; /* cesta */
Oproti zvyklostem není sun_path ukončena nulou. Je také vidět, že struktura je delší, než obecná sockaddr. Naštěstí současné systémy podporují až 110 bajtovou strukturu. Ale např. směrovací tabulky v jádře podporují pouze 16 bajtovou strukturu, kterou však protokoly Unix domain nepotřebují...
Typický scénář komunikace pro spojované protokoly je vidět na
obr. 22
Obrázek 22: Soketové funkce pro spojované protokoly
a pro nespojované je na obr. 23.
Obrázek 23: Soketové funkce pro nespojované protokoly
První věcí, kterou proces při síťování dělá, je volání funkce socket, která specifikuje komunikační protokol.
| #include <sys/types.h> #include <sys/socket.h> |
| int socket (int family, int type, int protocol); |
| Vrací: deskriptor soketu když OK, -1 při chybě |
Rodina adres family je jedna z:
| AF_UNIX | interní unixové protokoly |
| AF_INET | internetové protokoly |
| AF_NS | protokoly Xerox NS |
| AF_IMPLINK | vrstva Interface Message Processor |
Typ soketu type může být:
| SOCK_STREAM | proudový soket (spojovaná služba) |
| SOCK_DGRAM | datagramový soket (nespojovaná služba) |
| SOCK_RAW | surový soket |
| SOCK_SEQPACKET | soketová posloupnost paketů (pro XNS) |
| SOCK_RDM | soket pro spolehlivě doručitelné zprávy (ještě neimplementováno) |
Jsou dovoleny jen některé kombinace family a type, viz tab. 15.
| AF_UNIX | AF_INET | AF_NS | |
|---|---|---|---|
| SOCK_STREAM | Ano | TCP | SPP |
| SOCK_DGRAM | Ano | UDP | IDP |
| SOCK_RAW | IP | Ano | |
| SOCK_SEQPACKET | SPP |
Protokoly, v jejichž kolonkách v tabulce 15 je Ano, nemají specifické jméno. Prázdné kolonky znamenají, že protokol není implementován.
Argument protocol bývá většinou 0, což nastaví implicitní, který je vidět z tab. 15. Další internetovské protokoly lze najít v souboru <netinet/in.h> (např. IPPROTO_ICMP).
| #include <sys/types.h> #include <sys/socket.h> |
| int socketpair (int family, int type, int protocol, int sockvec[2]); |
| Vrací: 0 když OK, -1 při chybě |
Funkce vrací dva nepojmenované soketové deskriptory v poli sockvec. Je podobná funkci pipe, viz kap. 9. Narozdíl od pipe vytváří ale obousměrnou, tzv. proudovou rouru.
Jsou dovolené jen dvě možnosti volání:
int rc, sockfd[2]; rc = socketpair (AF_UNIX, SOCK_STREAM, 0, sockfd);nebo
rc = socketpair (AF_UNIX, SOCK_DGRAM, 0, sockfd);
Funkce bind přiřadí jméno nepojmenovanému soketu.
| #include <sys/types.h> #include <sys/socket.h> |
| int bind (int sockfd, struct sockaddr *myaddr, int addrlen); |
| Vrací: 0 když OK, -1 při chybě |
myaddr obsahuje identifikátor závisející na přenosových protokolech.
K vytvoření spojení se serverem použije klient funkci connect.
| #include <sys/types.h> #include <sys/socket.h> |
| int connect (int sockfd, struct sockaddr *servaddr, int addrlen); |
| Vrací: 0 když OK, -1 při chybě |
sockfd vrátila funkce socket. servadrs je adresa serveru. Pro datagramy se spojení nevytvoří.
Funkce listen je používána serverem pro spojované služby, když si přeje, aby se s ním někdo spojil.
| #include <sys/types.h> #include <sys/socket.h> |
| int listen (int sockfd, int backlog); |
| Vrací: 0 když OK, -1 při chybě |
backlog určuje, kolik požadavků se má uložit do fronty, než se spustí server. Většinou se nastavuje na 5, což je zatím maximální povolená hodnota.
Funkci accept volá server většinou po listen.
| #include <sys/types.h> #include <sys/socket.h> |
| int accept (int sockfd, struct sockaddr *peer, int *addrlen); |
| Vrací: 0 když OK, -1 při chybě |
accept vezme první požadavek z fronty požadavků a vytvoří další soket se stejnými vlastnostmi jako sockfd. Pokud nejsou ve frontě žádné požadavky, funkce čeká. Lze ji nastavit i tak, aby nečekala, pomocí funkcí v kap. "Nastavování parametrů soketů".
Adresu klienta server zjistí z peer a addrlen.
Vlastní přenos dat se děje pomocí funkcí send, sendto, recv a recvfrom. Tyto funkce jsou podobné standardním funkcím read (kap. "Funkce read") a write (kap. "Funkce write") s dalšími argumenty.
| #include <sys/types.h> #include <sys/socket.h> |
| int send (int sockfd, char *buff, int nbytes, int flags); int sendto (int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to, int addrlen); int recv (int sockfd, char *buff, int nbytes, int flags); int recvfrom (int sockfd, char *buff, int nbytes, int flags, struct sockaddr *from, int *addrlen); |
| Vrací: počet poslaných/přijatých znaků když OK, -1 při chybě |
flags jsou buď 0 nebo logicky přičítají tyto konstanty:
| MSG_OOB | posílá/přijímá out-of-band data |
| MSG_PEEK | podívá se na přijatá data, aniž je vymaže z fronty |
| MSG_DONTROUTE | nebude směrovat při posílání |
Funkce sendto a recvfrom se používají pro nespojované protokoly.
Na zavírání soketů se používá standardní funkce close, viz kap. Funkce close.
Systém se normálně z této funkce vrací okamžitě, ale jádro se snaží poslat data, která jsou ve frontě.
Funkce shutdown nabízí více možností, jak uzavřít spojení, než close.
| #include <sys/socket.h> |
| int shutdown (int sockfd, int howto); |
| Vrací: 0 když OK, -1 při chybě |
Pokud je , soket nepřijme již žádná data, když je , soket již nic nevyšle, a když je , pak již přes soket neprojdou žádná data tam ani zpět.
Tyto funkce mění pořadí bajtů ve slovech podle architektury počítače.
| #include <sys/types.h> #include <netinet/in.h> |
| u_long htonl (u_long hostlong); u_short htons (u_short hostshort); u_long ntohl (u_long netlong); u_short ntohs (u_short netshort); |
| Vrací: překonvertované slovo |
Tyto funkce byly navrženy pro internetové protokoly. Fungují však i pro XNS. Na systémech, které mají stejné pořadí, jako internetové protokoly (Motorola 68000), jsou to jen nulová makra.
Protože se při komunikaci používají řetězce, které nekončí 0, jak je zvykem v C, je nutno nad nimi definovat přenositelné operace.
| #include <string.h> |
| void bcopy (char *src, char *dest, int nbytes); |
| void bzero (char *dest, int nbytes); |
| int bcmp (char *ptr1, char *ptr2, int nbytes); |
| Vrací: 0 když jsou stejné, jinak nenulovou hodnotu |
bcopy kopíruje daný počet bajtů z řetězce src do dest. Řetězce mohou obsahovat i nuly. bzero plní řetězec daným počtem nul. bcmp porovnává dva řetězce, ve kterých mohou být i nuly.
Jak bylo řečeno v kap. "TCP/IP -- internetové protokoly", internetové adresy se píší po bajtech, odděleny tečkou. Následující funkce převádějí tuto formu na strukturu in_addr (viz též kap. "TCP/IP -- internetové protokoly") a naopak.
| #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> |
| unsigned long inet_addr (char *ptr); char *inet_ntoa (struct in_addr inaddr); |
| Vrací: adresu |
Funkce inet_addr je již trochu zastaralá, neboť dobře neindikuje chybu. Nová funkce se jmenuje inet_aton.
Teď vyzkoušíme základní soketová systémová volání na jednoduchém příkladu:
Nejdříve si představíme jednotlivé podpůrné podprogramy, nejdříve pro spojované služby. První čte ze soketového deskriptoru.
/*
* Read "n" bytes from a descriptor.
* Use in place of read() when fd is a stream socket.
*/
int
readn(fd, ptr, nbytes)
register int fd;
register char *ptr;
register int nbytes;
{
int nleft, nread;
nleft = nbytes;
while (nleft > 0) {
nread = read(fd, ptr, nleft);
if (nread < 0)
return(nread); /* error, return < 0 */
else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(nbytes - nleft); /* return >= 0 */
}
/*
* Write "n" bytes to a descriptor.
* Use in place of write() when fd is a stream socket.
*/
int
writen(fd, ptr, nbytes)
register int fd;
register char *ptr;
register int nbytes;
{
int nleft, nwritten;
nleft = nbytes;
while (nleft > 0) {
nwritten = write(fd, ptr, nleft);
if (nwritten <= 0)
return(nwritten); /* error */
nleft -= nwritten;
ptr += nwritten;
}
return(nbytes - nleft);
}
/*
* Read a line from a descriptor. Read the line one byte at a time,
* looking for the newline. We store the newline in the buffer,
* then follow it with a null (the same as fgets(3)).
* We return the number of characters up to, but not including,
* the null (the same as strlen(3)).
*/
int
readline(fd, ptr, maxlen)
register int fd;
register char *ptr;
register int maxlen;
{
int n, rc;
char c;
for (n = 1; n < maxlen; n++) {
if ( (rc = read(fd, &c, 1)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
if (n == 1)
return(0); /* EOF, no data read */
else
break; /* EOF, some data was read */
} else
return(-1); /* error */
}
*ptr = 0;
return(n);
}
/*
* Read a stream socket one line at a time, and write each line back
* to the sender.
*
* Return when the connection is terminated.
*/
#define MAXLINE 512
str_echo(sockfd)
int sockfd;
{
int n;
char line[MAXLINE];
for ( ; ; ) {
n = readline(sockfd, line, MAXLINE);
if (n == 0)
return; /* connection terminated */
else if (n < 0)
err_dump("str_echo: readline error");
if (writen(sockfd, line, n) != n)
err_dump("str_echo: writen error");
}
}
/*
* Read the contents of the FILE *fp, write each line to the
* stream socket (to the server process), then read a line back from
* the socket and write it to the standard output.
*
* Return to caller when an EOF is encountered on the input file.
*/
#include <stdio.h>
#define MAXLINE 512
str_cli(fp, sockfd)
register FILE *fp;
register int sockfd;
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
while (fgets(sendline, MAXLINE, fp) != NULL) {
n = strlen(sendline);
if (writen(sockfd, sendline, n) != n)
err_sys("str_cli: writen error on socket");
/*
* Now read a line from the socket and write it to
* our standard output.
*/
n = readline(sockfd, recvline, MAXLINE);
if (n < 0)
err_dump("str_cli: readline error");
recvline[n] = 0; /* null terminate */
fputs(recvline, stdout);
}
if (ferror(fp))
err_sys("str_cli: error reading file");
}
/*
* Read a datagram from a connectionless socket and write it back to
* the sender.
*
* We never return, as we never know when a datagram client is done.
*/
#include <sys/types.h>
#include <sys/socket.h>
#define MAXMESG 2048
dg_echo(sockfd, pcli_addr, maxclilen)
int sockfd;
struct sockaddr *pcli_addr;
/* ptr to appropriate sockaddr_XX structure */
int maxclilen; /* sizeof(*pcli_addr) */
{
int n, clilen;
char mesg[MAXMESG];
for ( ; ; ) {
clilen = maxclilen;
n = recvfrom(sockfd, mesg, MAXMESG, 0, pcli_addr, &clilen);
if (n < 0)
err_dump("dg_echo: recvfrom error");
if (sendto(sockfd, mesg, n, 0, pcli_addr, clilen) != n)
err_dump("dg_echo: sendto error");
}
}
/*
* Read the contents of the FILE *fp, write each line to the
* datagram socket, then read a line back from the datagram
* socket and write it to the standard output.
*
* Return to caller when an EOF is encountered on the input file.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MAXLINE 512
dg_cli(fp, sockfd, pserv_addr, servlen)
FILE *fp;
int sockfd;
struct sockaddr *pserv_addr;
/* ptr to appropriate sockaddr_XX structure */
int servlen; /* actual sizeof(*pserv_addr) */
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
while (fgets(sendline, MAXLINE, fp) != NULL) {
n = strlen(sendline);
if (sendto(sockfd, sendline, n, 0, pserv_addr, servlen) != n)
err_dump("dg_cli: sendto error on socket");
/*
* Now read a message from the socket and write it to
* our standard output.
*/
n = recvfrom(sockfd, recvline, MAXLINE, 0,
(struct sockaddr *) 0, (int *) 0);
if (n < 0)
err_dump("dg_cli: recvfrom error");
recvline[n] = 0; /* null terminate */
fputs(recvline, stdout);
}
if (ferror(fp))
err_dump("dg_cli: error reading file");
}
Nejdříve ukážeme hlavičkový soubor inet.h, který používáme i v příkladu na UDP.
/*
* Definitions for TCP and UDP client/server programs.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERV_UDP_PORT 6000
#define SERV_TCP_PORT 6000
#define SERV_HOST_ADDR "192.43.235.6" /* host addr for server */
char *pname;
/*
* Example of server using TCP protocol.
*/
#include "inet.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd, newsockfd, clilen, childpid;
struct sockaddr_in cli_addr, serv_addr;
pname = argv[0];
/*
* Open a TCP socket (an Internet stream socket).
*/
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_dump("server: can't open stream socket");
/*
* Bind our local address so that the client can send to us.
*/
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERV_TCP_PORT);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
< 0)
err_dump("server: can't bind local address");
listen(sockfd, 5);
for ( ; ; ) {
/*
* Wait for a connection from a client process.
* This is an example of a concurrent server.
*/
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr,
&clilen);
if (newsockfd < 0)
err_dump("server: accept error");
if ( (childpid = fork()) < 0)
err_dump("server: fork error");
else if (childpid == 0) { /* child process */
close(sockfd); /* close original socket */
str_echo(newsockfd); /* process the request */
exit(0);
}
close(newsockfd); /* parent process */
}
}
/*
* Example of client using TCP protocol.
*/
#include "inet.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd;
struct sockaddr_in serv_addr;
pname = argv[0];
/*
* Fill in the structure "serv_addr" with the address of the
* server that we want to connect with.
*/
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
serv_addr.sin_port = htons(SERV_TCP_PORT);
/*
* Open a TCP socket (an Internet stream socket).
*/
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("client: can't open stream socket");
/*
* Connect to the server.
*/
if (connect(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
err_sys("client: can't connect to server");
str_cli(stdin, sockfd); /* do it all */
close(sockfd);
exit(0);
}
Tento náš příklad není spolehlivý, tj. nezaručuje doručení paketů. Server vypadá takto:
/*
* Example of server using UDP protocol.
*/
#include "inet.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd;
struct sockaddr_in serv_addr, cli_addr;
pname = argv[0];
/*
* Open a UDP socket (an Internet datagram socket).
*/
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
err_dump("server: can't open datagram socket");
/*
* Bind our local address so that the client can send to us.
*/
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERV_UDP_PORT);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
< 0)
err_dump("server: can't bind local address");
dg_echo(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr));
/* NOTREACHED */
}
/*
* Example of client using UDP protocol.
*/
#include "inet.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd;
struct sockaddr_in cli_addr, serv_addr;
pname = argv[0];
/*
* Fill in the structure "serv_addr" with the address of the
* server that we want to send to.
*/
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
serv_addr.sin_port = htons(SERV_UDP_PORT);
/*
* Open a UDP socket (an Internet datagram socket).
*/
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
err_dump("client: can't open datagram socket");
/*
* Bind any local address for us.
*/
bzero((char *) &cli_addr, sizeof(cli_addr)); /* zero out */
cli_addr.sin_family = AF_INET;
cli_addr.sin_addr.s_addr = htonl(INADDR_ANY);
cli_addr.sin_port = htons(0);
if (bind(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr))
< 0)
err_dump("client: can't bind local address");
dg_cli(stdin, sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr));
close(sockfd);
exit(0);
}
Spojovaný server je podobný příkladu na TCP. Nejdříve si ukážeme hlavičkový soubor unix.h, využívaný v dalších příkladech.
/*
* Definitions for UNIX domain stream and datagram client/server
* programs.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIXSTR_PATH "./s.unixstr"
#define UNIXDG_PATH "./s.unixdg"
#define UNIXDG_TMP "/tmp/dg.XXXXXX"
char *pname;
/*
* Example of server using UNIX domain stream protocol.
*/
#include "unix.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd, newsockfd, clilen, childpid, servlen;
struct sockaddr_un cli_addr, serv_addr;
pname = argv[0];
/*
* Open a socket (a UNIX domain stream socket).
*/
if ( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
err_dump("server: can't open stream socket");
/*
* Bind our local address so that the client can send to us.
*/
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, UNIXSTR_PATH);
servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
if (bind(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0)
err_dump("server: can't bind local address");
listen(sockfd, 5);
for ( ; ; ) {
/*
* Wait for a connection from a client process.
* This is an example of a concurrent server.
*/
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr,
&clilen);
if (newsockfd < 0)
err_dump("server: accept error");
if ( (childpid = fork()) < 0)
err_dump("server: fork error");
else if (childpid == 0) { /* child process */
close(sockfd); /* close original socket */
str_echo(newsockfd); /* process the request */
exit(0);
}
close(newsockfd); /* parent process */
}
}
/*
* Example of client using UNIX domain stream protocol.
*/
#include "unix.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd, servlen;
struct sockaddr_un serv_addr;
pname = argv[0];
/*
* Fill in the structure "serv_addr" with the address of the
* server that we want to send to.
*/
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, UNIXSTR_PATH);
servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
/*
* Open a socket (an UNIX domain stream socket).
*/
if ( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
err_sys("client: can't open stream socket");
/*
* Connect to the server.
*/
if (connect(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0)
err_sys("client: can't connect to server");
str_cli(stdin, sockfd); /* do it all */
close(sockfd);
exit(0);
}
V tomto datagramovém příkladu je vidět zajímavý problém, se kterým jsme se ještě nesetkali -- sokety jsou identifikovány jmény souborů a proto klient musí vyprodukovat pro bind unikátní jméno souboru.
Server:
/*
* Example of server using UNIX domain datagram protocol.
*/
#include "unix.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd, servlen;
struct sockaddr_un serv_addr, cli_addr;
pname = argv[0];
/*
* Open a socket (a UNIX domain datagram socket).
*/
if ( (sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
err_dump("server: can't open datagram socket");
/*
* Bind our local address so that the client can send to us.
*/
unlink(UNIXDG_PATH); /* in case it was left from last time */
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, UNIXDG_PATH);
servlen = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);
if (bind(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0)
err_dump("server: can't bind local address");
dg_echo(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr));
/* NOTREACHED */
}
/*
* Example of client using UNIX domain datagram protocol.
*/
#include "unix.h"
main(argc, argv)
int argc;
char *argv[];
{
int sockfd, clilen, servlen;
char *mktemp();
struct sockaddr_un cli_addr, serv_addr;
pname = argv[0];
/*
* Fill in the structure "serv_addr" with the address of the
* server that we want to send to.
*/
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, UNIXDG_PATH);
servlen = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);
/*
* Open a socket (a UNIX domain datagram socket).
*/
if ( (sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
err_dump("client: can't open datagram socket");
/*
* Bind a local address for us.
* In the UNIX domain we have to choose our own name (that
* should be unique). We'll use mktemp() to create a unique
* pathname, based on our process id.
*/
bzero((char *) &cli_addr, sizeof(cli_addr)); /* zero out */
cli_addr.sun_family = AF_UNIX;
strcpy(cli_addr.sun_path, UNIXDG_TMP);
mktemp(cli_addr.sun_path);
clilen = sizeof(cli_addr.sun_family) + strlen(cli_addr.sun_path);
if (bind(sockfd, (struct sockaddr *) &cli_addr, clilen) < 0)
err_dump("client: can't bind local address");
dg_cli(stdin, sockfd, (struct sockaddr *) &serv_addr, servlen);
close(sockfd);
unlink(cli_addr.sun_path);
exit(0);
}
Je několik způsobů, jak měnit parametry soketů:
Funkce fcntl a ioctl byly popsány v kapitole "Souborový vstup a výstup".
Funkcí getsockopt lze získat informace o soketu.
| #include <sys/types.h> #include <sys/socket.h> |
| int getsockopt (int sockfd, int level, int optname, char *optval, int *optlen); int setsockopt (int sockfd, int level, int optname, char *optval, int optlen); |
| Vrací: 0 když OK, -1 při chybě |
Ladislav Dobias