Baixe o app para aproveitar ainda mais
Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
Departamento de Engenharia Electrotécnica e de Computadores Secção de Telecomunicações Redes de Computadores I Introdução à Interface "Socket" do UNIX Licenciatura em Engenharia de Redes de Comunicação e de Informação Prof. Paulo Pereira 1 SOCKETS Domínio INTERNET: Transporte Internet Interface de Rede Socket Camadas Conceptuais Interface 1 Interface 2 Interface 3 Módulo IP Protocolo 1 Protocolo 2 Protocolo 3 Socket Organização de Software 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. PF_UNIX - Domínio Unix, local a uma máquina PF_INET - Domínio Inet, redes Internet type 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) ; 2 Connectionless (SOCK_DGRAM) Servidor bloqueia até receber dados de um cliente processa pedido recvfrom ( ) bind ( ) socket ( ) socket ( ) sendto ( ) sendto ( ) recvfrom ( ) Cliente dados(pedido) dados(resposta) 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). 3 Connection-oriented (SOCK_STREAM) Servidor bloqueia até ter ligação de um cliente processa pedido accept ( ) bind ( ) socket ( ) connect ( ) socket ( ) read ( ) write ( ) read ( ) Cliente dados(pedido) dados(resposta) listen ( ) estabelecimento de ligação write ( ) 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. 4 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: • 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) Saídas : • 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) 5 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 deve- se 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"); 6 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; 7 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. 8 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)); 9 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"); exit(1); } 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 Synopsis bcmp (s1, s2, n) bcopy (s1, s2, n) bzero (base, n) htonl (val) htons (val) ntohl (val) ntohs (val) 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 10 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. 11 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"); exit(1); } /* * 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); } 12 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 sock, length; struct sockaddr_in name, from; int fromlen= sizeof(from); char buf[BUFSIZE]; /* Create socket on which to read. */ sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("opening datagram socket"); exit(1); } /* 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"); exit(1); } /* Find assigned port value and print it out. */ length = sizeof(name); if (getsockname(sock, &name, &length)) { perror("getting socket name"); exit(1); } 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); } 13 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"); exit(1); } /* Connect socket using name specified by command line. */ hp = gethostbyname(argv[1]); if (hp == 0) { fprintf(stderr, "%s: unknown host\n", argv[1]); exit(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"); exit(1); } if (write(sock, DATA, strlen(DATA)+1) < 0) perror("writing on stream socket"); close(sock); } 14 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"); exit(1); } /* 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"); exit(1); } /* Find assigned port value and print it out. */ length = sizeof(server); if (getsockname(sock, &server, &length)) { perror("getting socket name"); exit(1); } 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); } } } 15 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 sd, ns, n, fromlen, active; fd_set rmask, Rmask; char buf[BUFSIZE]; struct sockaddr_in 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); } } } } 16 Exemplo de utilização de processos filhos com sockets orientados à conexão no domínio Internet. int main() { int sock, msgsock, n, pid; struct sockaddr_in server; char buf[BUFSIZE+1]; /* Criar socket... */ /* ignorar o signal SIG_PIPE para evitar core dumps quando escrever para um socket com ligação fechada */ signal(SIGPIPE, SIG_IGN); /* ignorar o signal SIG_CHILD para evitar processos zombies */ signal(SIGCHLD, SIG_IGN); /* começar a aceitar ligações */ listen(sock, 5); while (1) { msgsock= accept(sock, NULL, NULL); if (msgsock == -1) perror("accept"); else { printf("Father process accepted new connection. Starting a child\n"); if ( (pid=fork()) == -1) perror("fork"); else if (pid==0) { pid= getpid(); printf("Son %d running\n", pid); /* fechar o socket para aceitar ligações: fica aberto apenas no processo pai */ close(sock); 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); } printf("Son %d exiting\n", pid); exit(0); } printf("Father started child %d\n", pid); /* Pai: fecha o socket utilizado pelo filho */ close(msgsock); } } }
Compartilhar