next_inactive up previous


Sockets. TCP/UDP sockets. UNIX sockets

Razvan Deaconescu

10 decembrie 2005

Interfata de socket-i a fost introdusa in versiunile Berkeley de Unix (BSD), ca o extensie a conceptului de pipe. Socket-ii pot fi utilizati la fel ca si pipe-urile, dar includ comunicatia intre calculatoare prin intermediul retelei.

Un socket este un mecanism de comunicare care permite sistemelor client/server sa fie dezvoltate fie local, pe un singur calculator, fie de-a lungul retelei. Socket-ii sunt creati altfel decat pipe-urile pentru ca fac o distinctie clara intre client si server. Mecanismul de socket-i poate implementa mai multi clienti atasati la un singur server.

Comunicatia prin socket-i

Socket-ii se utilizeaza intr-un sistem client/server. O aplicatie de tip server creeaza un socket si ii asociaza un nume: pentru socket-ii UNIX acesta va fi o intrare in sistemul de fisiere, iar pentru socket-ii de retea va fi un identificator de forma adresa IP + port. In continuare server-ul asteapta conexiuni de la clienti.

In momentul in care se accepta conexiunea, se creeaza un nou socket diferit de cel pe care s-au asteptat conexiuni. Acesta este folosit exclusiv pentru comunicatia cu clientul, primul socket putand astepta, in continuare, conexiuni de la clienti. In acest fel se pot folosi mai multe conexiuni.

Partea de client este mult mai directa. Se creeaza un socket si apoi se realizeaza conexiunea cu server-ul folosind pe post de adresa numele socket-ului server.

Exemplu - Echo Client/Echo Server cu socket-i UNIX

/*
 * UNIX socket family client
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main()
{
        int sockfd;
        int len;
        struct sockaddr_un address;
        int result;
        char ch = 'A';

        sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

        address.sun_family = AF_UNIX;
        strcpy(address.sun_path, "server_socket");
        len = sizeof(address);

        result = connect(sockfd, (struct sockaddr *)&address, len);

        if(result == -1) {
                perror("oops: client1");
                exit(1);
        }

        write(sockfd, &ch, 1);
        read(sockfd, &ch, 1);
        printf("char from server = %c\n", ch);
        close(sockfd);
        exit(0);
}

/*
 * UNIX socket family server
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main()
{
        int server_sockfd, client_sockfd;
        int server_len, client_len;
        struct sockaddr_un server_address;
        struct sockaddr_un client_address;

        unlink("server_socket");
        server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

        server_address.sun_family = AF_UNIX;
        strcpy(server_address.sun_path, "server_socket");
        server_len = sizeof(server_address);
        bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

        listen(server_sockfd, 5);
        while(1) {
                char ch;

                printf("server waiting\n");

                client_len = sizeof(client_address);
                client_sockfd = accept(server_sockfd, 
                                       (struct sockaddr *)&client_address, &client_len);

                read(client_sockfd, &ch, 1);
                ch++;
                write(client_sockfd, &ch, 1);
                close(client_sockfd);
        }
}

Exemplu - Echo Client/Echo Server cu socket-i de retea

/*
 * INET socket family client
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main()
{
        int sockfd;
        int len;
        struct sockaddr_in address;
        int result;
        char ch = 'A';

        sockfd = socket(AF_INET, SOCK_STREAM, 0);


        address.sin_family = AF_INET;
        address.sin_addr.s_addr = inet_addr("127.0.0.1");
        address.sin_port = htons(9734);
        len = sizeof(address);

        result = connect(sockfd, (struct sockaddr *)&address, len);

        if(result == -1) {
                perror("oops: client1");
                exit(1);
        }

        write(sockfd, &ch, 1);
        read(sockfd, &ch, 1);
        printf("char from server = %c\n", ch);
        close(sockfd);
        exit(0);
}

/*
 * INET socket family server
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main()
{
        int server_sockfd, client_sockfd;
        int server_len, client_len;
        struct sockaddr_in server_address;
        struct sockaddr_in client_address;

        server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

        server_address.sin_family = AF_INET;
        server_address.sin_addr.s_addr = htonl(INADDR_ANY);
        server_address.sin_port = htons(9734);
        server_len = sizeof(server_address);
        bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

        listen(server_sockfd, 5);
        while(1) {
                char ch;

                printf("server waiting\n");

                client_len = sizeof(client_address);
                client_sockfd = accept(server_sockfd, 
                                       (struct sockaddr *)&client_address, &client_len);

                read(client_sockfd, &ch, 1);
                ch++;
                write(client_sockfd, &ch, 1);
                close(client_sockfd);
        }
}

Comenzile pentru compialarea si rularea programelor de mai sus sunt:

razvan@valhalla:~/so/labs/lab11$ gcc -Wall -o client_un client_un.c
razvan@valhalla:~/so/labs/lab11$ gcc -Wall -o server_un server_un.c
razvan@valhalla:~/so/labs/lab11$ ./server_un &
server waiting
[2] 15576
razvan@valhalla:~/so/labs/lab11$ ./client_un
server waiting
char from server = B
razvan@valhalla:~/so/labs/lab11$ ls
client_in    client_un.o  lab11.ps        server_in      server_un.c
client_in.c  lab11        lab11-teme.lyx  server_in.c
client_un    lab11.html   lab11-teme.tex  server_socket
client_un.c  lab11.lyx    lab11.tex       server_un
razvan@valhalla:~/so/labs/lab11$ ls -l server_socket
srwxr-xr-x  1 razvan razvan     0 2005-12-15 10:25 server_socket

razvan@valhalla:~/so/labs/lab11$ gcc -Wall -o client_in client_in.c
razvan@valhalla:~/so/labs/lab11$ gcc -Wall -o server_in server_in.c
razvan@valhalla:~/so/labs/lab11$ ./server_in &
server waiting
[1] 15586
razvan@valhalla:~/so/labs/lab11$ ./client_in
server waiting
char from server = B

Crearea unui socket

Pentru utilizarea socket-ilor este nevoie de crearea unui socket cu intermediul unui apel de sistem, apelul socket:

#include <sys/types.h>
#include <sys/socket.h>

int socket (int domain, int type, int protocol);

domain poate avea una din valorile

type poate fi

protocolul va fi IPPROTO_TCP sau IPPROTO_UDP, dar se prefera utilizarea lui 0.

Adresele de socket

Fiecare domeniu de socket necesita un format de adrese propriu. Structurile, respectiv, pentru socket-i UNIX si petru socket-i AF_INET sunt prezentate mai jos:

#include <sys/un.h>

struct sockaddr_un {
     sa_family_t sun_family;   /* AF_UNIX */
     char sun_path[];
};

