Departamento de Engenharia Electrotécnica e de Computadores Secção de Telecomunicações Redes de Computadores I Introdução à Interface "Socket" do UNIX Licenciatura de Engenharia Informática e de Computadores Prof. Vitor Vargas Eng. Paulo Pereira Eng. Luís Bernardo
SOCKETS Domínio INTERNET: Camadas Conceptuais Socket Transporte Organização de Software Socket Protocolo 1 Protocolo 2 Protocolo 3 Internet Interface de Rede Módulo IP Interface 1 Interface 2 Interface 3 int socket (int domain, int type, int protocol) ; Cria um porto para comunicação assíncrona, bidireccional e retorna um descritor (idêntico aos utilizados nos ficheiros e pipes). domain - domínio onde o socket é criado, que define os protocolos e o espaço de nomes. type PF_UNIX - Domínio Unix, local a uma máquina PF_INET - Domínio Inet, redes Internet SOCK_STREAM - orientado à ligação, comunicação fiável e sequencial. SOCK_DGRAM - sem ligação, mensagens limitadas no comprimento, comunicação sem garantia de sequencialidade, fiabilidade ou não existência de duplicações. protocol - depende do domínio. Normalmente é colocado a zero, que indica o protocolo por omissão no domínio respectivo (TCP, UDP). int bind (int s, struct sockaddr *name, int namelen) ; Associa um nome a um socket já criado. s - identificador do socket. name - o nome depende do domínio onde o socket foi criado. No domínio UNIX corresponde a um "pathname". No domínio Internet é do tipo struct sockaddr_in, que é composto pelo endereço da máquina, protocolo e número de porto. namelen - inteiro igual a sizeof(*name) int close (int s) ; 1
Connectionless (SOCK_DGRAM) Servidor socket ( ) bind ( ) recvfrom ( ) Cliente socket ( ) bloqueia até receber dados de um cliente processa pedido sendto ( ) dados(pedido) dados(resposta) sendto ( ) recvfrom ( ) int sendto (int s, char *msg, int len, int flags, struct sockaddr *to, int tolen) ; Envia uma mensagem através do socket s para o socket especificado em to independentemente de existir ou não ligação estabelecida entre os dois sockets. msg - mensagem a enviar. len - dimensão da mensagem a enviar flags : MSG_OOB - Out of band to - endereço do socket para onde vai ser enviada a mensagem. tolen - inteiro igual a sizeof(*to) int recvfrom (int s, char *buf, int len, int flags, struct sockaddr *from, int * fromlen) ; ou int recv (int s, char *buf, int len, int flags) ; ou int read (int s, char *buf, int len) ; Recebe uma mensagem através do socket s de um socket remoto independentemente de existir ou não ligação estabelecida entre os dois. Devolve o tamanho da mensagem lida. buf - buffer para a mensagem a receber. len - dimensão do buffer. flags : MSG_OOB - Out of band. MSG_PEEK- Ler sem consumir. from - endereço do socket que enviou a mensagem. fromlen - ponteiro para inteiro inicializado a sizeof(*from). 2
Connection-oriented (SOCK_STREAM) Servidor socket ( ) bind ( ) listen ( ) accept ( ) bloqueia até ter ligação de um cliente estabelecimento de ligação Cliente socket ( ) connect ( ) read ( ) processa pedido write ( ) dados(pedido) dados(resposta) write ( ) read ( ) int connect (int s, struct sockaddr *name, int namelen) ; Estabelece uma ligação entre o socket s e o outro socket indicado em name. int listen (int s, int backlog) ; Indica que o socket s pode receber ligações. backlog - comprimento da fila de espera de novos pedidos de ligação. int accept (int s, struct sockaddr *addr, int * addrlen) ; Bloqueia o processo até um processo remoto estabelecer uma ligação. Retorna o identificador de um novo socket para transferência de dados. int send (int s, char *msg, int len, int flags) ; ou int write(int s, char *msg, int len) ; Envia uma mensagem através do socket s para o socket remoto associado. Retorna o número de bytes efectivamente enviados, ou -1 em caso de erro. No caso de a ligação ser cortada, o processo pode receber um signal SIGPIPE. int recv (int s, char *buf, int len, int flags) ; ou int read (int s, char *buf, int len) ; Recebe uma mensagem do socket remoto através do socket s. Retorna o número de bytes lidos, ou 0 se a ligação foi cortada, ou -1 se a operação foi interrompida. int shutdown (int s, int how) ; how: 0 - no more reading, 1 - no more writing, 2 - no more reading or writing. 3
Multiplexagem das Entradas/Saídas ( select ) #include <sys/types.h> #include <sys/time.h> int select ( int width, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) ; Bloqueia o processo até que uma das operações de E/S pendentes se realize. Entradas: Saídas : máscaras readfds, writefds, exceptfds - padrão de bits indicando os descritores de E/S sobre os quais se pretende efectuar operações de Leitura, Escrita ou processar Excepções. width - número do maior descritor a considerar na máscara + 1. Este parâmetro pode ser preenchido com o valor retornado pela função getdtablesize() que retorna o número máximo de descritores que pode estar associado a um processo, ou com a macro FD_SETSIZE. timeout - tempo de espera (0 = bloqueio) máscaras readfds, writefds, exceptfds - padrão de bits indicando os descritores "ready". Retorna - número de descritores "ready", 0 se expirou o timeout, -1 se foi interrompido ou se houve um erro. Para modificar os bits das máscaras são utilizadas as funções (macros): FD_ZERO (fd_set *fdset) Coloca todos os bits da máscara a 0. FD_SET (int fd, fd_set *fdset) Liga o bit correspondente ao descritor de ficheiro fd. FD_CLR (int fd, fd_set *fdset) Limpa o bit correspondente ao descritor de ficheiro fd. FD_ISSET (int fd, fd_set *fdset) Testa se o bit correspondente ao descritor de ficheiro fd está activo. Em versões antigas do Unix foi utilizado o tipo int em vez de fd_set, mas nesse caso, o máximo valor de width suportado é 32. É possível obter o descritor do teclado com: fileno(stdin) 4
Exemplo de utilização ( leitura bloqueante de mensagens de 2 descritores ) : #define NO_DESC 0 #define BLOCK 0 fd_set rmask; /* máscara de leitura */ int sd1, sd2, /* Descritores de sockets */ n; FD_ZERO(&rmask); FD_SET(sd1, &rmask); FD_SET(sd2, &rmask); n= select (getdtablesize(), &rmask, NO_DESC, NO_DESC, BLOCK); if (n <= 0) { fprintf(stderr, "Interruption of select\n");... else { if (FD_ISSET(sd1, &rmask)) { /* Há dados disponíveis para leitura no socket sd1 */... if (FD_ISSET(sd2, &rmask)) { /* Há dados disponíveis para leitura no socket sd2 */... Configurar um socket para escrita não bloqueante Para que uma chamada sistema de escrita num socket s não seja bloqueante devese ligar a flag NDELAY: #include <sys/fcntl.h> int flags; if ((flags= fcntl(s, F_GETFL)) < 0) perror("fcntl error getting socket flags"); flags = O_NDELAY; if (fcntl(s, F_SETFL, flags) < 0) perror("fcntl error setting NO DELAY on socket"); Outro método de obter o mesmo resultado: #include <sys/filio.h> int nonblock=1; if (ioctl(s, FIONBIO, &nonblock) < 0) perror("ioctl error setting NO DELAY on socket"); 5
As funções apresentadas anteriormente retornam o valor -1 em caso de falha. A variável errno é afectada com o código da ocorrência. perror escreve a mensagem de erro do sistema operativo associada a esse código de erro. Em seguida apresenta-se a definição de alguns dos tipos referidos anteriormente: <sys/time.h>: struct timeval { long tv_sec; /* seconds */ long tv_usec; /* and microseconds */ ; <sys/socket.h>: struct sockaddr { u_short sa_family; /* Address family : AF_xxx */ char sa_data[14]; /* up to 14 bytes of protocol specific address */ ; <netinet/in.h>: struct in_addr { u_long s_addr; /* 32-bit netid/hostid network byte ordered */ ; struct sockaddr_in { short sin_family; /* AF_INET */ u_short sin_port; /* 16-bit port number network byte ordered */ struct in_addr sin_addr; /* 32-bit netid/hostid network byte ordered */ char sin_zero[8]; /* unused */ ; <netdb.h>: struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses, null terminated */ ; #define h_addr h_addr_list[0] /* first address, network byte order */ <sys/types.h>: typedef long fd_mask; typedef struct fd_set { fd_mask fds_bits[... ]; fd_set; 6
Dados fora de banda (OOB) em sockets orientados à conexão Os sockets orientados à conexão suportam o envio de dados fora de banda constituídos por um byte de informação. Uma possível utilização é o envio de CONTROL-C. O envio de dados fora de banda é feito utilizando a função send() com a flag MSG_OOB. A recepção dos dados pode ser síncrona, mantendo-se a ordem em relação aos dados normais com que foram enviados. Nesse caso, pode-se utilizar qualquer das primitivas descritas anteriormente para ler dados de sockets. A função ioctl permite descobrir se os próximos dados lidos do socket s são "fora de banda" ou normais: int yes; ioctl (s, SIOCATMARK, &yes); if (yes) { /* dados fora de banda */ else { /* dados normais */ Outra hipótese será fazer leitura assíncrona, associando o signal SIGURG à recepção de dados fora de banda. Essa associação é feita utilizando a função ioctl sobre o socket s, fornecendo como parâmetro o pid do processo vezes -1, como está representado: #ifdef sun #include <sys/sockio.h> #else #include <sys/ioctl.h> #endif int pid= -getpid(); if (ioctl(s, SIOCSPGRP, (char *)&pid) < 0) { perror ("ioctl: SIOCSPGRP,"); Neste caso, os dados fora de banda são lidos na rotina de tratamento do signal, utilizando a função recv() ou recvfrom() com a flag MSG_OOB. 7
Funções auxiliares Existem definidas um conjunto de funções de biblioteca. struct hostent *gethostbyname(char *hostname); Devolve o identificador de host/rede associado ao hostname. unsigned long inet_addr(char *cp); Converte uma string cp na notação standard da Internet d.d.d.d no endereço IP em formato rede, devolvendo -1 em caso de erro. char *inet_ntoa(struct in_addr in); Converte o endereço IP in, no formato rede para uma string com o endereço d.d.d.d associado. No quadro seguinte apresenta-se um excerto de um programa com o preenchimento de uma estrutura sockaddr_in com um endereço dado pelo número de porto e nome do host (máquina). #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> char *host_name= "mega.ist.utl.pt"; struct sockaddr_in addr; struct hostent *hp;... hp= gethostbyname(host_name); if (hp == NULL) { fprintf (stderr, "%s : unknown host\n", host_name);... bzero((char *)&addr, sizeof addr); bcopy (hp->h_addr, (char *) &addr.sin_addr, hp->h_length); addr.sin_family= AF_INET; addr.sin_port= htons(port_number); printf("built address %s:%d\n", inet_ntoa(addr.sin_addr), (int)ntohs(addr.sin_port)); 8
Quando se está a preencher a estrutura para uma chamada à função bind, pode-se inicializar o campo sin_addr.s_addr com INADDR_ANY, que fica automaticamente associado a todos os endereços IP do host local. Se o porto for inicializado a 0, então é atribuído um valor durante a chamada a bind. int getsockname ( int s, struct sockaddr *addr, int *addrlen ); Utilizada para obter a estrutura com o endereço associado ao socket s. Em seguida apresenta-se um excerto de um programa, onde se obtém o número de porto associado a um socket s. struct sockaddr_in addr; int len= sizeof(addr);... if (getsockname(s, &addr, &len)) { fprintf(stderr, "Error getting socket name\n"); if (addr.sin_family!= AF_INET) { fprintf(stderr, "Nao e' socket INET\n");... printf("socket has port #%d\n", ntohs(addr.sin_port)); Outras funções C Run-time Routines : Call bcmp (s1, s2, n) bcopy (s1, s2, n) bzero (base, n) htonl (val) htons (val) ntohl (val) ntohs (val) Synopsis Compare byte-strings; 0 if same, not 0 otherwise Copy n bytes from s1 to s2 Zero-fill n bytes starting at base 32-bit quantity (long) from host into network byte order 16-bit quantity (short) from host into network byte order 32-bit quantity (long) from network into host byte order 16-bit quantity (short) from network into host byte order 9
Para permitir a portabilidade do código para máquinas que utilizem uma ordem dos bytes diferentes da ordem utilizada na rede, devem ser utilizadas as rotinas?to?? (em <netinet/in.h>) para manipular as componentes dos endereços. Estas funções também podem ser utilizadas para possibilitar a transmissão de shorts e ints entre máquinas que usam uma ordem diferente dos bytes. Note-se que a função memcpy tem os argumentos pela ordem inversa de bcopy, e o seu correcto funcionamento não é garantido quando as zonas de memória se sobrepõem. A função bcopy trata correctamente de sobreposições. Em alguns sistemas operativos tem o nome memmove. A função bzero pode ser substituída por memset com os parâmetros adequados. Por razões de eficiência, deve-se evitar cópias e limpezas de memória desnecessárias. 10
Exemplo de envio de dados datagram no domínio Internet. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define DATA "The sea in calm tonight, the tide is full..." /* * Here I send a datagram to a receiver whose name I get * from the command line arguments. */ main (int argc, char *argv[]) { int sock; struct sockaddr_in name; struct hostent *hp; /* Create socket on which to send. */ sock = socket(pf_inet, SOCK_DGRAM, 0); if (sock < 0) { perror("opening datagram socket"); /* * Construct name, with no wildcards, of the socket to send * to. Gethostbyname() returns a structure including the * network address of the specified host. The port number * is taken from the command line. */ hp = gethostbyname(argv[1]); if (hp == 0) { fprintf(stderr, "%s: unknown host\n", argv[1]); exit(2); bcopy(hp->h_addr, &name.sin_addr, hp->h_length); name.sin_family = AF_INET; name.sin_port = htons(atoi(argv[2])); /* Send message. */ if (sendto(sock, DATA, strlen(data)+1, 0, &name, sizeof(name)) < 0) perror("sending datagram message"); close(sock); 11
Exemplo de recepção de dados datagram no domínio Internet. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> /* * This program creates a datagram socket, binds a name * to it, then reads from the socket. */ #define BUFSIZE 1024 main () { int struct sockaddr_in int char sock, length; name, from; fromlen= sizeof(from); buf[bufsize]; /* Create socket on which to read. */ sock = socket(pf_inet, SOCK_DGRAM, 0); if (sock < 0) { perror("opening datagram socket"); /* Create name with wildcards. */ name.sin_family = AF_INET; name.sin_addr.s_addr = INADDR_ANY; name.sin_port = 0; if (bind(sock, &name, sizeof(name))) { perror("binding datagram socket"); /* Find assigned port value and print it out. */ length = sizeof(name); if (getsockname(sock, &name, &length)) { perror("getting socket name"); printf("socket has port #%d\n", ntohs(name.sin_port)); /* Read from the socket */ if (recvfrom(sock, buf, BUFSIZE, 0, &from, &fromlen) < 0) perror("receiving datagram packet"); printf("-->%s\n", buf); close(sock); 12
Exemplo de envio de dados stream no domínio Internet. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define DATA "Half a league, half a league..." /* * This program creates a socket and initiates a connection * with the socket given in the command line. One message * is sent over the connection and then the socket is * closed, ending the connection. */ main (int argc, char *argv[]) { int sock; struct sockaddr_in server; struct hostent *hp; /* Create socket */ sock = socket(pf_inet, SOCK_STREAM, 0); if (sock < 0) { perror("opening stream socket"); /* Connect socket using name specified by command line. */ hp = gethostbyname(argv[1]); if (hp == 0) { fprintf(stderr, "%s: unknown host\n", argv[1]); bcopy(hp->h_addr, &server.sin_addr, hp->h_length); server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); if (connect(sock, &server, sizeof(server)) < 0) { perror("connecting stream socket"); if (write(sock, DATA, strlen(data)+1) < 0) perror("writing on stream socket"); close(sock); 13
Exemplo de recepção de dados stream no domínio Internet. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #define BUFSIZE 1024 main () { int sock, msgsock, length, n; struct sockaddr_in server; char buf[bufsize+1]; /* Create socket on which to read. */ sock = socket(pf_inet, SOCK_STREAM, 0); if (sock < 0) { perror("opening stream socket"); /* Create name with wildcards. */ server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = 0; if (bind(sock, &server, sizeof(server))) { perror("binding stream socket"); /* Find assigned port value and print it out. */ length = sizeof(server); if (getsockname(sock, &server, &length)) { perror("getting socket name"); printf("socket has port #%d\n", ntohs(server.sin_port)); /* Start accepting connections */ listen (sock, 5); while (1) { msgsock = accept(sock, 0, 0); if (msgsock == -1) perror("accept"); else { if ((n=read(msgsock, buf, BUFSIZE)) < 0) perror("receiving stream data"); else if (n==0) fprintf(stderr, "EOF\n"); else { buf[n]='\0'; printf("-->%s\n", buf); close(msgsock); 14
Exemplo de utilização da instrução select()com sockets orientados à conexão no domínio Internet. /* Processo Servidor */ #include <sys/types.h> #include <sys/socket.h> #define BUFSIZE 1024 #define BLOCK 0 #define NO_DESC 0 int which (fd_set *mask) {... procurar primeiro bit ligado... main () { int fd_set char struct sockaddr_in sd, ns, n, fromlen, active; rmask, Rmask; buf[bufsize]; NameSocket, OtherSocket; sd = socket (PF_INET, SOCK_STREAM, 0);... Preenchimento do endereço em NameSocket... bind (sd, &NameSocket, sizeof(struct sockaddr_in)); listen (sd, 1); FD_ZERO(&rmask); FD_SET(sd, &rmask); fromlen= sizeof(struct sockaddr_in); for ( ; ; ) { memcpy(&rmask, &rmask, sizeof(rmask)); select (FD_SETSIZE, &Rmask, NO_DESC, NO_DESC, BLOCK); active= which (&Rmask); if (active == sd) { /* Recepção no socket de Ligações */ ns= accept (sd, &OtherSocket, &fromlen); FD_SET (ns, &rmask); else { /* Recepção num socket ordinário */ n= read (active, buf, BUFSIZE); if (n>0) printf("o serviço recebeu %.*s\n", n, buf); else { /* == 0: fim de ligação; == -1: erro */... Tratamento do fim de ligação, ou erro... FD_CLR(active, &rmask); close(active); 15