#include <netinet/in.h>

struct sockaddr_in {
     short int      sin_family;   /* AF_INET */
     unsigned short int sin_port; /* port number */
     struct in_addr sin_addr;     /* Internet address */
};

struct in_addr {
     unsigned long int s_addr;
};

Toate aceste structuri au un format comun (provin dintr-o structura generica struct sockaddr) pentru a putea permite transmiterea acesteia ca si parametru pentru apelurile de sistem. Cand folosim socket-i AF_UNIX adresa este specificata de un fisier parte a caii sun_path.

Socket-ii AF_INET pot fi complet descrisi prin domeniu, adresa IP si port.

Din punct de vedere a aplicatiei, socket-ii apar ca descriptori de fisier si sunt adresati de o valoare intreaga unica.

Numirea unui socket

Pentru ca un socket sa fie folosit de catre alte procese, un program server trebuie sa asocieze acestuia un nume. In acest fel, socket-ii AF_UNIX sunt asociati cu un fisier din sistemul de fisiere iar socket-ii AF_INET sunt asociati cu o adresa IP si numar de port.

#include <sys/socket.h>

int bind (int socket, const struct sockaddr *address, size_t address_len);

Apelul de sistem bind realizeaza atasarea adresei specificate ca parametru unui socket fara nume asociat cu descriptorul de fisier. Se paseaza lungimea structurii in campul address_len. Lungimea si formatul adresei depinde de familia utilizata. Vom utiliza un cast al unei structuri particulare la structura generica (struct sockaddr *).

Crearea unei cozi de asteptare pe socket

Pentru a accepta conexiunile pe un socket, un program server va trebui sa creeze o coada pentru a stoca cererile sosite de la programe client. Pentru a realiza acest lucru foloseste apelul de sistem listen.

#include <sys/socket.h>

int listen (int socket, int backlog);

Sistemul de operare poate limita numarul maxim de cereri de conectare care pot fi mentinute in coada. Lungimea acestei cozi este stabilita la valoarea backlog. O valoare de 5 este una obisnuita, pentru a permite server-ului mentinerea de conexiuni in timp ce se ocupa de un client anterior.

Acceptarea de conexiuni

O data ce un program server a creat un socket cu nume, poate astepta conexiunile sosite de la client folosind apelul accept.

#include <sys/socket.h>

int accept (int socket, struct sockaddr *address, size_t *address_len);

Apelul accept se intoarce atunci cand un program client incearca sa se conecteze la un socket specificat de parametrul socket. Apelul accept creeaza un nou socket care sa comunice cu clientul (primul socket va fi folosit tot pentru ascultarea conexiunilor) si se intoarce descriptorul acestui socket. Noul socket va avea acelasi tip ca si socket-ul de tip listener.

Parametrul socket trebuie sa fi fost numit folosind bind si sa aiba o coada de conexiuni creata de apelul listen. Adresa socket-ului client apelant va fi stocata in structura transmisa ca parametru (la fel ca si lungimea ei). Se poate folosi NULL daca nu ne intereseaza adresa clientului.

Conectarea la server

Programele client se conecteaza la server prin stabilirea unei conexiuni intre un socket fara nume si un socket listener. Acest lucru se realizeaza folosind apelul connect:

#include <sys/socket.h>

int connect (int socket, const struct sockaddr *address, size_t address_len);

Socket-ul specificat ca parametru este conectat la socket-ul server specificat de adresa si lungimea ei. Socket-ul trebuie sa fie un descriptor de fisier valid obtinut printr-un apel socket.

Un socket poate fi inchis folosind apelul close.

Byte Order

Numere ce semnifica porturile si adresele IP sunt comunicate pe interfetele tip socket ca numere binare. Totusi, arhitecturi diferite utilizeaza ordonare diferita a octetilor (byte order). Pentru a permite comunicatia calculatoarelor de tipuri diferite folosind numere multioctet, se defineste un format de ordonare in retea: network byte order. Programele server si client vor face conversia in astfel de formate inainte de transmitere. Functiile utilizate sunt:

#include <netinet/in.h>

unsigned long int htonl (unsigned long int hostlong);
unsigned long int ntohl (unsigned long int netlong);
unsigned short int htons (unsigned short int hostshort);
unsigned short int ntohs (unsigned short int netshort);

Functiile lucreaza cu numere, respectiv, pe 16 sau 32 de biti.

Informatii despre statiile din retea

Pentru programe client-server mai generale, putem folosi functii de informatie ca sa determinam adresele si port-urile de utilizat. Pentru aceasta se folosesc sisteme de informare precum NIS sau DNS. Functiile utilizate pentru acest lucru sunt:

#include <netdb.h>

struct hostent *gethostbyaddr (const void *addr, size_t len, int type);
struct hostent *gethostbyname (const char *name);

struct hostent {
     char *h_name;         /* name of host */
     char **h_aliases;     /* lista de alias-uri */
     int h_addrtype;       /* address type */
     int h_length;         /* lungimea adresei in octeti */
     char **h_addr_list;   /* lista de adrese (network order) */
};

#include <arpa/inet.h>

char *inet_ntoa (struct in_addr in);
char *inet_aton (const char *cp, struct in_addr *inp);

Structura struct hostent contine informatii despre host-ul respectiv.

Functiile inet_ntoa si inet_aton sunt folosite pentru a asigura conversia din format sir in format intreg a adreselor de host si invers.

Transmiterea mesajelor folosind socket-i

Pentru ca un socket este identificat prin intermediul unui descriptor de fisier, se poate face transmiterea de mesaje folosind read/write. Totusi, pentru prezenta anumitor optiuni si pentru intuitivitate se recomanda folosirea functiilor send/recv pentru comunicatie cu conexiune si sendto/recvfrom pentru comunicatie UDP.

ssize_t send (int s, const void *buf, size_t len, int flags);
ssize_t sendto (int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

ssize_r recv (int s, void *buf, size_t len, int flags);
ssize_r recvfrom (int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

Deoarece in cazul UDP nu exista apeluri de tipul accept, connect se va preciza adresa sursei sau destinatiei chiar ca si parametru al lui sendto/recvfrom.

About this document ...

Sockets. TCP/UDP sockets. UNIX sockets

This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.71)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were:
latex2html -nosubdir -split 0 lab11.tex

The translation was initiated by Razvan Deaconescu on 2005-12-15


next_inactive up previous
Razvan Deaconescu 2005-12-15